feat(property-marketing): 新增PDF导出功能并优化投放指南页面交互
This commit is contained in:
@ -21,7 +21,7 @@ export function configAutoImport() {
|
|||||||
'@vueuse/core',
|
'@vueuse/core',
|
||||||
{
|
{
|
||||||
dayjs: [['default', 'dayjs']],
|
dayjs: [['default', 'dayjs']],
|
||||||
'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'isNumber'],
|
'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'isNumber', 'isEmpty'],
|
||||||
'@/hooks': ['useModal'],
|
'@/hooks': ['useModal'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -75,7 +75,6 @@ let previousFileListLength = 0;
|
|||||||
//删除图片
|
//删除图片
|
||||||
const onChange = (fileList) => {
|
const onChange = (fileList) => {
|
||||||
if (fileList.length < previousFileListLength) {
|
if (fileList.length < previousFileListLength) {
|
||||||
// 在这里执行你需要的操作
|
|
||||||
if (props.limit === 1) {
|
if (props.limit === 1) {
|
||||||
if (fileList.length === 0) {
|
if (fileList.length === 0) {
|
||||||
emit('update:modelValue', '');
|
emit('update:modelValue', '');
|
||||||
@ -90,7 +89,6 @@ const onChange = (fileList) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新上一次的文件列表长度
|
|
||||||
previousFileListLength = fileList.length;
|
previousFileListLength = fileList.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="part-div">
|
<div class="part-div">
|
||||||
<div class="part-div-header">
|
<div class="part-div-header">
|
||||||
<span class="part-div-header-title">本月摘要</span>
|
<span class="part-div-header-title">总体摘要</span>
|
||||||
<a-popover position="tl">
|
<a-popover position="tl">
|
||||||
<icon-question-circle />
|
<icon-question-circle />
|
||||||
<template #content>
|
<template #content>
|
||||||
<p style="margin: 0">本月摘要2。</p>
|
<p style="margin: 0">基于筛选出来的投流账户/计划的情况生成的总体描述。</p>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<a-popover position="tl">
|
<a-popover position="tl">
|
||||||
<icon-question-circle />
|
<icon-question-circle />
|
||||||
<template #content>
|
<template #content>
|
||||||
<p style="margin: 0">投放建议优化。</p>
|
<p style="margin: 0">基于筛选出来的投流账户/计划的情况生成的优化建议。</p>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</div>
|
</div>
|
||||||
@ -15,7 +15,15 @@
|
|||||||
<a-row class="grid-demo" :gutter="{ md: 8, lg: 24, xl: 32 }">
|
<a-row class="grid-demo" :gutter="{ md: 8, lg: 24, xl: 32 }">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<div class="overall-strategy">
|
<div class="overall-strategy">
|
||||||
<span class="placement-optimization-title">总体策略</span>
|
<span class="placement-optimization-title"
|
||||||
|
>总体策略
|
||||||
|
<a-popover position="tl">
|
||||||
|
<icon-question-circle />
|
||||||
|
<template #content>
|
||||||
|
<p style="margin: 0">优化建议的整体调整概述。</p>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</span>
|
||||||
<span class="placement-optimization-str">{{ props.optimization?.[0]?.['content'] }}</span>
|
<span class="placement-optimization-str">{{ props.optimization?.[0]?.['content'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -23,13 +31,29 @@
|
|||||||
<a-row class="grid-demo" style="margin-right: 10px" :gutter="{ md: 8, lg: 24, xl: 32 }">
|
<a-row class="grid-demo" style="margin-right: 10px" :gutter="{ md: 8, lg: 24, xl: 32 }">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="overall-strategy">
|
<div class="overall-strategy">
|
||||||
<span class="placement-optimization-title">预算分配</span>
|
<span class="placement-optimization-title"
|
||||||
|
>预算分配
|
||||||
|
<a-popover position="tl">
|
||||||
|
<icon-question-circle />
|
||||||
|
<template #content>
|
||||||
|
<p style="margin: 0">优化建议在预算分配部分的详细描述。</p>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</span>
|
||||||
<span class="placement-optimization-str">{{ props.optimization?.[1]?.['content'] }}</span>
|
<span class="placement-optimization-str">{{ props.optimization?.[1]?.['content'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="overall-strategy">
|
<div class="overall-strategy">
|
||||||
<span class="placement-optimization-title">时段优化</span>
|
<span class="placement-optimization-title"
|
||||||
|
>时段优化
|
||||||
|
<a-popover position="tl">
|
||||||
|
<icon-question-circle />
|
||||||
|
<template #content>
|
||||||
|
<p style="margin: 0">优化建议在时段优化部分的详细描述。</p>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</span>
|
||||||
<span class="placement-optimization-str">{{ props.optimization?.[2]?.['content'] }}</span>
|
<span class="placement-optimization-str">{{ props.optimization?.[2]?.['content'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -37,13 +61,29 @@
|
|||||||
<a-row class="grid-demo" style="margin-right: 10px" :gutter="{ md: 8, lg: 24, xl: 32 }">
|
<a-row class="grid-demo" style="margin-right: 10px" :gutter="{ md: 8, lg: 24, xl: 32 }">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="overall-strategy">
|
<div class="overall-strategy">
|
||||||
<span class="placement-optimization-title">人群包优化</span>
|
<span class="placement-optimization-title"
|
||||||
|
>人群包优化
|
||||||
|
<a-popover position="tl">
|
||||||
|
<icon-question-circle />
|
||||||
|
<template #content>
|
||||||
|
<p style="margin: 0">优化建议在人群包优化部分的详细描述。</p>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</span>
|
||||||
<span class="placement-optimization-str">{{ props?.optimization?.[3]?.['content'] }}</span>
|
<span class="placement-optimization-str">{{ props?.optimization?.[3]?.['content'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="overall-strategy">
|
<div class="overall-strategy">
|
||||||
<span class="placement-optimization-title">素材优化</span>
|
<span class="placement-optimization-title"
|
||||||
|
>素材优化
|
||||||
|
<a-popover position="tl">
|
||||||
|
<icon-question-circle />
|
||||||
|
<template #content>
|
||||||
|
<p style="margin: 0">优化建议在素材优化部分的详细描述。</p>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</span>
|
||||||
<span class="placement-optimization-str">{{ props?.optimization?.[4]?.['content'] }}</span>
|
<span class="placement-optimization-str">{{ props?.optimization?.[4]?.['content'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<a-popover position="tl">
|
<a-popover position="tl">
|
||||||
<icon-question-circle />
|
<icon-question-circle />
|
||||||
<template #content>
|
<template #content>
|
||||||
<p>本周总消耗</p>
|
<p>当前周内所有投流账户的累计广告花费,反映整体投放规模。</p>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<a-popover position="tl">
|
<a-popover position="tl">
|
||||||
<icon-question-circle />
|
<icon-question-circle />
|
||||||
<template #content>
|
<template #content>
|
||||||
<p>本周总消耗环比</p>
|
<p>本周消耗金额与上周对比的变化百分比,用于衡量预算投放趋势。</p>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<a-popover position="tl">
|
<a-popover position="tl">
|
||||||
<icon-question-circle />
|
<icon-question-circle />
|
||||||
<template #content>
|
<template #content>
|
||||||
<p>ROI</p>
|
<p>投资回报率(ROI)= 收益 ÷ 投入成本,反映整体投流账户的收益效率。</p>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
@ -46,7 +46,7 @@
|
|||||||
<a-popover position="tl">
|
<a-popover position="tl">
|
||||||
<icon-question-circle />
|
<icon-question-circle />
|
||||||
<template #content>
|
<template #content>
|
||||||
<p>CTR</p>
|
<p>点击率(CTR)= 点击量 ÷ 展示量,是衡量广告素材吸引力的关键指标。</p>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import top1 from '@/assets/img/captcha/top1.svg';
|
import top1 from '@/assets/img/captcha/top1.svg';
|
||||||
import top2 from '@/assets/img/captcha/top2.svg';
|
import top2 from '@/assets/img/captcha/top2.svg';
|
||||||
import top3 from '@/assets/img/captcha/top3.svg';
|
import top3 from '@/assets/img/captcha/top3.svg';
|
||||||
|
import { fetchUploadFile } from '@/api/all';
|
||||||
|
import { jsPDF } from 'jspdf';
|
||||||
|
import html2canvas from 'html2canvas';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据评分获取对应的星星图标路径
|
* 根据评分获取对应的星星图标路径
|
||||||
@ -27,3 +31,49 @@ export enum AiResultStatus {
|
|||||||
FAILED = 2, // 失败
|
FAILED = 2, // 失败
|
||||||
SUCCESS = 3, // 成功
|
SUCCESS = 3, // 成功
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将指定 DOM 元素导出为 PDF Blob
|
||||||
|
* @param elementSelector - 要导出的 DOM 元素选择器
|
||||||
|
*/
|
||||||
|
export const generatePDF = async (elementSelector: string): Promise<Blob> => {
|
||||||
|
const sections = document.querySelectorAll(elementSelector);
|
||||||
|
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||||
|
let position = 0;
|
||||||
|
for (const section of sections) {
|
||||||
|
if ((section as HTMLElement).children.length === 0) continue;
|
||||||
|
const canvas = await html2canvas(section as HTMLElement, {
|
||||||
|
ignoreElements: (element) => element.classList.contains('ignore-export'),
|
||||||
|
scale: 2,
|
||||||
|
useCORS: true,
|
||||||
|
});
|
||||||
|
const imgData = canvas.toDataURL('image/png');
|
||||||
|
const imgProps = pdf.getImageProperties(imgData);
|
||||||
|
const pdfWidth = pdf.internal.pageSize.getWidth();
|
||||||
|
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
|
||||||
|
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, pdfHeight);
|
||||||
|
position += pdfHeight + 10;
|
||||||
|
}
|
||||||
|
return pdf.output('blob');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传 PDF 并返回文件 URL
|
||||||
|
* @param fileName - PDF 文件名
|
||||||
|
* @param elementSelector - 要导出的 DOM 元素选择器
|
||||||
|
*/
|
||||||
|
export const uploadPdf = async (fileName: string, elementSelector: string): Promise<string> => {
|
||||||
|
const response = await fetchUploadFile({ suffix: 'pdf' });
|
||||||
|
const preSignedUrl = response?.data?.upload_url;
|
||||||
|
const fileUrl = response?.data?.file_url;
|
||||||
|
if (!preSignedUrl) {
|
||||||
|
throw new Error('未能获取有效的预签名上传地址');
|
||||||
|
}
|
||||||
|
const pdfBlob = await generatePDF(elementSelector);
|
||||||
|
await axios.put(preSignedUrl, pdfBlob, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': pdfBlob.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return fileUrl;
|
||||||
|
};
|
||||||
|
|||||||
@ -54,8 +54,16 @@
|
|||||||
|
|
||||||
<!-- 投放建议-->
|
<!-- 投放建议-->
|
||||||
<PlacementSuggestions :optimization="aiResult.optimization"></PlacementSuggestions>
|
<PlacementSuggestions :optimization="aiResult.optimization"></PlacementSuggestions>
|
||||||
<!-- 投放行动指南-->
|
</div>
|
||||||
<ActionGuideDistribution :action_guide="aiResult.action_guide"></ActionGuideDistribution>
|
<div class="ignore-export">
|
||||||
|
<a-space class="down-btn">
|
||||||
|
<a-button type="outline" :loading="exportLoading" @click="downPage">
|
||||||
|
<template #icon>
|
||||||
|
<icon-download />
|
||||||
|
</template>
|
||||||
|
<template #default>下载</template>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -65,17 +73,18 @@ import { reactive, ref } from 'vue';
|
|||||||
|
|
||||||
import MonthData from './components/month-data/index.vue';
|
import MonthData from './components/month-data/index.vue';
|
||||||
import PlacementSuggestions from './components/placement-suggestions/index.vue';
|
import PlacementSuggestions from './components/placement-suggestions/index.vue';
|
||||||
import ActionGuideDistribution from './components/action-guide-distribution';
|
|
||||||
import { PLATFORM_LIST } from '@/views/property-marketing/put-account/common_constants.ts';
|
import { PLATFORM_LIST } from '@/views/property-marketing/put-account/common_constants.ts';
|
||||||
import { getPlacementGuideDetail } from '@/api/all/propertyMarketing';
|
import { getPlacementGuideDetail } from '@/api/all/propertyMarketing';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { uploadPdf } from '@/views/property-marketing/put-account/investment-guidelines/constants';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
const aiResult = reactive({
|
const aiResult = reactive({
|
||||||
optimization: [], // 投放建议优化
|
optimization: [], // 投放建议优化
|
||||||
action_guide: [], // 新投放建议生成
|
action_guide: [], // 新投放建议生成
|
||||||
overview: [], // 新投放建议生成
|
overview: [], // 新投放建议生成
|
||||||
});
|
});
|
||||||
|
const fileUrl = ref('');
|
||||||
const detailData = reactive({
|
const detailData = reactive({
|
||||||
created_at: '',
|
created_at: '',
|
||||||
account: '',
|
account: '',
|
||||||
@ -92,6 +101,27 @@ const getDetail = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportLoading = ref(false);
|
||||||
|
const downPage = async () => {
|
||||||
|
try {
|
||||||
|
let downFileUrl = fileUrl.value;
|
||||||
|
exportLoading.value = true;
|
||||||
|
if (downFileUrl === '') {
|
||||||
|
downFileUrl = await uploadPdf('投放指南.pdf', '.guidelines-data-wrap');
|
||||||
|
fileUrl.value = downFileUrl;
|
||||||
|
}
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = downFileUrl;
|
||||||
|
link.download = '投放指南.pdf';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
exportLoading.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
Message.error(error.message);
|
||||||
|
exportLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDetail();
|
getDetail();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
>
|
>
|
||||||
<a-tab-pane key="placement_guide" title="投放指南"></a-tab-pane>
|
<a-tab-pane key="placement_guide" title="投放指南"></a-tab-pane>
|
||||||
<a-tab-pane key="guide_history">
|
<a-tab-pane key="guide_history">
|
||||||
<template #title>历史投放指南</template>
|
<template #title>历史指南列表</template>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
@updateQuery="handleUpdateQuery"
|
@updateQuery="handleUpdateQuery"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="listData.total > 0" class="pagination-box flex justify-end">
|
<div v-if="listData.total > 0" class="pagination-box flex justify-end ignore-export">
|
||||||
<a-pagination
|
<a-pagination
|
||||||
:total="listData.total"
|
:total="listData.total"
|
||||||
size="mini"
|
size="mini"
|
||||||
@ -60,7 +60,7 @@
|
|||||||
</a-spin>
|
</a-spin>
|
||||||
<div v-if="tabData == 'placement_guide'" class="ignore-export">
|
<div v-if="tabData == 'placement_guide'" class="ignore-export">
|
||||||
<a-space class="down-btn">
|
<a-space class="down-btn">
|
||||||
<a-button type="outline" @click="downPage">
|
<a-button type="outline" :loading="exportLoading" @click="downPage">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-download />
|
<icon-download />
|
||||||
</template>
|
</template>
|
||||||
@ -84,7 +84,6 @@ import listSearchForm from './components/table-data/listSearchForm.vue';
|
|||||||
import GuideListHistory from './components/table-data/guideListHistory.vue';
|
import GuideListHistory from './components/table-data/guideListHistory.vue';
|
||||||
import MonthData from './components/month-data/index.vue';
|
import MonthData from './components/month-data/index.vue';
|
||||||
import PlacementSuggestions from './components/placement-suggestions/index.vue';
|
import PlacementSuggestions from './components/placement-suggestions/index.vue';
|
||||||
import ActionGuideDistribution from './components/action-guide-distribution';
|
|
||||||
import {
|
import {
|
||||||
getAiResult,
|
getAiResult,
|
||||||
getPlacementGuide,
|
getPlacementGuide,
|
||||||
@ -92,11 +91,8 @@ import {
|
|||||||
savePlacementGuide,
|
savePlacementGuide,
|
||||||
} from '@/api/all/propertyMarketing';
|
} from '@/api/all/propertyMarketing';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import html2canvas from 'html2canvas';
|
|
||||||
import { jsPDF } from 'jspdf';
|
|
||||||
import { AiResultStatus } from '@/views/property-marketing/put-account/investment-guidelines/constants';
|
import { AiResultStatus } from '@/views/property-marketing/put-account/investment-guidelines/constants';
|
||||||
import { fetchUploadFile } from '@/api/all';
|
import { uploadPdf } from '@/views/property-marketing/put-account/investment-guidelines/constants';
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const tabData = ref('placement_guide');
|
const tabData = ref('placement_guide');
|
||||||
|
|
||||||
@ -133,7 +129,7 @@ const handleUpdateQuery = (payload) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const exportLoading = ref(false);
|
||||||
const listData = reactive({
|
const listData = reactive({
|
||||||
total: 0,
|
total: 0,
|
||||||
list: [],
|
list: [],
|
||||||
@ -166,15 +162,27 @@ const aiResult = reactive({
|
|||||||
|
|
||||||
// 下载当前页面
|
// 下载当前页面
|
||||||
const downPage = async () => {
|
const downPage = async () => {
|
||||||
await nextTick();
|
try {
|
||||||
const fileUrl = await uploadPdf();
|
let fileUrl = saveForm.file_url;
|
||||||
|
exportLoading.value = true;
|
||||||
|
if (saveForm.file_url === '') {
|
||||||
|
fileUrl = await uploadPdf('投放指南.pdf', '.guidelines-data-wrap');
|
||||||
|
saveForm.file_url = fileUrl;
|
||||||
|
}
|
||||||
|
console.log(fileUrl, 'fileUrl');
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = fileUrl;
|
link.href = fileUrl;
|
||||||
link.download = '投放指南.pdf';
|
link.download = '投放指南.pdf';
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
|
exportLoading.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
Message.error(error.message);
|
||||||
|
exportLoading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveForm = reactive({
|
const saveForm = reactive({
|
||||||
account: [],
|
account: [],
|
||||||
plan: [],
|
plan: [],
|
||||||
@ -258,51 +266,6 @@ const handleSave = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 上传pdf文件
|
|
||||||
const uploadPdf = async () => {
|
|
||||||
if (saveForm.file_url !== '') {
|
|
||||||
return saveForm.file_url;
|
|
||||||
}
|
|
||||||
const response = await fetchUploadFile({ suffix: 'pdf' });
|
|
||||||
const preSignedUrl = response?.data?.upload_url;
|
|
||||||
const fileUrl = response?.data?.file_url;
|
|
||||||
if (!preSignedUrl) {
|
|
||||||
throw new Error('未能获取有效的预签名上传地址');
|
|
||||||
}
|
|
||||||
saveForm.file_url = fileUrl;
|
|
||||||
const pdfBlob = await generatePDF(); // 生成 PDF Blob
|
|
||||||
console.log('pdfBlob', pdfBlob);
|
|
||||||
await axios.put(preSignedUrl, pdfBlob, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': pdfBlob.type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return fileUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generatePDF = async (): Promise<Blob> => {
|
|
||||||
await nextTick();
|
|
||||||
const sections = document.querySelectorAll('.guidelines-data-wrap');
|
|
||||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
|
||||||
let position = 0;
|
|
||||||
for (const section of sections) {
|
|
||||||
if (section.children.length === 0) continue; // 跳过空内容区域
|
|
||||||
const canvas = await html2canvas(section as HTMLElement, {
|
|
||||||
ignoreElements: (element) => {
|
|
||||||
return element.classList.contains('ignore-export'); // 例如:忽略 class 为 ignore-export 的元素
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const imgData = canvas.toDataURL('image/png');
|
|
||||||
const imgProps = pdf.getImageProperties(imgData);
|
|
||||||
const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
||||||
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
|
|
||||||
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, pdfHeight);
|
|
||||||
position += pdfHeight + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pdf.output('blob');
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onSearch();
|
onSearch();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user