新增营销系统、分销系统、会员功能、门店、提现功能
This commit is contained in:
378
components/Poster/GroupBy.vue
Normal file
378
components/Poster/GroupBy.vue
Normal file
@ -0,0 +1,378 @@
|
||||
<!--
|
||||
@name: GoodsPoster
|
||||
@author: kahu4
|
||||
@date: 2024-01-17 17:38
|
||||
@description:商品海报
|
||||
@update: 2024-01-17 17:38
|
||||
-->
|
||||
<script setup>
|
||||
import { getCurrentInstance, nextTick, ref } from "vue";
|
||||
import { useImage } from "@/hooks/useImage";
|
||||
import { useMainStore } from "@/store/store";
|
||||
import { generateMiniProgramQrCode } from "@/api/global";
|
||||
|
||||
// =================================== hooks ============================================
|
||||
const mainStore = useMainStore();
|
||||
const {getImageInfo, base64ToUrl, saveImageToPhotosAlbum} = useImage();
|
||||
|
||||
|
||||
const show = ref(false)
|
||||
|
||||
const goods = ref(undefined)
|
||||
|
||||
const miniProgramQrCode = ref('')
|
||||
|
||||
/**
|
||||
* 打开分享弹窗
|
||||
* @param good 商品信息
|
||||
* @param shareInfo
|
||||
*/
|
||||
async function open(good, shareInfo) {
|
||||
goods.value = good
|
||||
show.value = true
|
||||
// 生成小程序二维码
|
||||
uni.showLoading({title: '获取数据中...'})
|
||||
miniProgramQrCode.value = await generateMiniProgramQrCode({path: `pages/share/index`, name: shareInfo.query});
|
||||
await nextTick(() => {
|
||||
draw()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭分享弹窗
|
||||
*/
|
||||
function close() {
|
||||
goods.value = posterImagePath.value = undefined
|
||||
show.value = false
|
||||
}
|
||||
|
||||
|
||||
defineExpose({open, close})
|
||||
// ======================= 画布 =========================================
|
||||
const posterImagePath = ref(null) // 海报图片路径
|
||||
const _this = getCurrentInstance() // 当前组件实例
|
||||
const canvasRef = ref() // 画布ref
|
||||
const ctx = uni.createCanvasContext('goods-canvas', _this) // 画笔对象
|
||||
|
||||
/**
|
||||
* 绘制画布
|
||||
*/
|
||||
function draw() {
|
||||
uni.showLoading({title: '海报生成中'})
|
||||
const selectorQuery = uni.createSelectorQuery().in(_this);
|
||||
selectorQuery.select('#goods-canvas').boundingClientRect().exec(async res => {
|
||||
try {
|
||||
const {width, height} = res[0] //px
|
||||
// 绘制背景
|
||||
drawRoundedRectangle({
|
||||
width,
|
||||
height,
|
||||
round: 10
|
||||
})
|
||||
// 绘制header
|
||||
await drawUserInfo()
|
||||
// 绘制商品信息
|
||||
await drawGoodsInfo(width)
|
||||
// 绘制小程序分享信息
|
||||
await drawShareInfo(width)
|
||||
console.log('画布绘画完毕')
|
||||
ctx.draw(false, () => {
|
||||
generatePoster()
|
||||
});
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
console.dir(e)
|
||||
throw new Error(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制用户信息
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function drawUserInfo() {
|
||||
// 用户头像
|
||||
await drawImage({x: 15, y: 15, width: 32, height: 32, src: mainStore.user.avatar})
|
||||
ctx.save()
|
||||
ctx.font = 'normal 16px sans-serif';
|
||||
ctx.fillStyle = '#000000'
|
||||
ctx.translate(68, 32)
|
||||
// y 要设置成字体大小的一半
|
||||
ctx.fillText(mainStore.user.nickname + "邀请您来拼团啦~", 0, 8)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制商品信息
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function drawGoodsInfo(width) {
|
||||
// 图片
|
||||
const imageHeight = width - 24 * 2 // 24是UI稿单边边距
|
||||
const src = goods.value.image
|
||||
await drawImage({x: 24, y: 72, width: imageHeight, height: imageHeight, src})
|
||||
// 商品名称
|
||||
const goodsName = goods.value.storeName
|
||||
// 商品价格
|
||||
const goodsPrice = goods.value.price
|
||||
// 商品划线价格
|
||||
const goodsOldPrice = "快来和我拼团"
|
||||
|
||||
const sliceName = ctx.measureText(goodsName) >= width / 1.5 ? goodsName : goodsName.slice(0, 17) + '...'
|
||||
/** 商品名称 **/
|
||||
ctx.save()
|
||||
ctx.translate(24, width + 58)
|
||||
ctx.font = 'normal bold 16px sans-serif';
|
||||
ctx.fillStyle = '#000000'
|
||||
ctx.fillText(sliceName, 0, 0)
|
||||
ctx.restore()
|
||||
/** 商品原始价格 **/
|
||||
ctx.save()
|
||||
ctx.translate(24, width + 58 + 36)
|
||||
ctx.font = 'normal bold 24px sans-serif';
|
||||
ctx.fillStyle = '#EE6D46'
|
||||
ctx.fillText(`¥${ goodsPrice }`, 0, 0)
|
||||
// 删除线价格
|
||||
const goodsPriceTextMetrics = ctx.measureText(`¥${ goodsPrice }`); // 商品价格宽度
|
||||
ctx.font = 'normal normal 16px sans-serif';
|
||||
ctx.fillStyle = '#999999'
|
||||
ctx.fillText(`${ goodsOldPrice }`, goodsPriceTextMetrics.width + 10, 0)
|
||||
// const goodsOldPriceTextMetrics = ctx.measureText(`${ goodsOldPrice }`); // 商品删除价格宽度
|
||||
// ctx.fillRect(goodsPriceTextMetrics.width + 10, -8, goodsOldPriceTextMetrics.width + 5, 1.5)
|
||||
ctx.restore()
|
||||
/** 分割线 */
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = '#F0F0F0'
|
||||
ctx.translate(24, width + 58 + 36 + 15)
|
||||
ctx.moveTo(0, 0)
|
||||
ctx.lineWidth = 1
|
||||
ctx.lineTo(imageHeight, 0)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制分享信息
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function drawShareInfo(width) {
|
||||
ctx.save();
|
||||
ctx.translate(24, width + 58 + 36 + 15 + 25)
|
||||
const text = "长按识别图中二维码"
|
||||
const subText = "来自「Yshop商城」小程序"
|
||||
ctx.font = 'normal bold 16px sans-serif';
|
||||
ctx.fillStyle = '#000000'
|
||||
// 主要字体
|
||||
ctx.fillText(text, 0, 0)
|
||||
ctx.font = 'normal normal 14px sans-serif';
|
||||
ctx.fillStyle = '#8C8C8C'
|
||||
ctx.fillText(subText, 0, 25)
|
||||
/** 二维码 **/
|
||||
const path = await base64ToUrl(miniProgramQrCode.value);
|
||||
await drawImage({
|
||||
x: width - (24 * 2) - 50,
|
||||
y: -18,
|
||||
width: 50,
|
||||
height: 50,
|
||||
src: path
|
||||
})
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绘制圆角矩形
|
||||
* @param options
|
||||
*/
|
||||
function drawRoundedRectangle(options = {}) {
|
||||
ctx.save()
|
||||
const defaultOptions = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
round: 0,
|
||||
stroke: false,
|
||||
strokeStyle: 'rgb(255,255,255)',
|
||||
fillStyle: 'rgb(255,255,255)',
|
||||
}
|
||||
options = {...defaultOptions, ...options}
|
||||
const {x, y, width, height, round, stroke, strokeStyle, fillStyle} = options
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + round, y);
|
||||
ctx.arcTo(x + width, y, x + width, y + round, round);
|
||||
ctx.lineTo(x + width, y + height - round);
|
||||
ctx.arcTo(x + width, y + height, x + width - round, y + height, round);
|
||||
ctx.lineTo(x + round, y + height);
|
||||
ctx.arcTo(x, y + height, x, y + height - round, round);
|
||||
ctx.lineTo(x, y + round);
|
||||
ctx.arcTo(x, y, x + round, y, round);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.strokeStyle = strokeStyle;
|
||||
ctx.fill();
|
||||
stroke && ctx.stroke()
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制图片
|
||||
* @param options
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function drawImage(options = {}) {
|
||||
const defaultOptions = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
src: ''
|
||||
}
|
||||
options = {...defaultOptions, ...options}
|
||||
// 获取图片信息
|
||||
const {path} = await getImageInfo(options.src);
|
||||
ctx.drawImage(path, options.x, options.y, options.width, options.height)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成画布
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function generatePoster() {
|
||||
console.log("开始生成画布")
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: 'goods-canvas',
|
||||
success: async (res) => {
|
||||
// 在H5平台下,tempFilePath 为 base64
|
||||
// #ifdef H5
|
||||
posterImagePath.value = await base64ToUrl(res.tempFilePath);
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
posterImagePath.value = res.tempFilePath;
|
||||
console.log(posterImagePath.value, res)
|
||||
// #endif
|
||||
uni.hideLoading()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err)
|
||||
uni.hideLoading()
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.canvasToTempFilePath({
|
||||
canvasId: 'goods-canvas',
|
||||
canvas:canvasRef.value,//这里是重点,获取实例的时候保存为全局变量就行了
|
||||
success: async (res) => {
|
||||
posterImagePath.value = res.tempFilePath;
|
||||
uni.hideLoading()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err)
|
||||
uni.hideLoading()
|
||||
}
|
||||
},_this)
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存到相册
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function save() {
|
||||
uni.showLoading({title: '保存中'})
|
||||
try {
|
||||
await saveImageToPhotosAlbum(posterImagePath.value, goods.value.storeName)
|
||||
close()
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<u-overlay
|
||||
@touchmove="(e)=>{e.preventDefault();}"
|
||||
:show="show"
|
||||
@click="close">
|
||||
<view
|
||||
class="poster"
|
||||
v-if="goods && posterImagePath">
|
||||
<!-- 海报 -->
|
||||
<view
|
||||
class="poster-image"
|
||||
@click.stop>
|
||||
<image :src="posterImagePath" />
|
||||
</view>
|
||||
<!-- 按钮组合 -->
|
||||
<view class="button-group">
|
||||
<view
|
||||
class="button line-button"
|
||||
@click.stop="close">
|
||||
取消
|
||||
</view>
|
||||
<view
|
||||
class="button animation-button"
|
||||
@click.stop="save">
|
||||
保存
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<canvas
|
||||
canvas-id="goods-canvas"
|
||||
ref="canvasRef"
|
||||
style="width:654rpx;height: 1032rpx;margin: 20rpx auto;position: absolute;z-index:999;top: -999px;"
|
||||
id="goods-canvas" />
|
||||
|
||||
</u-overlay>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss">
|
||||
.poster {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.poster-image {
|
||||
width: 90%;
|
||||
aspect-ratio: 327 / 516;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
width: 90%;
|
||||
margin-top: 40rpx;
|
||||
@include useFlex(center, center, row, nowrap, 20rpx);
|
||||
|
||||
.button {
|
||||
flex-grow: 1;
|
||||
@include useFlex(center, center);
|
||||
height: 80rpx;
|
||||
background: $primary-color;
|
||||
color: $white-color;
|
||||
border-radius: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.line-button {
|
||||
border: 4rpx solid $white-color;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user