-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: chat export pdf #3931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: chat export pdf #3931
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| 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) | ||
| }) | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code you provided is an implementation of a function to convert HTML content into a PDF using
Here's a revised version of the function incorporating these considerations: import html2Canvas from 'html2canvas';
import jsPDF from 'jspdf';
interface ExportOptions {
id?: string;
filename?: string;
}
export const exportToPDF = async ({
id = 'content-container',
filename = 'document.pdf',
}: ExportOptions = {}) => {
try {
const element = document.getElementById(id);
if (!element) throw new Error(`Element with ID "${id}" not found`);
// Capture the entire window or specific div
await html2Canvas(document.body).then((canvas: HTMLCanvasElement) => {
const width = canvas.width;
const height = canvas.height;
const pdf = new jsPDF();
const pageSize = [width, height]; // A4 size
if (pageNumber > totalPages) break; // Handle multi-page PDFs if necessary
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', marginLeft, marginTop, pageSize[0], pageSize[1]);
pageNumber++;
});
pdf.save(filename);
} catch (error) {
console.error('Failed to generate PDF:', error.message);
}
};In this updated version:
For more advanced features like supporting multi-page PDFs or optimizing image resampling, depending on your needs, you might need additional logic outside this function. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -139,6 +139,9 @@ | |
| <el-dropdown-item @click="exportHTML" | ||
| >{{ $t('common.export') }} HTML</el-dropdown-item | ||
| > | ||
| <el-dropdown-item @click="exportToPDF('chatListId', currentChatName + '.pdf')" | ||
| >{{ $t('common.export') }} PDF</el-dropdown-item | ||
| > | ||
| </el-dropdown-menu> | ||
| </template> | ||
| </el-dropdown> | ||
|
|
@@ -222,7 +225,6 @@ import { ref, onMounted, nextTick, computed, watch } from 'vue' | |
| import { marked } from 'marked' | ||
| import { saveAs } from 'file-saver' | ||
| import chatAPI from '@/api/chat/chat' | ||
|
|
||
| import useStore from '@/stores' | ||
| import useResize from '@/layout/hooks/useResize' | ||
| import { hexToRgba } from '@/utils/theme' | ||
|
|
@@ -236,6 +238,7 @@ 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' | ||
| useResize() | ||
|
|
||
| const { common, chatUser } = useStore() | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code snippet provided appears to be an update to the front-end part of a Vue.js application that handles chat functionalities. Here is a review of potential issues and areas for improvement: Regularity and Potential Issues:
Optimization Suggestions:
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no obvious irregularities in the provided code snippet, but I have some suggestions for improvement:
Attribute Naming: The
id="chatListId"attribute adds an unnecessary ID to the component that may not need it. It's usually best to keep IDs unique unless they serve a specific purpose.Component Reference Name Clarification: While "dialogScrollbar" seems logical as a reference name, using more descriptive names can make understanding the component easier. For instance, you could name it something like "_contentContainer".
Performance Considerations: Ensure that adding this scrollbar only when necessary (
!(isUserInput || isAPIInput) || !firstUserInput || type === 'log') doesn't cause performance bottlenecks.Type Checking: Since
typemight be of a certain type (e.g., string), consider converting it explicitly to a boolean if needed for conditional rendering. This can help avoid errors related to implicit typing.Here's your updated code with these suggestions included:
If you're working within Vue, ensure that this approach adheres to the correct lifecycle hooks and component usage practices.