ch-tgr-h5/src/views/Merchant/PayCode.vue
2026-05-21 09:09:54 +08:00

196 lines
4.6 KiB
Vue

<template>
<BasePage>
<div class="paycode-page">
<div class="paycode-wrap">
<!-- 生成后的图片 -->
<div v-if="generatedImage" class="paycode-generated">
<img :src="generatedImage" alt="收款码">
</div>
<!-- 原始元素 -->
<div v-else class="paycode-original" ref="paycodeOriginalRef">
<img src="/img/paycode.jpg" alt="" style="width: 100%; display: block;" class="bg-img" @load="onBgLoad">
<div class="user-info">
<div class="info">
<img :src="$file(shopimg)" alt="" style="border-radius: 50%;">
</div>
<div class="name">
<span>{{ shopname?.length > 8 ? shopname.slice(0, 8) + '...' : shopname }}</span>
</div>
</div>
<div class="code">
<vue-qr v-if="link" :margin="0" :text="link" backgroundColor="rgb(255,255,255)"
colorLight="rgb(255,255,255)"></vue-qr>
</div>
</div>
</div>
<div class="save-btn">
<van-button type="primary" color="#ca2904" round block :loading="loading" @click="handleSave">{{ loading ?
'生成中...' : ('保存图片')
}}</van-button>
</div>
</div>
</BasePage>
</template>
<script>
import { toDataURL } from '@/utils/html2image'
export default {
name: 'PayCode',
data() {
return {
shopname: '',
shopimg: '',
link: '',
loading: false,
generatedImage: '',
bgLoaded: false,
}
},
mounted() {
this.init()
},
methods: {
init() {
const id = this.$route.query.id
this.$get(`/v1/client/DShopsClient/${id}`).then(res => {
this.shopname = res.data.shopname
this.shopimg = res.data.shopimg
this.link = `${location.origin}${location.pathname}#/Checkout?id=${res.data.userid}`
}).catch(err => {
this.$showFailToast('加载失败')
})
},
onBgLoad() {
this.bgLoaded = true
// 背景图加载完成后自动生成图片
this.generateImage()
},
async generateImage() {
if (!this.bgLoaded || this.loading) return
this.loading = true
try {
await this.$nextTick()
await document.fonts.ready
await new Promise(resolve => setTimeout(resolve, 300))
const el = this.$refs.paycodeOriginalRef
this.generatedImage = await toDataURL(el, { format: 'png', pixelRatio: 3, useCORS: true })
} catch (e) {
this.$showFailToast('生成失败')
} finally {
this.loading = false
}
},
async handleSave() {
if (this.loading) return
// 如果已生成图片,直接下载
if (this.generatedImage) {
this.downloadByBlob(this.generatedImage, `收款码_${this.shopname}.png`)
this.$showSuccessToast('保存成功')
return
}
// 否则生成图片
await this.generateImage()
if (this.generatedImage) {
this.downloadByBlob(this.generatedImage, `收款码_${this.shopname}.png`)
this.$showSuccessToast('保存成功')
}
},
downloadByBlob(dataUrl, filename) {
const arr = dataUrl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
const blob = new Blob([u8arr], { type: mime })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.download = filename
link.href = url
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
},
},
}
</script>
<style lang="less" scoped>
.paycode-page {
// min-height: 100vh;
background: #f5f5f5;
padding-bottom: 20px;
}
.paycode-generated {
img {
width: 100%;
display: block;
}
}
.paycode-original {
position: relative;
width: 100%;
font-family: 'PingFang SC';
>img {
width: 100%;
display: block;
}
.user-info {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 32.27vw;
display: flex;
align-items: center;
}
.info {
display: flex;
align-items: center;
img {
width: 6.4vw;
height: 6.4vw;
border-radius: 50%;
}
}
.name {
margin-left: 2.67vw;
// margin-bottom: 2.4vw;
span {
font-size: 4vw;
color: #333;
}
}
.code {
position: absolute;
top: 42.4vw;
left: 23.33vw;
canvas,
img {
width: 53.33vw;
height: 53.33vw;
}
}
}
.save-btn {
margin: 20px;
}
</style>