From 7948e4f78b8a9521c6c86dc7b9b6209a87c662a9 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:38:58 +0800 Subject: [PATCH] feat: Export conversation page to PDF (#3941) --- ui/package.json | 2 + ui/src/components/pdf-export/index.vue | 146 +++++++++++++++++++++++++ ui/src/locales/lang/en-US/ai-chat.ts | 3 + ui/src/locales/lang/zh-CN/ai-chat.ts | 3 + ui/src/locales/lang/zh-Hant/ai-chat.ts | 3 + ui/src/utils/htmlToPdf.ts | 56 ---------- ui/src/views/chat/pc/index.vue | 13 ++- 7 files changed, 165 insertions(+), 61 deletions(-) create mode 100644 ui/src/components/pdf-export/index.vue delete mode 100644 ui/src/utils/htmlToPdf.ts diff --git a/ui/package.json b/ui/package.json index c5d6b4668..f391ddaeb 100644 --- a/ui/package.json +++ b/ui/package.json @@ -32,6 +32,7 @@ "element-plus": "^2.10.2", "file-saver": "^2.0.5", "highlight.js": "^11.11.1", + "html-to-image": "^1.11.13", "html2canvas": "^1.4.1", "jspdf": "^3.0.1", "katex": "^0.16.10", @@ -45,6 +46,7 @@ "recorder-core": "^1.3.25011100", "screenfull": "^6.0.2", "sortablejs": "^1.15.6", + "svg2pdf.js": "^2.5.0", "use-element-plus-theme": "^0.0.5", "vite-plugin-html": "^3.2.2", "vue": "^3.5.13", diff --git a/ui/src/components/pdf-export/index.vue b/ui/src/components/pdf-export/index.vue new file mode 100644 index 000000000..6ea6bc5c1 --- /dev/null +++ b/ui/src/components/pdf-export/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/ui/src/locales/lang/en-US/ai-chat.ts b/ui/src/locales/lang/en-US/ai-chat.ts index 4100a079e..42c4e1045 100644 --- a/ui/src/locales/lang/en-US/ai-chat.ts +++ b/ui/src/locales/lang/en-US/ai-chat.ts @@ -9,6 +9,9 @@ export default { only20history: 'Showing only the last 20 chats', question_count: 'Questions', exportRecords: 'Export Chat History', + exportPDF: 'Export PDF', + exportImg: 'Exporting images', + preview: 'Preview', chatId: 'Chat ID', userInput: 'User Input', quote: 'Quote', diff --git a/ui/src/locales/lang/zh-CN/ai-chat.ts b/ui/src/locales/lang/zh-CN/ai-chat.ts index d725a8c46..88976f169 100644 --- a/ui/src/locales/lang/zh-CN/ai-chat.ts +++ b/ui/src/locales/lang/zh-CN/ai-chat.ts @@ -9,6 +9,9 @@ export default { only20history: '仅显示最近 20 条对话', question_count: '条提问', exportRecords: '导出聊天记录', + exportPDF: '导出PDF', + exportImg: '导出图片', + preview: '预览', chatId: '对话 ID', chatUserId: '对话用户 ID', chatUserType: '对话用户类型', diff --git a/ui/src/locales/lang/zh-Hant/ai-chat.ts b/ui/src/locales/lang/zh-Hant/ai-chat.ts index 3b5c430e5..530714e02 100644 --- a/ui/src/locales/lang/zh-Hant/ai-chat.ts +++ b/ui/src/locales/lang/zh-Hant/ai-chat.ts @@ -9,6 +9,9 @@ export default { only20history: '僅顯示最近 20 條對話', question_count: '條提問', exportRecords: '導出聊天記錄', + exportPDF: '匯出PDF', + exportImg: '匯出圖片', + preview: '預覽', chatId: '對話 ID', userInput: '用戶輸入', quote: '引用', diff --git a/ui/src/utils/htmlToPdf.ts b/ui/src/utils/htmlToPdf.ts deleted file mode 100644 index 3a8b21d89..000000000 --- a/ui/src/utils/htmlToPdf.ts +++ /dev/null @@ -1,56 +0,0 @@ -import html2Canvas from 'html2canvas' -import jsPDF from 'jspdf' - -export const exportToPDF = async (elementId: string, filename = 'document.pdf') => { - const element = document.getElementById(elementId) - if (!element) return - await html2Canvas(element, { - useCORS: true, - allowTaint: true, - logging: false, - scale: 2, - backgroundColor: '#fff', - }).then((canvas: any) => { - const pdf = new jsPDF('p', 'mm', 'a4') - const pageWidth = 190 // 保留边距后的有效宽度 - const pageHeight = 277 // 保留边距后的有效高度 //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277 - const imgHeight = (pageHeight * canvas.width) / pageWidth - - let renderedHeight = 0 - while (renderedHeight < canvas.height) { - const pageCanvas = document.createElement('canvas') - pageCanvas.width = canvas.width - pageCanvas.height = Math.min(imgHeight, canvas.height - renderedHeight) - - pageCanvas - .getContext('2d')! - .putImageData( - canvas - .getContext('2d')! - .getImageData( - 0, - renderedHeight, - canvas.width, - Math.min(imgHeight, canvas.height - renderedHeight), - ), - 0, - 0, - ) - - pdf.addImage( - pageCanvas.toDataURL('image/jpeg', 1.0), - 'JPEG', - 10, - 10, // 左边距和上边距 - pageWidth, - Math.min(pageHeight, (pageWidth * pageCanvas.height) / pageCanvas.width), - ) - - renderedHeight += imgHeight - if (renderedHeight < canvas.height) { - pdf.addPage() - } - } - pdf.save(filename) - }) -} diff --git a/ui/src/views/chat/pc/index.vue b/ui/src/views/chat/pc/index.vue index 0e83a82a7..c56c57645 100644 --- a/ui/src/views/chat/pc/index.vue +++ b/ui/src/views/chat/pc/index.vue @@ -139,7 +139,7 @@ {{ $t('common.export') }} HTML - {{ $t('common.export') }} PDF @@ -169,7 +169,7 @@

{{ rightPanelTitle }}

-   + @@ -238,12 +239,14 @@ import ParagraphDocumentContent from '@/components/ai-chat/component/knowledge-s import HistoryPanel from '@/views/chat/component/HistoryPanel.vue' import { cloneDeep } from 'lodash' import { getFileUrl } from '@/utils/common' -import { exportToPDF } from '@/utils/htmlToPdf' +import PdfExport from '@/components/pdf-export/index.vue' useResize() - +const pdfExportRef = ref>() const { common, chatUser } = useStore() const router = useRouter() - +const openPDFExport = () => { + pdfExportRef.value?.open(document.getElementById('chatListId')) +} const isCollapse = ref(false) const isPcCollapse = ref(false) watch(