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(