Skip to content

feat: chat export - #7914

Merged
wangdan-fit2cloud merged 1 commit into
v3from
pr@v3@feat_chat_export
Nov 13, 2026
Merged

wangdan-fit2cloud merged 1 commit into
v3from
pr@v3@feat_chat_export

Conversation

@shaohuzhang1

Copy link
Copy Markdown
Contributor

feat: chat export

@wangdan-fit2cloud
wangdan-fit2cloud merged commit 4a56690 into v3 Nov 13, 2026
3 of 4 checks passed
@wangdan-fit2cloud
wangdan-fit2cloud deleted the pr@v3@feat_chat_export branch November 13, 2026 07:55
<el-button type="primary" :loading="loading" @click="save('png')">导出图片</el-button>
</template>
</MkDialog>
</template>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the code

Overall the logic is sound. Below are irregularities, potential issues, and optimization suggestions.

1. Potential issues (correctness / robustness)

a) toDataURL on a tainted canvas

  • In open(), canvas.toDataURL('image/png') can throw a SecurityError if the canvas contains cross-origin images/fonts that weren't drawn with CORS. This isn't wrapped in try/catch, so it would surface as an uncaught error. Consider wrapping and showing MsgError.

b) PDF pagination crops content at page boundaries

  • Slicing by pagePixels means a row of pixels can be cut mid-line across two pages. If content ends up taller than pagePixels, it will overlap/duplicate visually. A common approach: scale the whole image to page width and let jsPDF handle multi-page via autoPaging, or add small margins. At minimum document this behavior.
  • Also (page.height * width) / page.width uses width (page width mm), which is correct, but for the last partial page the image height is smaller — that's fine, but there's no vertical margin.

c) JPEG quality loss

  • Using 'image/jpeg', 0.95 for PDF introduces compression artifacts and makes text look blurry. For screenshots/logs, consider PNG ('image/png') for sharper text, at the cost of larger file size. Or make it configurable.

d) Re-entrancy of loading guard

  • if (!canvas || loading.value) return guards concurrent saves, but since both buttons share loading, clicking one disables both — expected here, but note el-button with the same loading prop means during one export the other button also shows spinning. Acceptable, just be aware.

e) fileName not sanitized

  • If name comes from user/conversation content, it may contain / \ : * ? " < > | etc., breaking downloads on some OSes. Sanitize before use.

f) Large canvas memory

  • Creating one <canvas> per page in a loop is fine, but for very long conversations this could allocate many large canvases. They become GC-eligible each iteration (no reference kept), so it's OK. However toDataURL on each page is synchronous and blocks the UI. Consider toBlob + async conversion, or chunk with await new Promise(r => setTimeout(r)) between pages to keep UI responsive.

g) capturedCanvas retained after close

  • clear() on @closed and onBeforeUnmount nulls it — good. But previewUrl (a full base64 data URL) stays in memory until close; that's expected. Ensure MkDialog actually emits closed; if it only supports v-model update, clear might never run. Verify the component contract.

h) alt / accessibility

  • The preview <img> has alt — good. The dialog itself could use a more descriptive label, but that depends on MkDialog.

2. Minor / style

  • previewUrl = canvas.toDataURL() duplicates work: save('png') calls canvas.toBlob again. Not a bug, just redundant.
  • Magic numbers 0.95, 'image/jpeg' could be named constants.
  • Error message '图片生成失败' and '无法创建画布' aren't surfaced to the user (the catch swallows them). Log the actual error for debugging:
    } catch (err) {
      console.error(err)
      MsgError('导出失败,请稍后重试')
    }
  • The for loop could compute totalPages = Math.ceil(canvas.height / pagePixels) first for clarity and to reuse in progress display.

3. Optimization suggestions

  • Use toBlob instead of toDataURL for the final image in open() if you don't strictly need a data URL — create an object URL (URL.createObjectURL(blob)) and revoke it in clear(). This avoids a large base64 string lingering in memory and is friendlier to memory for big canvases:
    function clear() {
      if (previewUrl.value.startsWith('blob:')) URL.revokeObjectURL(previewUrl.value)
      previewUrl.value = ''
      capturedCanvas = null
    }
  • Yield to the event loop between pages when generating the PDF to keep the UI responsive on long conversations.
  • Consider passing jsPDF image format based on content: PNG for text-heavy logs (sharpness), JPEG for photo-heavy.
  • Add a page margin so sliced pages don't run edge-to-edge and look cleaner.

Summary

No blocking bugs, but the main risks are: uncaught toDataURL security error, mid-line cropping in PDF pagination, unsanitized filename, swallowed error details, and synchronous blocking on large exports. Address those and the code is solid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants