|
|
@@ -2,6 +2,50 @@
|
|
|
<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-tpl-row">
|
|
|
+ <view class="tpl-label">海报模板</view>
|
|
|
+ <view class="tpl-options">
|
|
|
+ <view
|
|
|
+ class="tpl-option"
|
|
|
+ :class="{ 'tpl-active': template === 'gradient' }"
|
|
|
+ @click="switchTemplate('gradient')"
|
|
|
+ >
|
|
|
+ <view class="tpl-dot tpl-dot-grad"></view>
|
|
|
+ <text class="tpl-text">渐变</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="tpl-option"
|
|
|
+ :class="{ 'tpl-active': template === 'minimal' }"
|
|
|
+ @click="switchTemplate('minimal')"
|
|
|
+ >
|
|
|
+ <view class="tpl-dot tpl-dot-dark"></view>
|
|
|
+ <text class="tpl-text">深色</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="tpl-option"
|
|
|
+ :class="{ 'tpl-active': template === 'card' }"
|
|
|
+ @click="switchTemplate('card')"
|
|
|
+ >
|
|
|
+ <view class="tpl-dot tpl-dot-light"></view>
|
|
|
+ <text class="tpl-text">卡片</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 背景颜色选择(渐变模板可用) -->
|
|
|
+ <view class="poster-bg-row" v-if="template === 'gradient'">
|
|
|
+ <view class="tpl-label">背景色</view>
|
|
|
+ <view class="bg-options">
|
|
|
+ <view
|
|
|
+ v-for="(c, i) in bgColors"
|
|
|
+ :key="i"
|
|
|
+ class="bg-option"
|
|
|
+ :class="{ 'bg-active': bgColor === c }"
|
|
|
+ :style="{ background: c }"
|
|
|
+ @click="switchBgColor(c)"
|
|
|
+ ></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
<view class="poster-actions">
|
|
|
<button class="poster-btn save" @click="savePoster">保存到相册</button>
|
|
|
</view>
|
|
|
@@ -23,6 +67,12 @@ export default {
|
|
|
priceText: { type: String, default: '' },
|
|
|
ctaText: { type: String, default: '长按识别二维码 · 查看详情' },
|
|
|
inviteText: { type: String, default: '' },
|
|
|
+ // 海报模板: gradient(default) | minimal | card
|
|
|
+ template: { type: String, default: 'gradient' },
|
|
|
+ // 自定义背景色(覆盖模板默认背景)
|
|
|
+ bgColor: { type: String, default: '' },
|
|
|
+ // 二维码尺寸覆盖(默认150rpx,建议活动类可用 100 缩小)
|
|
|
+ qrSizeOverride: { type: Number, default: 150 },
|
|
|
// 五维能量数据卡:{ label?: String, overall?: Number, dims: [{ key, label, score, color }] }
|
|
|
// 传 null 时不画数据卡,按 coverImage -> brandFallback -> 占位图 顺序降级(原有行为不变)
|
|
|
energyData: { type: Object, default: null },
|
|
|
@@ -38,6 +88,12 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 可选的背景色(渐变模板下展示)
|
|
|
+ bgColors: ['#FF8C42', '#6366F1', '#F59E0B', '#10B981', '#FF6B9D', '#1E293B', '#FFFFFF']
|
|
|
+ }
|
|
|
+ },
|
|
|
methods: {
|
|
|
getColors() {
|
|
|
var map = {
|
|
|
@@ -49,6 +105,35 @@ export default {
|
|
|
}
|
|
|
return map[this.dimensionCode] || { main: '#F97316', dark: '#EA580C', light: '#FFF7ED' }
|
|
|
},
|
|
|
+ // 海报模板配色
|
|
|
+ getTemplateConfig() {
|
|
|
+ var colors = this.getColors()
|
|
|
+ var template = this.template || 'gradient'
|
|
|
+ var bgColor = this.bgColor || ''
|
|
|
+ // 梯度背景模板
|
|
|
+ if (template === 'gradient') {
|
|
|
+ return { template: 'gradient', bgColor: bgColor || colors.main, main: colors.main, dark: colors.dark, light: colors.light }
|
|
|
+ }
|
|
|
+ // 极简模板:深色背景+白色卡片
|
|
|
+ if (template === 'minimal') {
|
|
|
+ var dark = bgColor || '#1E293B'
|
|
|
+ return { template: 'minimal', bgColor: dark, main: '#F97316', dark: '#EF4444', light: '#F3F4F6' }
|
|
|
+ }
|
|
|
+ // 卡片模板:浅色背景+彩色顶栏
|
|
|
+ return { template: 'card', bgColor: bgColor || '#FFFFFF', main: colors.main, dark: colors.dark, light: colors.light }
|
|
|
+ },
|
|
|
+ // 获取当前模板的背景样式
|
|
|
+ getBgStyle() {
|
|
|
+ var cfg = this.getTemplateConfig()
|
|
|
+ if (cfg.template === 'gradient') {
|
|
|
+ var dark = cfg.dark
|
|
|
+ return 'linear-gradient(135deg, ' + cfg.main + ', ' + dark + ')'
|
|
|
+ }
|
|
|
+ if (cfg.template === 'minimal') {
|
|
|
+ return 'linear-gradient(135deg, ' + cfg.bgColor + ', ' + cfg.dark + ')'
|
|
|
+ }
|
|
|
+ return 'linear-gradient(135deg, ' + cfg.main + ', ' + (cfg.bgColor || '#FFFFFF') + ')'
|
|
|
+ },
|
|
|
drawPoster() {
|
|
|
var self = this
|
|
|
uni.createSelectorQuery().in(this)
|
|
|
@@ -69,7 +154,7 @@ export default {
|
|
|
var ctx = uni.createCanvasContext('contentPosterCanvas', this)
|
|
|
var w = 500
|
|
|
var h = 800
|
|
|
- var colors = this.getColors()
|
|
|
+ var cfg = this.getTemplateConfig()
|
|
|
|
|
|
// 画布 CSS 用 rpx,实际像素随设备变化(375px 屏约 310x496),而绘制按 500x800 设计坐标;
|
|
|
// 不缩放坐标系时右侧/底部内容(含二维码 y≈552-702)会被裁剪出画布,导致海报显示不全。
|
|
|
@@ -77,20 +162,33 @@ export default {
|
|
|
ctx.scale(scaleX, scaleY)
|
|
|
}
|
|
|
|
|
|
- var gradient = ctx.createLinearGradient(0, 0, w * 0.3, h)
|
|
|
- gradient.addColorStop(0, colors.main)
|
|
|
- gradient.addColorStop(0.55, colors.dark)
|
|
|
- gradient.addColorStop(1, colors.dark)
|
|
|
- ctx.setFillStyle(gradient)
|
|
|
- ctx.fillRect(0, 0, w, h)
|
|
|
+ // 【模板1 · gradient】渐变背景
|
|
|
+ if (cfg.template === 'gradient') {
|
|
|
+ var gradient = ctx.createLinearGradient(0, 0, w * 0.3, h)
|
|
|
+ gradient.addColorStop(0, cfg.main)
|
|
|
+ gradient.addColorStop(0.55, cfg.dark)
|
|
|
+ gradient.addColorStop(1, cfg.dark)
|
|
|
+ ctx.setFillStyle(gradient)
|
|
|
+ ctx.fillRect(0, 0, w, h)
|
|
|
+ }
|
|
|
+ // 【模板2 · minimal】纯色深色背景
|
|
|
+ else if (cfg.template === 'minimal') {
|
|
|
+ ctx.setFillStyle(cfg.bgColor)
|
|
|
+ ctx.fillRect(0, 0, w, h)
|
|
|
+ }
|
|
|
+ // 【模板3 · card】白色背景
|
|
|
+ else {
|
|
|
+ ctx.setFillStyle('#FFFFFF')
|
|
|
+ ctx.fillRect(0, 0, w, h)
|
|
|
+ }
|
|
|
|
|
|
- this._drawTopDecoration(ctx, w, colors)
|
|
|
+ this._drawTopDecoration(ctx, w, cfg)
|
|
|
|
|
|
var cardX = 28
|
|
|
var cardY = 112
|
|
|
var cardW = 444
|
|
|
var cardH = 600
|
|
|
- this._drawContentCard(ctx, cardX, cardY, cardW, cardH, colors)
|
|
|
+ this._drawContentCard(ctx, cardX, cardY, cardW, cardH, cfg)
|
|
|
|
|
|
var coverY = cardY + 20
|
|
|
var coverW = cardW - 40
|
|
|
@@ -99,8 +197,8 @@ export default {
|
|
|
|
|
|
if (this.energyData) {
|
|
|
// 方案C:五维能量数据卡,canvas 直接绘制(文字锐利、数据真实、无网络开销)
|
|
|
- this._drawEnergyCard(ctx, cardX + 20, coverY, coverW, coverH, colors)
|
|
|
- this._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)
|
|
|
+ this._drawEnergyCard(ctx, cardX + 20, coverY, coverW, coverH, cfg)
|
|
|
+ this._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, cfg)
|
|
|
} else if (this.coverImage) {
|
|
|
uni.downloadFile({
|
|
|
url: this.coverImage,
|
|
|
@@ -112,29 +210,29 @@ export default {
|
|
|
success: function(info) {
|
|
|
var rect = self._fitCoverRect(info.width, info.height, cardX + 20, coverY, coverW, coverH)
|
|
|
ctx.drawImage(res.tempFilePath, rect.x, rect.y, rect.w, rect.h)
|
|
|
- self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)
|
|
|
+ self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, cfg)
|
|
|
},
|
|
|
fail: function() {
|
|
|
ctx.drawImage(res.tempFilePath, cardX + 20, coverY, coverW, coverH)
|
|
|
- self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)
|
|
|
+ self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, cfg)
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
self._drawCoverPlaceholder(ctx, cardX + 20, coverY, coverW, coverH)
|
|
|
- self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)
|
|
|
+ self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, cfg)
|
|
|
}
|
|
|
},
|
|
|
fail: function() {
|
|
|
self._drawCoverPlaceholder(ctx, cardX + 20, coverY, coverW, coverH)
|
|
|
- self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)
|
|
|
+ self._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, cfg)
|
|
|
}
|
|
|
})
|
|
|
} else if (this.brandFallback) {
|
|
|
- this._drawBrandCard(ctx, cardX + 20, coverY, coverW, coverH, colors)
|
|
|
- this._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)
|
|
|
+ this._drawBrandCard(ctx, cardX + 20, coverY, coverW, coverH, cfg)
|
|
|
+ this._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, cfg)
|
|
|
} else {
|
|
|
this._drawCoverPlaceholder(ctx, cardX + 20, coverY, coverW, coverH)
|
|
|
- this._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, colors)
|
|
|
+ this._drawContentAfterCover(ctx, w, h, cardX, cardW, coverY, coverH, cfg)
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -370,8 +468,8 @@ export default {
|
|
|
ctx.setTextBaseline('top')
|
|
|
ctx.fillText(this.inviteText || '长按识别二维码 · 查看详情', w / 2, inviteY)
|
|
|
|
|
|
- // 二维码
|
|
|
- var qrSize = 150
|
|
|
+ // 二维码 - 可配置尺寸(默认150,可通过 qrSizeOverride 调整)
|
|
|
+ var qrSize = this.qrSizeOverride || 150
|
|
|
var qrX = (w - qrSize) / 2
|
|
|
var qrY = inviteY + 24
|
|
|
|
|
|
@@ -526,6 +624,21 @@ export default {
|
|
|
ctx.closePath()
|
|
|
},
|
|
|
|
|
|
+ switchTemplate(tpl) {
|
|
|
+ this.$emit('update:template', tpl)
|
|
|
+ var self = this
|
|
|
+ this.$nextTick(function() {
|
|
|
+ setTimeout(self.drawPoster.bind(self), 100)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ switchBgColor(color) {
|
|
|
+ this.$emit('update:bgColor', color)
|
|
|
+ var self = this
|
|
|
+ this.$nextTick(function() {
|
|
|
+ setTimeout(self.drawPoster.bind(self), 100)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
savePoster() {
|
|
|
var self = this
|
|
|
uni.canvasToTempFilePath({
|
|
|
@@ -610,4 +723,63 @@ export default {
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
.poster-btn.save:active { opacity: 0.85; }
|
|
|
-</style>
|
|
|
+.poster-tpl-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin: 12rpx 0;
|
|
|
+ font-size: 24rpx;
|
|
|
+}
|
|
|
+.tpl-label {
|
|
|
+ margin-right: 20rpx;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+.tpl-options {
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+.tpl-option {
|
|
|
+ flex: 1;
|
|
|
+ margin: 0 4rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+.tpl-dot {
|
|
|
+ width: 24rpx;
|
|
|
+ height: 24rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ margin-bottom: 4rpx;
|
|
|
+}
|
|
|
+.tpl-dot-grad { background: linear-gradient(135deg, #FF8C42, #EA580C); }
|
|
|
+.tpl-dot-dark { background: #1E293B; }
|
|
|
+.tpl-dot-light { background: #FFFFFF; }
|
|
|
+.tpl-text {
|
|
|
+ font-size: 14rpx;
|
|
|
+ text-align: center;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+.tpl-active .tpl-text { color: #F97316; }
|
|
|
+
|
|
|
+.poster-bg-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin: 12rpx 0;
|
|
|
+ font-size: 24rpx;
|
|
|
+}
|
|
|
+.bg-label {
|
|
|
+ margin-right: 20rpx;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+.bg-options {
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+.bg-option {
|
|
|
+ width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ margin: 0 4rpx;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+.bg-active {
|
|
|
+ border: 2rpx solid #F97316;
|
|
|
+}</style>
|