增加分销页面,订单增加会员折扣

This commit is contained in:
2025-10-25 16:31:38 +08:00
parent 26532150b5
commit 845d9f7b40
39 changed files with 5988 additions and 43 deletions

279
distribution/poster.vue Normal file
View 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>