|
|
@@ -116,6 +116,9 @@
|
|
|
<view v-if="loading" class="loading-wrap">
|
|
|
<text class="loading-text">加载中...</text>
|
|
|
</view>
|
|
|
+
|
|
|
+ <!-- ===== 分享卡片画布(离屏,仅用于生成分享缩略图) ===== -->
|
|
|
+ <canvas canvas-id="familyShareCard" id="familyShareCard" class="share-card-canvas"></canvas>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
@@ -152,7 +155,8 @@ export default {
|
|
|
userAvatar: '',
|
|
|
|
|
|
inviteToken: '',
|
|
|
- joinRequests: []
|
|
|
+ joinRequests: [],
|
|
|
+ shareCardImage: ''
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -187,52 +191,78 @@ export default {
|
|
|
this.loadJoinRequests()
|
|
|
},
|
|
|
// 页面级分享:转发邀请卡片,接收人点击直接进入加入家庭页(信息补充、家庭申请)
|
|
|
- onShareAppMessage() {
|
|
|
+ onShareAppMessage: function() {
|
|
|
var self = this
|
|
|
var title = '邀请你加入「' + (this.familyName || '我的家庭') + '」'
|
|
|
- var buildResult = function(token) {
|
|
|
+ var buildResult = function(token, imageUrl) {
|
|
|
return {
|
|
|
title: title,
|
|
|
path: '/pages/invite/join?token=' + (token || ''),
|
|
|
- success: function() {},
|
|
|
- fail: function() {}
|
|
|
+ imageUrl: imageUrl || self.shareCardImage || ''
|
|
|
}
|
|
|
}
|
|
|
- if (this.inviteToken) {
|
|
|
- return buildResult(this.inviteToken)
|
|
|
+ if (this.inviteToken && this.shareCardImage) {
|
|
|
+ return buildResult(this.inviteToken, this.shareCardImage)
|
|
|
}
|
|
|
- // 令牌未就绪时异步获取(基础库 >=2.6.0 支持 promise,超时约 3 秒)
|
|
|
+ // 令牌/卡片未就绪时异步获取
|
|
|
return {
|
|
|
title: title,
|
|
|
path: '/pages/index/index',
|
|
|
promise: new Promise(function(resolve) {
|
|
|
+ function done(token, image) {
|
|
|
+ resolve(buildResult(token || '', image || ''))
|
|
|
+ }
|
|
|
+ // 并行:令牌 + 分享卡片
|
|
|
+ var tokenDone = false
|
|
|
+ var cardDone = false
|
|
|
+ var tryResolve = function() {
|
|
|
+ if (!tokenDone || !cardDone) return
|
|
|
+ if (!self.inviteToken) { done('', self.shareCardImage) }
|
|
|
+ else { done(self.inviteToken, self.shareCardImage) }
|
|
|
+ }
|
|
|
self.prepareInviteToken(function(token) {
|
|
|
- resolve(token ? buildResult(token) : { path: '/pages/index/index' })
|
|
|
+ tokenDone = true
|
|
|
+ tryResolve()
|
|
|
+ })
|
|
|
+ self.drawShareCard(function(image) {
|
|
|
+ cardDone = true
|
|
|
+ self.shareCardImage = image
|
|
|
+ tryResolve()
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
- onShareTimeline() {
|
|
|
+ onShareTimeline: function() {
|
|
|
var self = this
|
|
|
var title = '邀请你加入「' + (this.familyName || '我的家庭') + '」'
|
|
|
- var buildResult = function(token) {
|
|
|
+ var buildResult = function(token, imageUrl) {
|
|
|
return {
|
|
|
title: title,
|
|
|
query: 'token=' + (token || ''),
|
|
|
- imageUrl: ''
|
|
|
+ imageUrl: imageUrl || self.shareCardImage || ''
|
|
|
}
|
|
|
}
|
|
|
- if (this.inviteToken) {
|
|
|
- return buildResult(this.inviteToken)
|
|
|
+ if (this.inviteToken && this.shareCardImage) {
|
|
|
+ return buildResult(this.inviteToken, this.shareCardImage)
|
|
|
}
|
|
|
- // 令牌未就绪时异步获取
|
|
|
return {
|
|
|
title: title,
|
|
|
query: '',
|
|
|
imageUrl: '',
|
|
|
promise: new Promise(function(resolve) {
|
|
|
- self.prepareInviteToken(function(token) {
|
|
|
- resolve(token ? buildResult(token) : { query: '', imageUrl: '' })
|
|
|
+ var tokenDone = false
|
|
|
+ var cardDone = false
|
|
|
+ var tryResolve = function() {
|
|
|
+ if (!tokenDone || !cardDone) return
|
|
|
+ var token = self.inviteToken || ''
|
|
|
+ var image = self.shareCardImage || ''
|
|
|
+ resolve(buildResult(token, image))
|
|
|
+ }
|
|
|
+ self.prepareInviteToken(function() { tokenDone = true; tryResolve() })
|
|
|
+ self.drawShareCard(function(image) {
|
|
|
+ cardDone = true
|
|
|
+ self.shareCardImage = image
|
|
|
+ tryResolve()
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
@@ -360,6 +390,152 @@ export default {
|
|
|
done('')
|
|
|
})
|
|
|
},
|
|
|
+ // 生成家庭邀请分享卡片图片(5:4 比例,500x400px),返回 tempFilePath
|
|
|
+ drawShareCard: function(callback) {
|
|
|
+ var self = this
|
|
|
+ var W = 500
|
|
|
+ var H = 400
|
|
|
+ var canvasId = 'familyShareCard'
|
|
|
+ var members = self.members || []
|
|
|
+ var roundRect = function(ctx, x, y, w, h, r) {
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(x + r, y)
|
|
|
+ ctx.arcTo(x + w, y, x + w, y + r, r)
|
|
|
+ ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
|
|
|
+ ctx.arcTo(x, y + h, x, y + h - r, r)
|
|
|
+ ctx.arcTo(x, y, x + r, y, r)
|
|
|
+ ctx.closePath()
|
|
|
+ }
|
|
|
+ var ctx = uni.createCanvasContext(canvasId, self)
|
|
|
+ // 暖橙渐变背景
|
|
|
+ var bgGrad = ctx.createLinearGradient(0, 0, W * 0.6, H)
|
|
|
+ bgGrad.addColorStop(0, '#FF8C42')
|
|
|
+ bgGrad.addColorStop(1, '#FFB366')
|
|
|
+ ctx.setFillStyle(bgGrad)
|
|
|
+ ctx.fillRect(0, 0, W, H)
|
|
|
+ // 白色内容卡片
|
|
|
+ var cx = 30, cy = 70, cw = W - 60, ch = 250, cr = 20
|
|
|
+ ctx.setFillStyle('rgba(0,0,0,0.04)')
|
|
|
+ roundRect(ctx, cx + 2, cy + 4, cw, ch, cr)
|
|
|
+ ctx.fill()
|
|
|
+ ctx.setFillStyle('#FFFCF5')
|
|
|
+ roundRect(ctx, cx, cy, cw, ch, cr)
|
|
|
+ ctx.fill()
|
|
|
+ // 四角装饰框
|
|
|
+ var bl = 14, br = 10
|
|
|
+ ctx.setStrokeStyle('rgba(249,115,22,0.5)')
|
|
|
+ ctx.setLineWidth(1.5)
|
|
|
+ ctx.beginPath(); ctx.moveTo(cx+br, cy+br+bl); ctx.lineTo(cx+br, cy+br); ctx.lineTo(cx+br+bl, cy+br); ctx.stroke()
|
|
|
+ ctx.beginPath(); ctx.moveTo(cx+cw-br-bl, cy+br); ctx.lineTo(cx+cw-br, cy+br); ctx.lineTo(cx+cw-br, cy+br+bl); ctx.stroke()
|
|
|
+ ctx.beginPath(); ctx.moveTo(cx+br, cy+ch-br-bl); ctx.lineTo(cx+br, cy+ch-br); ctx.lineTo(cx+br+bl, cy+ch-br); ctx.stroke()
|
|
|
+ ctx.beginPath(); ctx.moveTo(cx+cw-br-bl, cy+ch-br); ctx.lineTo(cx+cw-br, cy+ch-br); ctx.lineTo(cx+cw-br, cy+ch-br-bl); ctx.stroke()
|
|
|
+ // 主文案
|
|
|
+ var title = '邀请你加入「' + (self.familyName || '我的家庭') + '」'
|
|
|
+ ctx.setFillStyle('#C2410C')
|
|
|
+ ctx.setFontSize(26)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText(title, W / 2, cy + 36)
|
|
|
+ // 成员头像(最多3个,首字母占位)
|
|
|
+ var avatarY = cy + 115
|
|
|
+ var avatarR = 30
|
|
|
+ var avatarGap = 20
|
|
|
+ var avatars = []
|
|
|
+ var initials = []
|
|
|
+ for (var i = 0; i < Math.min(3, members.length); i++) {
|
|
|
+ avatars.push(members[i].avatar || '')
|
|
|
+ var name = members[i].nickname || ''
|
|
|
+ initials.push(name.substring(0, 1) || '?')
|
|
|
+ }
|
|
|
+ var totalW = avatars.length * (avatarR * 2) + Math.max(0, avatars.length - 1) * avatarGap
|
|
|
+ var startX = (W - totalW) / 2
|
|
|
+ for (var a = 0; a < avatars.length; a++) {
|
|
|
+ var ax = startX + a * (avatarR * 2 + avatarGap) + avatarR
|
|
|
+ ctx.save()
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(ax, avatarY, avatarR, 0, 2 * Math.PI)
|
|
|
+ ctx.setFillStyle('#FFF0E0')
|
|
|
+ ctx.fill()
|
|
|
+ ctx.restore()
|
|
|
+ ctx.save()
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(ax, avatarY, avatarR, 0, 2 * Math.PI)
|
|
|
+ ctx.clip()
|
|
|
+ ctx.setFillStyle('#FF8C42')
|
|
|
+ ctx.setFontSize(28)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('middle')
|
|
|
+ ctx.fillText(initials[a] || '?', ax, avatarY)
|
|
|
+ ctx.restore()
|
|
|
+ }
|
|
|
+ // 成员数量
|
|
|
+ ctx.setFillStyle('#999999')
|
|
|
+ ctx.setFontSize(16)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.fillText(members.length + ' 位家庭成员', W / 2, avatarY + avatarR + 18)
|
|
|
+ // CTA 按钮
|
|
|
+ var btnW = 200, btnH = 52
|
|
|
+ var btnX = (W - btnW) / 2
|
|
|
+ var btnY = cy + ch - 70
|
|
|
+ var btnGrad = ctx.createLinearGradient(btnX, btnY, btnX + btnW, btnY)
|
|
|
+ btnGrad.addColorStop(0, '#FF8C42')
|
|
|
+ btnGrad.addColorStop(1, '#FFB366')
|
|
|
+ ctx.setFillStyle(btnGrad)
|
|
|
+ roundRect(ctx, btnX, btnY, btnW, btnH, 26)
|
|
|
+ ctx.fill()
|
|
|
+ ctx.setFillStyle('#FFFFFF')
|
|
|
+ ctx.setFontSize(20)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('middle')
|
|
|
+ ctx.fillText('点击加入', W / 2, btnY + btnH / 2)
|
|
|
+ // 底部品牌
|
|
|
+ ctx.setFillStyle('rgba(255,255,255,0.85)')
|
|
|
+ ctx.setFontSize(13)
|
|
|
+ ctx.fillText('浠艾福 · 家庭成长俱乐部', W / 2, H - 30)
|
|
|
+ // 五维点装饰
|
|
|
+ var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
|
|
|
+ var dotY = H - 16
|
|
|
+ for (var d = 0; d < dotColors.length; d++) {
|
|
|
+ ctx.setFillStyle(dotColors[d])
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(W / 2 + (d - 2) * 16, dotY, 3, 0, 2 * Math.PI)
|
|
|
+ ctx.fill()
|
|
|
+ }
|
|
|
+ ctx.draw(false, function() {
|
|
|
+ uni.canvasToTempFilePath({
|
|
|
+ canvasId: canvasId,
|
|
|
+ success: function(res) {
|
|
|
+ self.shareCardImage = res.tempFilePath
|
|
|
+ if (typeof callback === 'function') callback(res.tempFilePath)
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ if (typeof callback === 'function') callback('')
|
|
|
+ }
|
|
|
+ }, self)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ redraw()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 导出 tempFilePath
|
|
|
+ uni.canvasToTempFilePath({
|
|
|
+ canvasId: canvasId,
|
|
|
+ success: function(res) {
|
|
|
+ self.shareCardImage = res.tempFilePath
|
|
|
+ if (typeof callback === 'function') callback(res.tempFilePath)
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ if (typeof callback === 'function') callback('')
|
|
|
+ }
|
|
|
+ }, self)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ render(true)
|
|
|
+ },
|
|
|
onEditRelation(member) {
|
|
|
this.showMemberDetailModal = false
|
|
|
var params = 'memberId=' + member.id + '&nickname=' + encodeURIComponent(member.nickname || '')
|
|
|
@@ -668,11 +844,18 @@ export default {
|
|
|
border: 2rpx solid #F97316;
|
|
|
}
|
|
|
.btn-invite {
|
|
|
- background: linear-gradient(135deg, #F97316, #EA580C);
|
|
|
- color: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-/* ===== 弹窗遮罩 ===== */
|
|
|
+ background: linear-gradient(135deg, #F97316, #EA580C);
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ /* 分享卡片画布:离屏不可见 */
|
|
|
+ .share-card-canvas {
|
|
|
+ position: fixed;
|
|
|
+ left: -9999px;
|
|
|
+ top: -9999px;
|
|
|
+ width: 500px;
|
|
|
+ height: 400px;
|
|
|
+ }
|
|
|
+ /* ===== 弹窗遮罩 ===== */
|
|
|
.modal-mask {
|
|
|
position: fixed;
|
|
|
top: 0;
|