Przeglądaj źródła

feat: pdf export UI and frontend integration

liaoxg 3 miesięcy temu
rodzic
commit
c97446fd2a
2 zmienionych plików z 105 dodań i 2 usunięć
  1. 84 1
      client/pages/chart/index.vue
  2. 21 1
      client/utils/api.js

+ 84 - 1
client/pages/chart/index.vue

@@ -1,5 +1,13 @@
 <template>
   <view class="page-chart">
+    <!-- Top operation bar -->
+    <view class="top-bar">
+      <view class="spacer"></view>
+      <view v-if="userStore.isVip" class="export-btn" @click="exportPdf">
+        导出PDF
+      </view>
+    </view>
+
     <!-- Triangle chart -->
     <view class="chart-section">
       <TriangleChart
@@ -657,7 +665,7 @@ async function onShareTap() {
 async function performShare(actionIndex) {
   shareGenerating.value = true
   try {
-    // Consume quota before generating
+    // Consume quota if not VIP
     if (!userStore.isVipActive) {
       try {
         await profileApi.trackShare()
@@ -699,6 +707,62 @@ async function performShare(actionIndex) {
   }
 }
 
+// US-2.2: Export PDF (paid users only)
+async function exportPdf() {
+  if (!store.currentRecordId) {
+    uni.showToast({ title: '暂无命盘记录', icon: 'none' })
+    return
+  }
+  uni.showLoading({ title: '生成中...' })
+  try {
+    const filePath = await exportApi.pdf(store.currentRecordId)
+    uni.hideLoading()
+    // Open PDF document
+    uni.openDocument({
+      filePath,
+      success: () => {
+        uni.showToast({ title: 'PDF 已生成', icon: 'success' })
+      },
+      fail: () => {
+        uni.showToast({ title: '打开文件失败', icon: 'none' })
+      }
+    })
+  } catch (e) {
+    uni.hideLoading()
+    uni.showToast({ title: e.message || '导出失败', icon: 'none' })
+  }
+}
+    }
+
+    // Generate image via canvas
+    if (!shareCanvas.value) throw new Error('Canvas未就绪')
+    const filePath = await shareCanvas.value.generate()
+
+    if (actionIndex === 0) {
+      // Save to album
+      await uni.saveImageToPhotosAlbum({ filePath })
+      uni.showToast({ title: '已保存到相册', icon: 'success' })
+    } else {
+      // Share via WeChat
+      const scene = actionIndex === 1 ? 'WXSceneSession' : 'WXSceneTimeline'
+      const title = store.currentChart?.O
+        ? `我的命盘主性格数字是${store.currentChart.O},快来测测你的!`
+        : '数字能量命盘分析'
+      await uni.share({
+        provider: 'weixin',
+        scene,
+        type: 'image',
+        imageUrl: filePath,
+        title
+      })
+    }
+  } catch (e) {
+    uni.showToast({ title: e.message || '分享失败', icon: 'none' })
+  } finally {
+    shareGenerating.value = false
+  }
+}
+
 // === Annotation / Tag handlers (US-7.1 / US-7.2) ===
 
 async function loadAnnotations() {
@@ -760,6 +824,25 @@ async function onTagCreate(tagData) {
   background: linear-gradient(180deg, #0c0a1a 0%, #120d28 50%, #0f0c1e 100%);
 }
 
+/* Top operation bar */
+.top-bar {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  padding: 8px 12px;
+  background: rgba(0,0,0,0.15);
+}
+.spacer {
+  flex: 1;
+}
+.export-btn {
+  font-size: 13px;
+  color: #fbbf24;
+  padding: 6px 12px;
+  border: 1px solid rgba(251,191,36,0.3);
+  border-radius: 8px;
+}
+
 .chart-section {
   flex-shrink: 0;
   margin: 0 14px;

+ 21 - 1
client/utils/api.js

@@ -119,5 +119,25 @@ export const withdrawApi = {
 }
 
 export const exportApi = {
-  pdf: (recordId) => requestInstance.post('/export/pdf', { recordId })
+  pdf: (recordId) => {
+    const token = uni.getStorageSync('token')
+    return new Promise((resolve, reject) => {
+      uni.downloadFile({
+        url: API_BASE_URL + '/export/pdf',
+        method: 'POST',
+        data: { recordId },
+        header: token ? { Authorization: `Bearer ${token}` } : {},
+        success: (res) => {
+          if (res.statusCode === 200) {
+            resolve(res.tempFilePath)
+          } else {
+            reject(new Error('导出失败: ' + res.statusCode))
+          }
+        },
+        fail: (err) => {
+          reject(err)
+        }
+      })
+    })
+  }
 }