|
|
@@ -0,0 +1,271 @@
|
|
|
+<template>
|
|
|
+ <view class="poster-overlay" v-if="show" @click.self="$emit('close')">
|
|
|
+ <view class="poster-container">
|
|
|
+ <canvas canvas-id="sharePosterCanvas" class="poster-canvas" id="sharePosterCanvas"></canvas>
|
|
|
+ <view class="poster-actions">
|
|
|
+ <button class="poster-btn save" @click="savePoster">保存到相册</button>
|
|
|
+ <button class="poster-btn cancel" @click="$emit('close')">取消</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'SharePoster',
|
|
|
+ props: {
|
|
|
+ show: { type: Boolean, default: false },
|
|
|
+ userAvatar: { type: String, default: '' },
|
|
|
+ nickname: { type: String, default: '' },
|
|
|
+ referralCode: { type: String, default: '' },
|
|
|
+ qrCodeBase64: { type: String, default: '' }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ show(val) {
|
|
|
+ if (val) {
|
|
|
+ this.$nextTick(function() {
|
|
|
+ setTimeout(function() {
|
|
|
+ this.drawPoster()
|
|
|
+ }.bind(this), 300)
|
|
|
+ }.bind(this))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ drawPoster() {
|
|
|
+ var ctx = uni.createCanvasContext('sharePosterCanvas', this)
|
|
|
+ var w = 500
|
|
|
+ var h = 700
|
|
|
+
|
|
|
+ // 1. Orange gradient background
|
|
|
+ var gradient = ctx.createLinearGradient(0, 0, w, h)
|
|
|
+ gradient.addColorStop(0, '#F97316')
|
|
|
+ gradient.addColorStop(1, '#EA580C')
|
|
|
+ ctx.setFillStyle(gradient)
|
|
|
+ ctx.fillRect(0, 0, w, h)
|
|
|
+
|
|
|
+ // 2. White circle border for avatar
|
|
|
+ var avatarX = w / 2
|
|
|
+ var avatarY = 120
|
|
|
+ var avatarR = 50
|
|
|
+ ctx.setStrokeStyle('#ffffff')
|
|
|
+ ctx.setLineWidth(3)
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(avatarX, avatarY, avatarR + 3, 0, 2 * Math.PI)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // 3. Draw avatar or default icon
|
|
|
+ if (this.userAvatar) {
|
|
|
+ var self = this
|
|
|
+ uni.downloadFile({
|
|
|
+ url: this.userAvatar,
|
|
|
+ success: function(res) {
|
|
|
+ if (res.statusCode === 200) {
|
|
|
+ ctx.save()
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(avatarX, avatarY, avatarR, 0, 2 * Math.PI)
|
|
|
+ ctx.clip()
|
|
|
+ ctx.drawImage(res.tempFilePath, avatarX - avatarR, avatarY - avatarR, avatarR * 2, avatarR * 2)
|
|
|
+ ctx.restore()
|
|
|
+ self._drawTextAndQr(ctx, w, h, avatarX, avatarY)
|
|
|
+ } else {
|
|
|
+ self._drawDefaultAvatar(ctx, avatarX, avatarY, avatarR)
|
|
|
+ self._drawTextAndQr(ctx, w, h, avatarX, avatarY)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self._drawDefaultAvatar(ctx, avatarX, avatarY, avatarR)
|
|
|
+ self._drawTextAndQr(ctx, w, h, avatarX, avatarY)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this._drawDefaultAvatar(ctx, avatarX, avatarY, avatarR)
|
|
|
+ this._drawTextAndQr(ctx, w, h, avatarX, avatarY)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawDefaultAvatar(ctx, x, y, r) {
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(x, y, r, 0, 2 * Math.PI)
|
|
|
+ ctx.fill()
|
|
|
+ ctx.setFillStyle('#F97316')
|
|
|
+ ctx.setFontSize(40)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('middle')
|
|
|
+ ctx.fillText('\u{1F464}', x, y)
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawTextAndQr(ctx, w, h, avatarX, avatarY) {
|
|
|
+ var self = this
|
|
|
+
|
|
|
+ // 4. Nickname
|
|
|
+ var nameY = avatarY + 80
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ ctx.setFontSize(28)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText(this.nickname || '\u597d\u53cb', w / 2, nameY)
|
|
|
+
|
|
|
+ // 5. "我的邀请码" label
|
|
|
+ var labelY = nameY + 50
|
|
|
+ ctx.setFontSize(20)
|
|
|
+ ctx.fillText('\u6211\u7684\u9080\u8bf7\u7801', w / 2, labelY)
|
|
|
+
|
|
|
+ // 6. Referral code in large bold text
|
|
|
+ var codeY = labelY + 36
|
|
|
+ ctx.setFontSize(48)
|
|
|
+ ctx.fillText(this.referralCode || '', w / 2, codeY)
|
|
|
+
|
|
|
+ // 7. QR code
|
|
|
+ var qrSize = 220
|
|
|
+ var qrX = (w - qrSize) / 2
|
|
|
+ var qrY = codeY + 70
|
|
|
+
|
|
|
+ if (this.qrCodeBase64) {
|
|
|
+ // Build a file path from base64 data
|
|
|
+ var base64Data = this.qrCodeBase64
|
|
|
+ // Handle data URI prefix
|
|
|
+ if (base64Data.indexOf('data:image') === 0) {
|
|
|
+ var commaIdx = base64Data.indexOf(',')
|
|
|
+ if (commaIdx > -1) {
|
|
|
+ base64Data = base64Data.substring(commaIdx + 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var fs = uni.getFileSystemManager()
|
|
|
+ var filePath = wx.env.USER_DATA_PATH + '/share_qr_tmp.png'
|
|
|
+
|
|
|
+ fs.writeFile({
|
|
|
+ filePath: filePath,
|
|
|
+ data: base64Data,
|
|
|
+ encoding: 'base64',
|
|
|
+ success: function() {
|
|
|
+ // White background behind QR
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ ctx.fillRect(qrX - 10, qrY - 10, qrSize + 20, qrSize + 20)
|
|
|
+ ctx.drawImage(filePath, qrX, qrY, qrSize, qrSize)
|
|
|
+ self._drawHintAndFinish(ctx, w, h, qrY, qrSize)
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self._drawQrPlaceholder(ctx, qrX, qrY, qrSize)
|
|
|
+ self._drawHintAndFinish(ctx, w, h, qrY, qrSize)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this._drawQrPlaceholder(ctx, qrX, qrY, qrSize)
|
|
|
+ this._drawHintAndFinish(ctx, w, h, qrY, qrSize)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawQrPlaceholder(ctx, x, y, size) {
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ ctx.fillRect(x - 10, y - 10, size + 20, size + 20)
|
|
|
+ ctx.setFillStyle('#F97316')
|
|
|
+ ctx.setFontSize(18)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('middle')
|
|
|
+ ctx.fillText('\u4e8c\u7ef4\u7801\u52a0\u8f7d\u4e2d', x + size / 2, y + size / 2)
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawHintAndFinish(ctx, w, h, qrY, qrSize) {
|
|
|
+ // 8. Hint text below QR
|
|
|
+ var hintY = qrY + qrSize + 20
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ ctx.setFontSize(22)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText('\u626b\u7801\u9080\u8bf7\u597d\u53cb\u52a0\u5165', w / 2, hintY)
|
|
|
+
|
|
|
+ ctx.draw()
|
|
|
+ },
|
|
|
+
|
|
|
+ savePoster() {
|
|
|
+ var self = this
|
|
|
+ uni.canvasToTempFilePath({
|
|
|
+ canvasId: 'sharePosterCanvas',
|
|
|
+ success: function(res) {
|
|
|
+ uni.saveImageToPhotosAlbum({
|
|
|
+ filePath: res.tempFilePath,
|
|
|
+ success: function() {
|
|
|
+ uni.showToast({ title: '\u5df2\u4fdd\u5b58\u5230\u76f8\u518c', icon: 'success' })
|
|
|
+ self.$emit('close')
|
|
|
+ },
|
|
|
+ fail: function(err) {
|
|
|
+ if (err && err.errMsg && (err.errMsg.indexOf('deny') > -1 || err.errMsg.indexOf('auth denied') > -1 || err.errMsg.indexOf('authorize') > -1)) {
|
|
|
+ uni.showModal({
|
|
|
+ title: '\u63d0\u793a',
|
|
|
+ content: '\u9700\u8981\u76f8\u518c\u6743\u9650\u624d\u80fd\u4fdd\u5b58\u56fe\u7247',
|
|
|
+ confirmText: '\u53bb\u8bbe\u7f6e',
|
|
|
+ success: function(modalRes) {
|
|
|
+ if (modalRes.confirm) {
|
|
|
+ uni.openSetting()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: '\u4fdd\u5b58\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5', icon: 'none' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ uni.showToast({ title: '\u751f\u6210\u56fe\u7247\u5931\u8d25', icon: 'none' })
|
|
|
+ }
|
|
|
+ }, this)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.poster-overlay {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 999;
|
|
|
+}
|
|
|
+.poster-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.poster-canvas {
|
|
|
+ width: 500px;
|
|
|
+ height: 700px;
|
|
|
+ border-radius: 16rpx;
|
|
|
+}
|
|
|
+.poster-actions {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ width: 500px;
|
|
|
+ margin-top: 30rpx;
|
|
|
+}
|
|
|
+.poster-btn {
|
|
|
+ height: 80rpx;
|
|
|
+ line-height: 80rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ text-align: center;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+.poster-btn::after { border: none; }
|
|
|
+.poster-btn.save {
|
|
|
+ background: #ffffff;
|
|
|
+ color: #F97316;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.poster-btn.save:active { opacity: 0.85; }
|
|
|
+.poster-btn.cancel {
|
|
|
+ background: rgba(255, 255, 255, 0.15);
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+.poster-btn.cancel:active { opacity: 0.85; }
|
|
|
+</style>
|