增加分销页面,订单增加会员折扣
This commit is contained in:
279
distribution/poster.vue
Normal file
279
distribution/poster.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<view class="poster-page">
|
||||
<view class="preview-container" v-if="posterUrl">
|
||||
<image :src="posterUrl" mode="widthFix" class="poster-img"></image>
|
||||
<button class="save-btn" @click="saveToAlbum">保存到相册</button>
|
||||
</view>
|
||||
<button class="generate-btn" @click="generatePoster" v-if="!posterUrl">生成海报</button>
|
||||
<canvas
|
||||
id="posterCanvas"
|
||||
type="2d"
|
||||
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
||||
class="canvas-hidden"
|
||||
></canvas>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, getCurrentInstance } from 'vue'
|
||||
import { onReady } from '@dcloudio/uni-app'
|
||||
|
||||
// 基础变量(不变)
|
||||
const canvasWidth = ref(375)
|
||||
const canvasHeight = ref(667)
|
||||
const posterUrl = ref('')
|
||||
let canvasNode = null
|
||||
let ctx = null
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// 海报配置(不变)
|
||||
const posterConfig = {
|
||||
bgImage: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/1/677c4a5ae43a45eb98c0ae1a6d242021.png',
|
||||
title: { text: '限时优惠活动', x: 20, y: 40, fontSize: 24, color: '#fff', fontWeight: 'bold' },
|
||||
desc: { text: '全场商品满200减50,限时3天', x: 20, y: 80, fontSize: 16, color: '#fff' },
|
||||
mainImage: { url: 'https://img.yzcdn.cn/vant/apple-1.jpg', x: 20, y: 120, width: 335, height: 335, radius: 10 },
|
||||
qrcode: { url: 'https://czg-oss.oss-cn-hangzhou.aliyuncs.com/catering/store/HQ200龙虾仔.JPG', x: 265, y: 500, width: 90, height: 90 },
|
||||
footerText: { text: '扫码立即参与活动', x: 20, y: 550, fontSize: 14, color: '#333' }
|
||||
}
|
||||
|
||||
// 初始化 Canvas(不变)
|
||||
onReady(() => {
|
||||
const query = uni.createSelectorQuery().in(instance)
|
||||
query
|
||||
.select('#posterCanvas')
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
if (!res[0] || !res[0].node) {
|
||||
console.error('未找到 Canvas 节点')
|
||||
uni.showToast({ title: 'Canvas节点初始化失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
canvasNode = res[0].node
|
||||
canvasNode.width = canvasWidth.value
|
||||
canvasNode.height = canvasHeight.value
|
||||
ctx = canvasNode.getContext('2d')
|
||||
if (!ctx) {
|
||||
console.error('获取 2D 上下文失败')
|
||||
uni.showToast({ title: 'Canvas上下文初始化失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 绘制背景(依赖修复后的 drawImage)
|
||||
const drawBackground = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!posterConfig.bgImage) return reject('无背景图')
|
||||
drawImage({
|
||||
url: posterConfig.bgImage,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: canvasWidth.value,
|
||||
height: canvasHeight.value
|
||||
}).then(resolve).catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
// 绘制文字(不变)
|
||||
const drawText = (options) => {
|
||||
const { text, x, y, fontSize, color, fontWeight = 'normal', fontFamily = 'sans-serif' } = options
|
||||
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`
|
||||
ctx.fillStyle = color
|
||||
ctx.fillText(text, x, y)
|
||||
}
|
||||
|
||||
// 绘制圆角矩形(不变)
|
||||
const drawRoundRect = (x, y, width, height, radius) => {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x + radius, y)
|
||||
ctx.arcTo(x + width, y, x + width, y + height, radius)
|
||||
ctx.arcTo(x + width, y + height, x, y + height, radius)
|
||||
ctx.arcTo(x, y + height, x, y, radius)
|
||||
ctx.arcTo(x, y, x + width, y, radius)
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
// ---------------------- 核心修复:drawImage 方法(无需 Image 实例) ----------------------
|
||||
const drawImage = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { url, x, y, width, height, radius = 0 } = options
|
||||
if (!url) return reject('图片路径为空')
|
||||
if (width <= 0 || height <= 0) return reject(`图片尺寸无效:${width}x${height}`)
|
||||
// 关键:确保 canvasNode 已初始化(否则无法创建图片对象)
|
||||
if (!canvasNode) return reject('Canvas 节点未初始化,无法创建图片对象')
|
||||
|
||||
// 1. 获取图片临时路径(不变)
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: (imgInfo) => {
|
||||
if (imgInfo.width <= 0 || imgInfo.height <= 0) {
|
||||
return reject(`无效图片:${url}`)
|
||||
}
|
||||
|
||||
// 2. 关键修复:用 Canvas 节点的 createImage() 方法创建图片对象
|
||||
// 这是微信小程序 Canvas 2D 节点原生支持的方法,类型完全匹配
|
||||
const image = canvasNode.createImage()
|
||||
|
||||
// 3. 监听图片加载完成(接口与标准 Image 一致)
|
||||
image.onload = () => {
|
||||
try {
|
||||
if (radius > 0) {
|
||||
ctx.save()
|
||||
drawRoundRect(x, y, width, height, radius)
|
||||
ctx.clip()
|
||||
}
|
||||
|
||||
// 4. 绘制图片:传入 Canvas 节点创建的图片实例(类型匹配)
|
||||
ctx.drawImage(image, x, y, width, height)
|
||||
|
||||
if (radius > 0) ctx.restore()
|
||||
resolve()
|
||||
} catch (drawErr) {
|
||||
reject(`绘制图片失败:${drawErr.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听加载失败
|
||||
image.onerror = (err) => {
|
||||
reject(`图片加载失败:${err.message || '未知错误'}`)
|
||||
}
|
||||
|
||||
// 5. 赋值临时路径,触发加载
|
||||
image.src = imgInfo.path
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(`获取图片信息失败:${err.errMsg}(路径:${url})`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 生成海报(不变)
|
||||
const generatePoster = async () => {
|
||||
try {
|
||||
if (!canvasNode || !ctx) throw new Error('Canvas 未初始化完成')
|
||||
ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
|
||||
|
||||
// 绘制背景(失败则用纯色兜底)
|
||||
await drawBackground().catch(() => {
|
||||
ctx.fillStyle = '#f5f5f5'
|
||||
ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value)
|
||||
})
|
||||
|
||||
// 绘制文字
|
||||
drawText(posterConfig.title)
|
||||
drawText(posterConfig.desc)
|
||||
drawText(posterConfig.footerText)
|
||||
|
||||
// 绘制图片(主图 + 二维码)
|
||||
await drawImage(posterConfig.mainImage)
|
||||
await drawImage(posterConfig.qrcode)
|
||||
|
||||
// 延迟确保绘制生效
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
await convertToImage()
|
||||
uni.showToast({ title: '海报生成成功', icon: 'success' })
|
||||
} catch (err) {
|
||||
console.error('生成失败:', err)
|
||||
uni.showToast({ title: `生成失败:${err.message}`, icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
// 转图片(不变)
|
||||
const convertToImage = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!canvasNode) return reject('Canvas 节点不存在')
|
||||
uni.canvasToTempFilePath({
|
||||
canvas: canvasNode,
|
||||
width: canvasWidth.value,
|
||||
height: canvasHeight.value,
|
||||
destWidth: canvasWidth.value * 2,
|
||||
destHeight: canvasHeight.value * 2,
|
||||
success: (res) => {
|
||||
posterUrl.value = res.tempFilePath
|
||||
resolve()
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(`转图片失败:${err.errMsg}`)
|
||||
}
|
||||
}, instance)
|
||||
})
|
||||
}
|
||||
|
||||
// 保存相册相关方法(不变)
|
||||
const saveToAlbum = () => {
|
||||
if (!posterUrl.value) return
|
||||
uni.getSetting({
|
||||
success: (res) => {
|
||||
if (!res.authSetting['scope.writePhotosAlbum']) {
|
||||
uni.authorize({
|
||||
scope: 'scope.writePhotosAlbum',
|
||||
success: saveImage,
|
||||
fail: () => {
|
||||
uni.showModal({
|
||||
title: '权限申请',
|
||||
content: '需要相册权限才能保存海报',
|
||||
success: (modalRes) => modalRes.confirm && uni.openSetting()
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
saveImage()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const saveImage = () => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: posterUrl.value,
|
||||
success: () => uni.showToast({ title: '保存成功', icon: 'success' }),
|
||||
fail: (err) => {
|
||||
console.error('保存失败:', err)
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式不变 */
|
||||
.poster-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
}
|
||||
.canvas-hidden {
|
||||
position: absolute;
|
||||
left: -9999rpx;
|
||||
top: -9999rpx;
|
||||
z-index: -1;
|
||||
width: 375px;
|
||||
height: 667px;
|
||||
}
|
||||
.preview-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.poster-img {
|
||||
width: 100%;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.generate-btn, .save-btn {
|
||||
width: 600rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
margin-top: 40rpx;
|
||||
background-color: #07c160;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
.save-btn {
|
||||
background-color: #1677ff;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user