|
|
@@ -0,0 +1,404 @@
|
|
|
+<template>
|
|
|
+ <view class="poster-overlay" v-if="show" @click.self="$emit('close')">
|
|
|
+ <view class="poster-container">
|
|
|
+ <canvas canvas-id="contentPosterCanvas" class="poster-canvas" id="contentPosterCanvas"></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: 'ContentSharePoster',
|
|
|
+ props: {
|
|
|
+ show: { type: Boolean, default: false },
|
|
|
+ title: { type: String, default: '' },
|
|
|
+ coverImage: { type: String, default: '' },
|
|
|
+ qrCodeBase64: { type: String, default: '' },
|
|
|
+ typeLabel: { type: String, default: '好物推荐' }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ show(val) {
|
|
|
+ if (val) {
|
|
|
+ this.$nextTick(function() {
|
|
|
+ setTimeout(this.drawPoster.bind(this), 300)
|
|
|
+ }.bind(this))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ drawPoster() {
|
|
|
+ var ctx = uni.createCanvasContext('contentPosterCanvas', this)
|
|
|
+ var w = 500
|
|
|
+ var h = 800
|
|
|
+
|
|
|
+ var gradient = ctx.createLinearGradient(0, 0, w * 0.3, h)
|
|
|
+ gradient.addColorStop(0, '#F97316')
|
|
|
+ gradient.addColorStop(0.55, '#EA580C')
|
|
|
+ gradient.addColorStop(1, '#D94F0A')
|
|
|
+ ctx.setFillStyle(gradient)
|
|
|
+ ctx.fillRect(0, 0, w, h)
|
|
|
+
|
|
|
+ this._drawTopDecoration(ctx, w)
|
|
|
+
|
|
|
+ var cardX = 28
|
|
|
+ var cardY = 112
|
|
|
+ var cardW = 444
|
|
|
+ var cardH = 600
|
|
|
+ this._drawContentCard(ctx, cardX, cardY, cardW, cardH)
|
|
|
+
|
|
|
+ var coverY = cardY + 20
|
|
|
+ var coverW = cardW - 40
|
|
|
+ var coverH = 220
|
|
|
+ var self = this
|
|
|
+
|
|
|
+ if (this.coverImage) {
|
|
|
+ uni.downloadFile({
|
|
|
+ url: this.coverImage,
|
|
|
+ success: function(res) {
|
|
|
+ if (res.statusCode === 200) {
|
|
|
+ ctx.drawImage(res.tempFilePath, cardX + 20, coverY, coverW, coverH)
|
|
|
+ } else {
|
|
|
+ self._drawCoverPlaceholder(ctx, cardX + 20, coverY, coverW, coverH)
|
|
|
+ }
|
|
|
+ self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH)
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self._drawCoverPlaceholder(ctx, cardX + 20, coverY, coverW, coverH)
|
|
|
+ self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this._drawCoverPlaceholder(ctx, cardX + 20, coverY, coverW, coverH)
|
|
|
+ this._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawTopDecoration(ctx, w) {
|
|
|
+ var centerX = w / 2
|
|
|
+ var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
|
|
|
+ var dotY = 28
|
|
|
+ var dotR = 5
|
|
|
+ var dotSpacing = 26
|
|
|
+ var i, offsetX, offsetY
|
|
|
+
|
|
|
+ for (i = 0; i < 5; i++) {
|
|
|
+ offsetX = (i - 2) * dotSpacing
|
|
|
+ offsetY = Math.abs(i - 2) * 3
|
|
|
+ ctx.setFillStyle(dotColors[i])
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(centerX + offsetX, dotY + offsetY, dotR, 0, 2 * Math.PI)
|
|
|
+ ctx.fill()
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ ctx.setFontSize(20)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText('浠艾福', centerX, 52)
|
|
|
+
|
|
|
+ ctx.setFontSize(14)
|
|
|
+ ctx.setFillStyle('rgba(255,255,255,0.8)')
|
|
|
+ ctx.fillText('给全家的一站式幸福提案', centerX, 76)
|
|
|
+
|
|
|
+ ctx.setStrokeStyle('rgba(255,255,255,0.25)')
|
|
|
+ ctx.setLineWidth(1)
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(80, 100)
|
|
|
+ ctx.lineTo(w - 80, 100)
|
|
|
+ ctx.stroke()
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawContentCard(ctx, x, y, w, h) {
|
|
|
+ ctx.setFillStyle('rgba(0,0,0,0.04)')
|
|
|
+ this._roundRect(ctx, x + 2, y + 3, w, h, 16)
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setFillStyle('#FFFCF5')
|
|
|
+ this._roundRect(ctx, x, y, w, h, 16)
|
|
|
+ ctx.fill()
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawCoverPlaceholder(ctx, x, y, w, h) {
|
|
|
+ ctx.setFillStyle('#FEF3C7')
|
|
|
+ this._roundRect(ctx, x, y, w, h, 8)
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setFillStyle('#F97316')
|
|
|
+ ctx.setFontSize(16)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('middle')
|
|
|
+ ctx.fillText('暂无封面图', x + w / 2, y + h / 2)
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH) {
|
|
|
+ var self = this
|
|
|
+
|
|
|
+ var labelY = coverY + coverH + 16
|
|
|
+ ctx.setFillStyle('#F97316')
|
|
|
+ ctx.setFontSize(13)
|
|
|
+ ctx.setTextAlign('left')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText(this.typeLabel, cardX + 20, labelY)
|
|
|
+
|
|
|
+ var titleY = labelY + 22
|
|
|
+ ctx.setFillStyle('#1C1917')
|
|
|
+ ctx.setFontSize(18)
|
|
|
+ ctx.setTextAlign('left')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ this._drawWrappedText(ctx, this.title || '精彩内容', cardX + 20, titleY, cardW - 40, 24, 2)
|
|
|
+
|
|
|
+ var hintY = titleY + 56
|
|
|
+ ctx.setFontSize(13)
|
|
|
+ ctx.setFillStyle('#A8A29E')
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText('长按识别二维码 · 查看详情', w / 2, hintY)
|
|
|
+
|
|
|
+ var qrSize = 160
|
|
|
+ var qrX = (w - qrSize) / 2
|
|
|
+ var qrY = hintY + 20
|
|
|
+
|
|
|
+ if (this.qrCodeBase64) {
|
|
|
+ var base64Data = this.qrCodeBase64
|
|
|
+ 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 + '/content_share_qr_tmp.png'
|
|
|
+
|
|
|
+ fs.writeFile({
|
|
|
+ filePath: filePath,
|
|
|
+ data: base64Data,
|
|
|
+ encoding: 'base64',
|
|
|
+ success: function() {
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ self._roundRect(ctx, qrX - 10, qrY - 10, qrSize + 20, qrSize + 20, 8)
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setStrokeStyle('#E7E5E4')
|
|
|
+ ctx.setLineWidth(0.5)
|
|
|
+ self._roundRect(ctx, qrX - 10, qrY - 10, qrSize + 20, qrSize + 20, 8)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ ctx.drawImage(filePath, qrX, qrY, qrSize, qrSize)
|
|
|
+ self._drawBottomDecoration(ctx, w, h, qrY, qrSize)
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self._drawQrPlaceholder(ctx, qrX, qrY, qrSize)
|
|
|
+ self._drawBottomDecoration(ctx, w, h, qrY, qrSize)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this._drawQrPlaceholder(ctx, qrX, qrY, qrSize)
|
|
|
+ this._drawBottomDecoration(ctx, w, h, qrY, qrSize)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawWrappedText(ctx, text, x, y, maxWidth, lineHeight, maxLines) {
|
|
|
+ var chars = (text || '').split('')
|
|
|
+ var line = ''
|
|
|
+ var lineCount = 0
|
|
|
+ for (var i = 0; i < chars.length; i++) {
|
|
|
+ var testLine = line + chars[i]
|
|
|
+ var testWidth = 0
|
|
|
+ if (ctx.measureText) {
|
|
|
+ try { testWidth = ctx.measureText(testLine).width } catch (e) { testWidth = testLine.length * 18 }
|
|
|
+ } else {
|
|
|
+ testWidth = testLine.length * 18
|
|
|
+ }
|
|
|
+ if (testWidth > maxWidth && line.length > 0) {
|
|
|
+ ctx.fillText(line, x, y + lineCount * lineHeight)
|
|
|
+ line = chars[i]
|
|
|
+ lineCount++
|
|
|
+ if (lineCount >= maxLines - 1) {
|
|
|
+ var remaining = chars.slice(i).join('')
|
|
|
+ var remainingLine = line + remaining
|
|
|
+ if (ctx.measureText) {
|
|
|
+ while (ctx.measureText(remainingLine + '..').width > maxWidth && remainingLine.length > 2) {
|
|
|
+ remainingLine = remainingLine.substring(0, remainingLine.length - 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ctx.fillText(remainingLine + '..', x, y + lineCount * lineHeight)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ line = testLine
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ctx.fillText(line, x, y + lineCount * lineHeight)
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawQrPlaceholder(ctx, x, y, size) {
|
|
|
+ ctx.setFillStyle('#ffffff')
|
|
|
+ this._roundRect(ctx, x - 10, y - 10, size + 20, size + 20, 8)
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setStrokeStyle('#E7E5E4')
|
|
|
+ ctx.setLineWidth(0.5)
|
|
|
+ this._roundRect(ctx, x - 10, y - 10, size + 20, size + 20, 8)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ ctx.setFillStyle('#A8A29E')
|
|
|
+ ctx.setFontSize(16)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('middle')
|
|
|
+ ctx.fillText('二维码加载中', x + size / 2, y + size / 2)
|
|
|
+ },
|
|
|
+
|
|
|
+ _drawBottomDecoration(ctx, w, h, qrY, qrSize) {
|
|
|
+ var centerX = w / 2
|
|
|
+ var bottomY = qrY + qrSize + 16
|
|
|
+
|
|
|
+ ctx.setFillStyle('rgba(249,115,22,0.15)')
|
|
|
+ var diamondY = bottomY + 6
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(centerX - 76, diamondY)
|
|
|
+ ctx.lineTo(centerX - 72, diamondY - 4)
|
|
|
+ ctx.lineTo(centerX - 68, diamondY)
|
|
|
+ ctx.lineTo(centerX - 72, diamondY + 4)
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.fill()
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(centerX + 68, diamondY)
|
|
|
+ ctx.lineTo(centerX + 72, diamondY - 4)
|
|
|
+ ctx.lineTo(centerX + 76, diamondY)
|
|
|
+ ctx.lineTo(centerX + 72, diamondY + 4)
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ ctx.setFillStyle('#F97316')
|
|
|
+ ctx.setFontSize(18)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText('扫码查看详情', centerX, bottomY)
|
|
|
+
|
|
|
+ var dotColors = ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D']
|
|
|
+ var dotY = h - 22
|
|
|
+ var dotR = 4
|
|
|
+ var dotSpacing = 20
|
|
|
+ var i_b, offsetX_b
|
|
|
+
|
|
|
+ for (i_b = 0; i_b < 5; i_b++) {
|
|
|
+ offsetX_b = (i_b - 2) * dotSpacing
|
|
|
+ ctx.setFillStyle(dotColors[i_b])
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(centerX + offsetX_b, dotY, dotR, 0, 2 * Math.PI)
|
|
|
+ ctx.fill()
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.setFillStyle('rgba(255,255,255,0.45)')
|
|
|
+ ctx.setFontSize(12)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.setTextBaseline('top')
|
|
|
+ ctx.fillText('浠艾福 · 家庭健康俱乐部', centerX, dotY + 7)
|
|
|
+
|
|
|
+ ctx.draw()
|
|
|
+ },
|
|
|
+
|
|
|
+ _roundRect(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, y + h - r, r)
|
|
|
+ ctx.arcTo(x, y, x + r, y, r)
|
|
|
+ ctx.closePath()
|
|
|
+ },
|
|
|
+
|
|
|
+ savePoster() {
|
|
|
+ var self = this
|
|
|
+ uni.canvasToTempFilePath({
|
|
|
+ canvasId: 'contentPosterCanvas',
|
|
|
+ success: function(res) {
|
|
|
+ uni.saveImageToPhotosAlbum({
|
|
|
+ filePath: res.tempFilePath,
|
|
|
+ success: function() {
|
|
|
+ uni.showToast({ title: '已保存到相册', 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: '提示',
|
|
|
+ content: '需要相册权限才能保存图片',
|
|
|
+ confirmText: '去设置',
|
|
|
+ success: function(modalRes) {
|
|
|
+ if (modalRes.confirm) {
|
|
|
+ uni.openSetting()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: '保存失败,请重试', icon: 'none' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ uni.showToast({ title: '生成图片失败', 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: 800px;
|
|
|
+ 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>
|