@@ -305,7 +683,7 @@ function DualReportWindow() {
if (emojiUrl) {
return (
-

{
+

{
(e.target as HTMLImageElement).style.display = 'none';
(e.target as HTMLImageElement).nextElementSibling?.removeAttribute('style');
}} />
@@ -356,7 +734,7 @@ function DualReportWindow() {
if (avatarUrl) {
return (
-

+
)
}
@@ -419,9 +797,99 @@ function DualReportWindow() {
+
+
+
+
+
+
+
+ {isExporting && (
+
+
+
+
正在导出
+
{exportProgress}
+
+
+ )}
+
+ {showExportModal && (
+
setShowExportModal(false)}>
+
e.stopPropagation()}>
+
+
{exportMode === 'long' ? '自定义导出长图' : '选择要导出的板块'}
+
+
+
+ {getAvailableSections().map((section) => (
+
toggleSection(section.id)}
+ >
+
+ {selectedSections.has(section.id) && }
+
+
{section.name}
+
+ ))}
+
+
+
+
+
+
+
+ )}
+
-
-
+
+
WEFLOW · DUAL REPORT
{yearTitle}
双人聊天报告
@@ -433,7 +901,7 @@ function DualReportWindow() {
每一次对话都值得被珍藏
-
+
首次聊天
故事的开始
{firstChat ? (
@@ -457,7 +925,7 @@ function DualReportWindow() {
{yearFirstChat && (!firstChat || yearFirstChat.createTime !== firstChat.createTime) ? (
-
+
第一段对话
{reportData.year === 0 ? '你们的第一段对话' : `${reportData.year}年的第一段对话`}
@@ -473,7 +941,7 @@ function DualReportWindow() {
) : null}
{reportData.heatmap && (
-
+
聊天习惯
作息规律
{mostActive && (
@@ -486,14 +954,14 @@ function DualReportWindow() {
)}
{reportData.initiative && (
-
+
主动性
情感的天平
- {reportData.selfAvatarUrl ?

: '我'}
+ {reportData.selfAvatarUrl ?

: '我'}
{reportData.initiative.initiated}次
{initiatedPercent.toFixed(1)}%
@@ -507,7 +975,7 @@ function DualReportWindow() {
- {reportData.friendAvatarUrl ?

: reportData.friendName.substring(0, 1)}
+ {reportData.friendAvatarUrl ?

: reportData.friendName.substring(0, 1)}
{reportData.initiative.received}次
{receivedPercent.toFixed(1)}%
@@ -521,7 +989,7 @@ function DualReportWindow() {
)}
{reportData.response && (
-
+
回应速度
你说,我在
@@ -558,7 +1026,7 @@ function DualReportWindow() {
)}
{reportData.streak && (
-
+
聊天火花
最长连续聊天
@@ -596,7 +1064,7 @@ function DualReportWindow() {
)}
-
+
常用语
{yearTitle}常用语
@@ -640,7 +1108,7 @@ function DualReportWindow() {
-
+
年度统计
{yearTitle}数据概览
@@ -664,7 +1132,7 @@ function DualReportWindow() {
我常用的表情
{myEmojiUrl ? (
-

{
+

{
(e.target as HTMLImageElement).nextElementSibling?.removeAttribute('style');
(e.target as HTMLImageElement).style.display = 'none';
}} />
@@ -677,7 +1145,7 @@ function DualReportWindow() {
{reportData.friendName}常用的表情
{friendEmojiUrl ? (
-

{
+

{
(e.target as HTMLImageElement).nextElementSibling?.removeAttribute('style');
(e.target as HTMLImageElement).style.display = 'none';
}} />
@@ -690,7 +1158,7 @@ function DualReportWindow() {
-
+
尾声
谢谢你一直在
愿我们继续把故事写下去
diff --git a/src/utils/reportExport.ts b/src/utils/reportExport.ts
new file mode 100644
index 0000000..224b99e
--- /dev/null
+++ b/src/utils/reportExport.ts
@@ -0,0 +1,36 @@
+const PATTERN_LIGHT_SVG = ``
+
+const PATTERN_DARK_SVG = ``
+
+export const drawPatternBackground = async (
+ ctx: CanvasRenderingContext2D,
+ width: number,
+ height: number,
+ bgColor: string,
+ isDark: boolean
+) => {
+ ctx.fillStyle = bgColor
+ ctx.fillRect(0, 0, width, height)
+
+ const svgString = isDark ? PATTERN_DARK_SVG : PATTERN_LIGHT_SVG
+ const blob = new Blob([svgString], { type: 'image/svg+xml' })
+ const url = URL.createObjectURL(blob)
+
+ return new Promise((resolve) => {
+ const img = new window.Image()
+ img.onload = () => {
+ const pattern = ctx.createPattern(img, 'repeat')
+ if (pattern) {
+ ctx.fillStyle = pattern
+ ctx.fillRect(0, 0, width, height)
+ }
+ URL.revokeObjectURL(url)
+ resolve()
+ }
+ img.onerror = () => {
+ URL.revokeObjectURL(url)
+ resolve()
+ }
+ img.src = url
+ })
+}