Files
2025-04-30 14:04:34 +08:00

549 lines
16 KiB
Vue

<template>
<view>
<fu-popup v-model="sharePopup" mode="bottom" :safe-area-inset-bottom="true" :mask-close-able="true" border-radius="24">
<view class="wrap">
<view class="flex">
<view class="flex-sub text-center">{{ $t('分享到') }}</view>
<view class="cuIcon-close" @tap.stop="close"></view>
</view>
<view class="padding-tb">
<!-- APP平台 start -->
<!-- #ifdef APP-PLUS -->
<view class="grid col-4">
<block v-for="(item, index) in shareList" :key="index">
<view v-if="provider.includes(item.type)" class="height-200 flex flex-direction align-center justify-center" @tap.stop="share(item)">
<image class="block-85" :src="item.icon" mode="aspectFit"></image>
<text class="margin-top-xs text-sm">{{ item.title }}</text>
</view>
</block>
</view>
<!-- #endif -->
<!-- APP平台 end -->
<!-- 微信小程序平台 start -->
<!-- #ifdef MP-WEIXIN -->
<view class="grid col-2">
<view>
<button class="height-200 flex flex-direction align-center justify-center cu-btn bg-white" open-type="share">
<image class="block-85" :src="require('./static/weixin.png')" mode="aspectFit"></image>
<text class="margin-top-xs text-sm">{{ $t('微信好友') }}</text>
</button>
</view>
<view>
<button class="height-200 flex flex-direction align-center justify-center cu-btn bg-white" @tap.stop="createPoster">
<image class="block-85" :src="require('./static/poster.png')" mode="aspectFit"></image>
<text class="margin-top-xs text-sm">{{ $t('二维码海报') }}</text>
</button>
</view>
</view>
<!-- #endif -->
<!-- 微信小程序平台 end -->
<!-- H5平台 staart -->
<!-- #ifdef H5 -->
<view class="grid col-3">
<view>
<button class="height-200 flex flex-direction align-center justify-center cu-btn bg-white" @tap.stop="createPoster">
<image class="block-85" :src="require('./static/poster.png')" mode="aspectFit"></image>
<text class="margin-top-xs text-sm">{{ $t('二维码海报') }}</text>
</button>
</view>
</view>
<!-- #endif -->
<!-- H5平台 end -->
</view>
</view>
</fu-popup>
<fu-popup v-model="posterPopup" mode="bottom" :safe-area-inset-bottom="true" :mask-close-able="true" border-radius="24">
<view class="wrap">
<view class="flex align-center justify-between">
<view class="text-df text-333">{{ $t('保存到相册') }}</view>
<view class="cuIcon-close text-333" @click.stop="close"></view>
</view>
<view class="margin-top flex justify-center align-center">
<image class="preview-image" :src="previewImageUrl" mode="aspectFill" @click="previewImage(previewImageUrl)"></image>
</view>
<!-- #ifndef H5 -->
<view class="margin-top" @click="saveImage">
<button class="action2 cu-btn block round">{{ $t('保存图片') }}</button>
</view>
<!-- #endif -->
</view>
</fu-popup>
<view class="offset-canvas">
<canvas canvas-id="poster-canvas" style="width: 375px; height: 550px"></canvas>
</view>
</view>
</template>
<script>
/**
* @alias 刘欢
* @description 常规分享组件,提供分享到微信,微信朋友圈,QQ,QQ空间,分享海报等功能
* @param { Boollean } isShareToMp 是否分享到小程序
* */
const providers = [
{
title: global.i18n.t('微信好友'),
icon: require('./static/weixin.png'),
type: 'weixin',
scene: 'WXSceneSession',
},
{
title: global.i18n.t('朋友圈'),
icon: require('./static/pengyouquan.png'),
type: 'weixin',
scene: 'WXSenceTimeline',
},
{
title: global.i18n.t('QQ好友'),
icon: require('./static/qq.png'),
type: 'qq',
},
{
title: global.i18n.t('QQ空间'),
icon: require('./static/kongjian.png'),
type: 'qq',
},
{
title: global.i18n.t('新浪微博'),
icon: require('./static/weibo.png'),
type: 'sinaweibo',
},
{
title: global.i18n.t('复制链接'),
icon: require('./static/copy.png'),
type: 'link',
},
{
title: global.i18n.t('二维码海报'),
icon: require('./static/poster.png'),
type: 'image',
},
]
export default {
props: {
info: Object,
isShareToMp: {
type: Boolean,
default: false,
},
},
data() {
return {
ctx: null,
previewImageUrl: '',
shareList: [
{
title: global.i18n.t('微信好友'),
icon: require('./static/weixin.png'),
type: 'weixin',
scene: 'WXSceneSession',
},
{
title: global.i18n.t('朋友圈'),
icon: require('./static/pengyouquan.png'),
type: 'weixin',
scene: 'WXSenceTimeline',
},
{
title: global.i18n.t('QQ好友'),
icon: require('./static/qq.png'),
type: 'qq',
},
{
title: global.i18n.t('QQ空间'),
icon: require('./static/kongjian.png'),
type: 'qq',
},
{
title: global.i18n.t('新浪微博'),
icon: require('./static/weibo.png'),
type: 'sinaweibo',
},
{
title: global.i18n.t('复制链接'),
icon: require('./static/copy.png'),
type: 'link',
},
{
title: global.i18n.t('二维码海报'),
icon: require('./static/poster.png'),
type: 'image',
},
],
provider: [], // 已配置的服务供应商
sharePopup: false,
posterPopup: false,
}
},
methods: {
// 图片点击放大
previewImage(url) {
uni.previewImage({
current: url,
urls: [url],
})
},
// 打开弹窗
open() {
console.log(this.info, 888888)
this.sharePopup = true
},
// 关闭弹窗
close() {
this.sharePopup = false
this.posterPopup = false
},
// 初始化分享数据
init() {
this.sharePopup = false
this.posterPopup = true
this.initCanvas()
console.log('init????')
},
// 获取服务商列表
getProvider() {
uni.getProvider({
service: 'share',
success: (data) => {
console.log('已配置的分享服务', data)
this.provider = [...data.provider, 'link', 'image']
},
})
},
// 分享
share(item) {
let _scene = ''
let _provider = item.type
let _type = 0
switch (_provider) {
case 'weixin':
_scene = item.scene
uni.share({
provider: _provider,
type: _type,
title: this.info.goodsName,
scene: _scene,
summary: this.info.summary,
href: this.info.href,
imageUrl: this.info.goodsImage,
success: (res) => {
this.close()
},
fail: (err) => {
console.log('分享失败', err)
this.$message.info(global.i18n.t('分享失败'))
},
})
break
case 'qq':
_type = 1
uni.share({
provider: _provider,
type: _type,
title: this.info.goodsName,
scene: _scene,
summary: this.info.summary,
href: this.info.href,
imageUrl: this.info.goodsImage,
success: (res) => {
this.close()
},
fail: (err) => {
console.log('分享失败', err)
this.$message.info(global.i18n.t('分享失败'))
},
})
break
case 'sinaweibo':
uni.share({
provider: _provider,
type: _type,
title: this.info.goodsName,
scene: _scene,
summary: this.info.summary,
href: this.info.href,
imageUrl: this.info.goodsImage,
success: (res) => {
this.close()
},
fail: (err) => {
console.log('分享失败', err)
this.$message.info(global.i18n.t('分享失败'))
},
})
break
case 'link':
uni.setClipboardData({
data: this.info.href,
success: () => {
this.$message.success(global.i18n.t('复制成功'))
this.close()
},
})
break
case 'image':
this.createPoster()
break
}
},
// 生成海报
createPoster() {
console.log('生成海报')
this.$util.actionAuth(() => {
this.$emit('getPosterInfo')
})
},
// 初始化画布
initCanvas() {
uni.showLoading({
title: global.i18n.t('海报生成中...'),
mask: true,
})
console.log('开始绘图', this.info)
this.ctx = uni.createCanvasContext('poster-canvas', this)
setTimeout(() => {
this.drawBack()
}, 200)
},
// 绘制背景
drawBack() {
let ctx = this.ctx
ctx.setFillStyle('#FFFFFF')
ctx.fillRect(0, 0, 375, 550)
ctx.draw(false, () => {
console.log('背景绘制成功')
this.drawUserInfo()
})
},
// 绘制个人信息
async drawUserInfo() {
let ctx = this.ctx
console.log(this.info)
await new Promise((resolve) => {
uni.getImageInfo({
src: this.info.avatar,
success: (res) => {
console.log('头像图片', res)
let { width, height, path } = res
if (width > height) {
let startX = (width - height) / 2
ctx.drawImage(res.path, startX, 0, height, height, 20, 20, 50, 50)
} else if (width < height) {
let startY = (height - width) / 2
ctx.drawImage(res.path, 0, startY, width, width, 20, 20, 50, 50)
} else {
ctx.drawImage(res.path, 20, 20, 50, 50)
}
resolve()
},
fail: (err) => {
uni.hideLoading()
this.$message.info(global.i18n.t('获取头像信息失败'))
console.log('获取图片信息失败', err)
},
})
})
ctx.setFillStyle('#333333')
ctx.setFontSize(18)
ctx.fillText(this.info.name, 90, 40)
ctx.setFillStyle('#999999')
ctx.setFontSize(16)
ctx.fillText(global.i18n.t('推荐一个好物给你,请查收'), 90, 65)
ctx.draw(true, () => {
console.log('个人信息绘制成功')
this.drawGoodsImage()
})
},
// 绘制商品图片
async drawGoodsImage() {
let ctx = this.ctx
await new Promise((resolve) => {
uni.getImageInfo({
src: this.info.goodsImage,
success: (res) => {
console.log('商品图片', res)
let { width, height, path } = res
if (width > height) {
let startX = (width - height) / 2
ctx.drawImage(res.path, startX, 0, height, height, 20, 90, 335, 335)
} else if (width < height) {
let startY = (height - width) / 2
ctx.drawImage(res.path, 0, startY, width, width, 20, 90, 335, 335)
} else {
ctx.drawImage(res.path, 20, 90, 335, 335)
}
resolve()
},
fail: (err) => {
uni.hideLoading()
console.log('商品图片绘制失败', err)
this.$message.info(global.i18n.t('获取商品图片信息失败'))
},
})
})
ctx.draw(true, () => {
console.log('商品图片绘制成功')
this.drawGoodsInfo()
})
},
// 绘制商品名称价格
drawGoodsInfo() {
let ctx = this.ctx
this.drawText(ctx, this.info.goodsName, 20, 455, 240, 2, '#333333', 18)
ctx.setFontSize(20)
ctx.setFillStyle('#d55034')
ctx.fillText('¥' + this.info.price, 20, 510)
ctx.draw(true, () => {
console.log('商品信息绘制成功')
this.drawCode()
// this.exportImage();
})
},
// 绘制二维码
async drawCode() {
let ctx = this.ctx
await new Promise((resolve) => {
uni.getImageInfo({
src: this.info.code,
success: (res) => {
console.log('二维码信息', res)
let { width, height, path } = res
if (width > height) {
let startX = (width - height) / 2
ctx.drawImage(res.path, startX, 0, height, height, 275, 445, 80, 80)
} else if (width < height) {
let startY = (height - width) / 2
ctx.drawImage(res.path, 0, startY, width, width, 275, 445, 80, 80)
} else {
ctx.drawImage(res.path, 275, 445, 80, 80)
}
resolve()
},
fail: (err) => {
console.log('二维码绘制失败', err)
uni.hideLoading()
this.$message.info(global.i18n.t('获取二维码信息失败'))
},
})
})
ctx.draw(true, () => {
console.log('二维码绘制成功')
this.exportImage()
})
},
// 导出图片
exportImage() {
uni.canvasToTempFilePath(
{
canvasId: 'poster-canvas',
success: (res) => {
uni.hideLoading()
console.log('导出图片成功', res)
this.previewImageUrl = res.tempFilePath
},
fail: (err) => {
uni.hideLoading()
console.log('导出图片失败', err)
},
},
this
)
},
// 保存图片
saveImage() {
uni.saveImageToPhotosAlbum({
filePath: this.previewImageUrl,
success: (res) => {
console.log(res)
this.$message.info(global.i18n.t('保存成功'))
this.close()
},
})
},
/**
* @description 绘制图片通用方法
* @param {Object} ctx - canvas上下文
* @param {String} 图片路径 - canvas上下文
* */
async drawImage(ctx, path) {},
/**
* @description 绘制文本通用方法
* @param {Object} ctx - canvas上下文
* @param {String} text - 文本
* @param {Number} startX - X轴开始位置
* @param {Number} startY - Y轴开始位置
* @param {Number} limitWidth - 文字绘制区限制宽度
* @param {Number} limitLine - 文字绘制限制行数
* @param {String} color - 文字颜色
* @param {Number} size - 字体大小
* */
drawText(ctx, text, startX, startY, limitWidth, limitLine = 2, color = '#333333', size = 18, isBold = false) {
ctx.setFillStyle(color)
ctx.setFontSize(size)
let arrText = text.split('')
let line = ''
let column = 0
for (let n = 0, len = arrText.length; n < len; n++) {
let testline = line + arrText[n]
let metrics = ctx.measureText(testline)
let testWidth = metrics.width
if (testWidth > limitWidth && n > 0 && column < limitLine) {
ctx.fillText(line, startX, startY)
column += 1
line = arrText[n]
startY += size * 1.5
} else {
line = testline
}
}
if (column < limitLine) {
ctx.fillText(line, startX, startY)
}
},
},
created() {
console.log('组件 created')
this.getProvider()
},
}
</script>
<style lang="scss" scoped>
.wrap {
padding: 30rpx;
}
.block-85 {
width: 85rpx;
height: 85rpx;
}
.height-200 {
height: 200rpx;
}
.action2 {
height: 80rpx;
background-color: #f02523;
font-size: 32rpx;
color: #ffffff;
}
.preview-image {
width: 375rpx;
height: 550rpx;
border-radius: 8rpx;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.2);
}
.offset-canvas {
position: fixed;
top: -1000px;
left: 0;
z-index: 9999;
}
</style>