|
|
@@ -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;
|