|
|
@@ -57,9 +57,20 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 我的推广码浮动按钮 -->
|
|
|
+ <!-- 我的分享海报浮动按钮(纯 CSS 二维码图标,避免 emoji 渲染成计算器) -->
|
|
|
<view class="share-qr-float" @click="showShareQr">
|
|
|
- <text class="share-qr-icon">📱</text>
|
|
|
+ <view class="qr-glyph">
|
|
|
+ <view class="qr-eye qr-eye-tl"></view>
|
|
|
+ <view class="qr-eye qr-eye-tr"></view>
|
|
|
+ <view class="qr-eye qr-eye-bl"></view>
|
|
|
+ <view class="qr-mid"></view>
|
|
|
+ <view class="qr-cluster">
|
|
|
+ <view class="qr-pip"></view>
|
|
|
+ <view class="qr-pip"></view>
|
|
|
+ <view class="qr-pip"></view>
|
|
|
+ <view class="qr-pip"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 家族成员速览 -->
|
|
|
@@ -265,43 +276,26 @@
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
- <view class="share-qr-modal" v-if="showShareQrModal" @click="closeShareQrModal">
|
|
|
- <view class="modal-mask" @click.stop />
|
|
|
- <view class="modal-qr" @click.stop>
|
|
|
- <view class="modal-qr-header">
|
|
|
- <text class="modal-qr-title">我的分享海报</text>
|
|
|
- <text class="modal-qr-close" @click="closeShareQrModal">✕</text>
|
|
|
- </view>
|
|
|
- <view class="modal-qr-content">
|
|
|
- <view v-if="qrLoading" class="qrcode-loading">
|
|
|
- <text class="loading-spinner">⏳</text>
|
|
|
- <text class="loading-text">生成中...</text>
|
|
|
- </view>
|
|
|
- <view v-else="!qrBase64" class="qrcode-empty">
|
|
|
- <text>暂无二维码</text>
|
|
|
- </view>
|
|
|
- <view v-else class="qrcode-image">
|
|
|
- <image class="qrcode-img" :src="qrBase64" mode="aspectFit"></image>
|
|
|
- </view>
|
|
|
- <view class="qrcode-info">
|
|
|
- <text class="qrcode-text">邀请码: {{ referralCode }}</text>
|
|
|
- <text class="qrcode-desc">扫码注册,好友成为您的推荐人</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <view class="modal-qr-actions">
|
|
|
- <view class="action-btn share-btn" @click="shareToWechat">
|
|
|
- <text class="share-icon">📤</text>
|
|
|
- <text class="share-text">分享给朋友</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
+ <!-- 我的分享海报:复用文章/活动/商品同一套 ContentSharePoster 组件(canvas 绘制 + 保存到相册) -->
|
|
|
+ <ContentSharePoster
|
|
|
+ :show="showSharePoster"
|
|
|
+ :title="shareHeadline"
|
|
|
+ :energyData="posterEnergyData"
|
|
|
+ :brandFallback="true"
|
|
|
+ :qrCodeBase64="posterQrCode || ''"
|
|
|
+ typeLabel="邀友加入"
|
|
|
+ :summary="posterSummary"
|
|
|
+ ctaText="长按识别二维码 · 加入浠艾福"
|
|
|
+ :inviteText="posterInviteText"
|
|
|
+ @close="closeSharePoster"
|
|
|
+ />
|
|
|
<FloatingAvatar />
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
|
|
|
+import ContentSharePoster from '../../components/ContentSharePoster.vue'
|
|
|
import {
|
|
|
getChildren, getFamilyMemberList,
|
|
|
getTodayTasksByCategory, getChallengeList, getMyMembership,
|
|
|
@@ -309,11 +303,11 @@ import {
|
|
|
cartCount, getOrderCounts, getReferralSummary,
|
|
|
getProfileStats, getMyReferrer, getTeamConsumption,
|
|
|
getTeacherInfo, getTeacherPendingTasks, getTeacherUnreadCount,
|
|
|
- getReferralCode, extractReferralCode, getShareQrCode
|
|
|
+ getReferralCode, extractReferralCode, getShareQrCode, getEnergySandbox
|
|
|
} from '../../utils/api.js'
|
|
|
|
|
|
export default {
|
|
|
- components: { FloatingAvatar },
|
|
|
+ components: { FloatingAvatar, ContentSharePoster },
|
|
|
data() {
|
|
|
return {
|
|
|
nickname: '',
|
|
|
@@ -348,10 +342,10 @@ export default {
|
|
|
profileProgress: 0,
|
|
|
myReferrer: null,
|
|
|
// 分享推广码
|
|
|
- showShareQrModal: false,
|
|
|
+ showSharePoster: false,
|
|
|
referralCode: '',
|
|
|
- qrBase64: '',
|
|
|
- qrLoading: false,
|
|
|
+ posterQrCode: '',
|
|
|
+ posterEnergyData: null,
|
|
|
referralCodeLoaded: false,
|
|
|
}
|
|
|
},
|
|
|
@@ -374,7 +368,23 @@ export default {
|
|
|
return this.memberLevel === 'FREE' || this.memberLevel === 'FAMILY'
|
|
|
},
|
|
|
upgradeBtnText() {
|
|
|
- return this.memberLevel === 'FAMILY' ? '升级' : '立即开通'
|
|
|
+ return this.hasUpgradeSpace ? '升级' : '立即开通'
|
|
|
+ },
|
|
|
+ // 分享海报标题:XXX邀请你关注家庭健康(昵称过长自动截断)
|
|
|
+ shareHeadline() {
|
|
|
+ var name = (this.nickname || '').trim()
|
|
|
+ if (!name) return '邀请你关注家庭健康'
|
|
|
+ if (name.length > 8) name = name.substring(0, 8) + '…'
|
|
|
+ return name + '邀请你关注家庭健康'
|
|
|
+ },
|
|
|
+ posterSummary() {
|
|
|
+ return '身体 · 心理 · 智慧 · 行动 · 财富,一家专注全家健康的成长平台'
|
|
|
+ },
|
|
|
+ posterInviteText() {
|
|
|
+ var name = (this.nickname || '').trim()
|
|
|
+ if (!name) return '扫码加入浠艾福,一起照顾好这个家'
|
|
|
+ if (name.length > 8) name = name.substring(0, 8) + '…'
|
|
|
+ return '「' + name + '」邀请你加入浠艾福'
|
|
|
}
|
|
|
},
|
|
|
onShow() {
|
|
|
@@ -547,72 +557,96 @@ export default {
|
|
|
console.error('获取推荐人失败', e)
|
|
|
}
|
|
|
},
|
|
|
- // ===== 分享推广码 =====
|
|
|
- showShareQr() {
|
|
|
- this.showShareQrModal = true
|
|
|
- if (!this.referralCodeLoaded) {
|
|
|
- this.loadReferralCode()
|
|
|
- } else if (this.referralCode && !this.qrBase64) {
|
|
|
- this.generateQrcode()
|
|
|
+ // ===== 分享海报(复用 ContentSharePoster,与文章/活动/商品同一套生成逻辑)=====
|
|
|
+ async showShareQr() {
|
|
|
+ uni.showLoading({ title: '生成海报中...' })
|
|
|
+ try {
|
|
|
+ if (!this.referralCodeLoaded) {
|
|
|
+ await this.loadReferralCode()
|
|
|
+ }
|
|
|
+ var scene = 'ref=' + (this.referralCode || '')
|
|
|
+ if (scene.length > 32) {
|
|
|
+ scene = scene.substring(0, 32)
|
|
|
+ }
|
|
|
+ // 首页路径,扫码后进入首页并绑定推荐人
|
|
|
+ // 注意:微信 getwxacodeunlimit 的 page 不能带前导斜杠,否则报 errcode 41030 invalid page rid
|
|
|
+ var res = await getShareQrCode('pages/index-home/index', scene)
|
|
|
+ if (res && res.code === 200 && res.data && res.data.qrCodeBase64) {
|
|
|
+ this.posterQrCode = res.data.qrCodeBase64
|
|
|
+ // 必须在 showSharePoster=true 之前取到数据:组件 watch 到 show 后 300ms 即开始绘制
|
|
|
+ await this.loadPosterEnergy()
|
|
|
+ this.showSharePoster = true
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: '生成失败,请重试', icon: 'none' })
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('生成分享海报失败', e)
|
|
|
+ uni.showToast({ title: '生成失败,请重试', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ uni.hideLoading()
|
|
|
}
|
|
|
},
|
|
|
- closeShareQrModal() {
|
|
|
- this.showShareQrModal = false
|
|
|
+ closeSharePoster() {
|
|
|
+ this.showSharePoster = false
|
|
|
+ },
|
|
|
+ // 五维能量数据卡数据(方案C):家庭聚合视角;未解锁全景/取数失败时置空,海报降级为品牌卡
|
|
|
+ loadPosterEnergy() {
|
|
|
+ var self = this
|
|
|
+ return getEnergySandbox('family').then(function(res) {
|
|
|
+ self.posterEnergyData = self.mapPosterEnergy(res)
|
|
|
+ }).catch(function(e) {
|
|
|
+ console.error('获取五维能量失败,海报降级为品牌卡', e)
|
|
|
+ self.posterEnergyData = null
|
|
|
+ })
|
|
|
+ },
|
|
|
+ mapPosterEnergy(res) {
|
|
|
+ if (!res || res.code !== 200 || !res.data) return null
|
|
|
+ // locked=true 表示用户未解锁全景,此时不展示任何数据,改为品牌卡
|
|
|
+ if (res.data.locked) return null
|
|
|
+ // 只认家庭聚合视角;仅当接口返回的是裸 DTO(无 personalData/familyData 包装)时才直接取 res.data
|
|
|
+ var d = res.data.familyData || null
|
|
|
+ if (!d) {
|
|
|
+ if (res.data.personalData) return null
|
|
|
+ d = res.data
|
|
|
+ }
|
|
|
+ if (!d || typeof d !== 'object') return null
|
|
|
+ var defs = [
|
|
|
+ { key: 'body', label: '身', color: '#FF8C42' },
|
|
|
+ { key: 'mind', label: '心', color: '#FF6B9D' },
|
|
|
+ { key: 'wisdom', label: '智', color: '#6366F1' },
|
|
|
+ { key: 'action', label: '行', color: '#10B981' },
|
|
|
+ { key: 'wealth', label: '富', color: '#F59E0B' }
|
|
|
+ ]
|
|
|
+ var dims = []
|
|
|
+ var sum = 0
|
|
|
+ for (var i = 0; i < defs.length; i++) {
|
|
|
+ var raw = d[defs[i].key + 'Score']
|
|
|
+ if (raw === null || raw === undefined || raw === '' || isNaN(raw)) continue
|
|
|
+ var n = Number(raw)
|
|
|
+ if (n < 0) n = 0
|
|
|
+ if (n > 100) n = 100
|
|
|
+ sum += n
|
|
|
+ dims.push({ key: defs[i].key, label: defs[i].label, score: n, color: defs[i].color })
|
|
|
+ }
|
|
|
+ if (dims.length === 0) return null
|
|
|
+ var overall = d.overallScore
|
|
|
+ if (overall === null || overall === undefined || overall === '' || isNaN(overall)) {
|
|
|
+ overall = Math.round(sum / dims.length)
|
|
|
+ }
|
|
|
+ return { label: '我们家的能量', overall: Number(overall), dims: dims }
|
|
|
},
|
|
|
async loadReferralCode() {
|
|
|
if (this.referralCodeLoaded) return
|
|
|
var self = this
|
|
|
try {
|
|
|
var res = await getReferralCode()
|
|
|
- var code = extractReferralCode(res)
|
|
|
- self.referralCode = code || ''
|
|
|
- self.referralCodeLoaded = true
|
|
|
- if (code) {
|
|
|
- self.generateQrcode()
|
|
|
- }
|
|
|
+ self.referralCode = extractReferralCode(res) || ''
|
|
|
} catch (e) {
|
|
|
console.error('获取推广码失败', e)
|
|
|
+ } finally {
|
|
|
self.referralCodeLoaded = true
|
|
|
}
|
|
|
},
|
|
|
- generateQrcode() {
|
|
|
- if (!this.referralCode) return
|
|
|
- this.qrLoading = true
|
|
|
- var self = this
|
|
|
- var scene = 'ref=' + this.referralCode
|
|
|
- // 首页路径,扫码后进入首页并绑定推荐人
|
|
|
- getShareQrCode('/pages/index-home/index', scene)
|
|
|
- .then(function(res) {
|
|
|
- if (res && res.code === 200 && res.data) {
|
|
|
- self.qrBase64 = res.data.qrCodeBase64
|
|
|
- }
|
|
|
- })
|
|
|
- .catch(function(e) {
|
|
|
- console.error('生成二维码失败', e)
|
|
|
- uni.showToast({ title: '生成失败,请重试', icon: 'none' })
|
|
|
- })
|
|
|
- .finally(function() {
|
|
|
- self.qrLoading = false
|
|
|
- })
|
|
|
- },
|
|
|
- shareToWechat() {
|
|
|
- if (!this.referralCode) return
|
|
|
- var path = '/pages/index-home/index?refCode=' + this.referralCode
|
|
|
- uni.share({
|
|
|
- provider: 'weixin',
|
|
|
- scene: { type: 0 },
|
|
|
- type: 5,
|
|
|
- imageUrl: this.qrBase64,
|
|
|
- path: path,
|
|
|
- title: '浠艾福 — 邀请你加入',
|
|
|
- success: function() {
|
|
|
- uni.showToast({ title: '分享成功', icon: 'success' })
|
|
|
- },
|
|
|
- fail: function() {
|
|
|
- uni.showToast({ title: '分享失败', icon: 'none' })
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
goToServiceCenter() {
|
|
|
uni.navigateTo({ url: '/pages/service-center/index' })
|
|
|
},
|
|
|
@@ -688,15 +722,14 @@ export default {
|
|
|
},
|
|
|
goToSettings: function() {
|
|
|
uni.showActionSheet({
|
|
|
- itemList: ['完善信息', '家庭成员', '邀请加入', '隐私政策', '用户协议', '我的画像'],
|
|
|
+ itemList: ['完善信息', '家庭成员', '隐私政策', '用户协议', '我的画像'],
|
|
|
success: function(res) {
|
|
|
var urls = {
|
|
|
0: '/pages/user-edit/user-edit',
|
|
|
1: '/pages/profile-extra/family-members',
|
|
|
- 2: '/pages/guide/invite/share',
|
|
|
- 3: '/pages/policy/privacy',
|
|
|
- 4: '/pages/policy/agreement',
|
|
|
- 5: '/pages/profile/portrait-edit'
|
|
|
+ 2: '/pages/policy/privacy',
|
|
|
+ 3: '/pages/policy/agreement',
|
|
|
+ 4: '/pages/profile/portrait-edit'
|
|
|
}
|
|
|
if (urls[res.tapIndex]) {
|
|
|
uni.navigateTo({ url: urls[res.tapIndex] })
|
|
|
@@ -1282,7 +1315,7 @@ export default {
|
|
|
/* 分享推广码浮动按钮 */
|
|
|
.share-qr-float {
|
|
|
position: fixed;
|
|
|
- top: 40rpx;
|
|
|
+ top: 180rpx;
|
|
|
right: 30rpx;
|
|
|
width: 80rpx;
|
|
|
height: 80rpx;
|
|
|
@@ -1294,147 +1327,45 @@ export default {
|
|
|
justify-content: center;
|
|
|
z-index: 99;
|
|
|
}
|
|
|
-.share-qr-icon {
|
|
|
- font-size: 40rpx;
|
|
|
- line-height: 1;
|
|
|
+/* 二维码图标(纯 CSS 绘制,不依赖 emoji 字体) */
|
|
|
+.qr-glyph {
|
|
|
+ position: relative;
|
|
|
+ width: 46rpx;
|
|
|
+ height: 46rpx;
|
|
|
}
|
|
|
-
|
|
|
-/* 分享海报弹窗 */
|
|
|
-.share-qr-modal {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- z-index: 999;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
+.qr-eye {
|
|
|
+ position: absolute;
|
|
|
+ width: 16rpx;
|
|
|
+ height: 16rpx;
|
|
|
+ border: 4rpx solid #1E293B;
|
|
|
+ border-radius: 3rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
-.modal-mask {
|
|
|
+.qr-eye-tl { left: 0; top: 0; }
|
|
|
+.qr-eye-tr { right: 0; top: 0; }
|
|
|
+.qr-eye-bl { left: 0; bottom: 0; }
|
|
|
+.qr-mid {
|
|
|
+ position: absolute;
|
|
|
+ left: 20rpx;
|
|
|
+ top: 20rpx;
|
|
|
+ width: 7rpx;
|
|
|
+ height: 7rpx;
|
|
|
+ background: #1E293B;
|
|
|
+ border-radius: 1rpx;
|
|
|
+}
|
|
|
+.qr-cluster {
|
|
|
position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
right: 0;
|
|
|
bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
-}
|
|
|
-.modal-qr {
|
|
|
- position: relative;
|
|
|
- width: 70%;
|
|
|
- max-width: 600rpx;
|
|
|
- background: #fff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 30rpx;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
-}
|
|
|
-.modal-qr-header {
|
|
|
- width: 100%;
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-.modal-qr-title {
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-.modal-qr-close {
|
|
|
- font-size: 32rpx;
|
|
|
- color: #999;
|
|
|
- padding: 10rpx;
|
|
|
-}
|
|
|
-.modal-qr-content {
|
|
|
+ width: 18rpx;
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
-.qrcode-loading,
|
|
|
-.qrcode-empty {
|
|
|
- width: 340rpx;
|
|
|
- height: 340rpx;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- background: #FAFAFA;
|
|
|
- border-radius: 16rpx;
|
|
|
- border: 2rpx dashed #E5E7EB;
|
|
|
-}
|
|
|
-.loading-spinner {
|
|
|
- font-size: 48rpx;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-.loading-text {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #94A3B8;
|
|
|
-}
|
|
|
-.qrcode-empty text {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #94A3B8;
|
|
|
-}
|
|
|
-.qrcode-image {
|
|
|
- width: 340rpx;
|
|
|
- height: 340rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- background: #fff;
|
|
|
- border-radius: 16rpx;
|
|
|
- border: 1rpx solid #E5E7EB;
|
|
|
- overflow: hidden;
|
|
|
-}
|
|
|
-.qrcode-img {
|
|
|
- width: 330rpx;
|
|
|
- height: 330rpx;
|
|
|
-}
|
|
|
-.qrcode-info {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- margin-top: 20rpx;
|
|
|
- gap: 8rpx;
|
|
|
-}
|
|
|
-.qrcode-text {
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #F97316;
|
|
|
-}
|
|
|
-.qrcode-desc {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #94A3B8;
|
|
|
-}
|
|
|
-.modal-qr-actions {
|
|
|
- width: 100%;
|
|
|
- display: flex;
|
|
|
- gap: 20rpx;
|
|
|
- margin-top: 24rpx;
|
|
|
-}
|
|
|
-.action-btn {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- gap: 8rpx;
|
|
|
- padding: 24rpx;
|
|
|
- border-radius: 16rpx;
|
|
|
- font-size: 24rpx;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-.share-btn {
|
|
|
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
- color: #fff;
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
-.share-icon {
|
|
|
- font-size: 40rpx;
|
|
|
+ flex-wrap: wrap;
|
|
|
}
|
|
|
-.share-text {
|
|
|
- font-size: 24rpx;
|
|
|
- font-weight: bold;
|
|
|
+.qr-pip {
|
|
|
+ width: 7rpx;
|
|
|
+ height: 7rpx;
|
|
|
+ margin: 1rpx;
|
|
|
+ background: #1E293B;
|
|
|
+ border-radius: 1rpx;
|
|
|
}
|
|
|
</style>
|