Trong phát triển ứng dụng thương mại điện tử, yêu cầu tạo ảnh poster chia sẻ sản phẩm kèm thông tin người giới thiệu và mã QR là rất phổ biến. Giải pháp này yêu cầu frontend kết hợp dữ liệu từ backend, tự động sinh mã QR từ link sản phẩm, sau đó ghép nối hình ảnh và tải về dưới dạng file ảnh.
Để thực hiện, chúng ta cần tích hợp ba thư viện chính: qrcodejs2 để tạo mã QR, html2canvas để chuyển đổi DOM thành canvas, và file-saver để xử lý việc tải file.
1. Thiết kế Giao diện Popup
Sử dụng một component popup để hiển thị preview. Cấu trúc bao gồm vùng hiển thị ảnh nền, thông tin user và vùng chứa QR code. Cần định nghĩa rõ các ref để thư viện có thể truy xuất phần tử DOM.
<template>
<u-popup
v-model="dialogVisible"
mode="center"
:width="650"
:height="80%"
@close="handleClose"
:closeable="true"
border-radius="15">
<div class="poster-wrapper" id="posterContainer" ref="posterNode">
<view class="preview-section" v-if="isGenerated">
<image :src="finalImage" class="result-img" />
<view class="action-btn" @tap="downloadFile">
<image src="/static/icons/save.png" />
<text>Lưu ảnh</text>
</view>
</view>
<view class="editing-section" v-else>
<view class="bg-layer"><image :src="backgroundSrc" /></view>
<view class="info-layer">
<view class="user-profile">
<view class="avatar"> <image :src="userAvatar" /></view>
<view class="details">
<view class="name-role">
{{ userProfile.nickname }}
<text> Tư vấn viên</text>
</view>
<view class="contact">{{ userProfile.phone }}</view>
</view>
</view>
<view class="qr-container" id="qrTarget" ref="qrBox">
<div id="qrCanvas" ref="qrElement"></div>
</view>
</view>
</view>
</div>
</u-popup>
</template>
2. Xử lý Logic Sinh Poster
Quy trình xử lý bao gồm: lấy danh sách poster, khi chọn item sẽ kích hoạt popup, sinh mã QR dựa trên URL sản phẩm, đợi DOM render xong rồi dùng html2canvas chụp lại vùng chứa và chuyển thành base64.
<script>
export default {
data() {
return {
dialogVisible: false,
marketingData: [],
backgroundSrc: '',
productLink: '',
isGenerated: false,
finalImage: '',
userProfile: { nickname: '', phone: '', avatar: '' },
renderTimer: null
};
},
methods: {
async fetchMarketingMaterials() {
const res = await getListByParentCodes({ parentCodes: ["poster"] });
this.marketingData = res.data.data.poster;
},
onPosterSelected(item) {
this.dialogVisible = true;
this.backgroundSrc = this.localHostImg;
this.productLink = this.baseUrl + '#' + item.url;
setStorageSync('poster_share_url', this.productLink);
this.initQRCode(this.productLink);
setTimeout(() => {
this.captureCanvas();
uni.showLoading({ title: 'Đang xử lý ảnh' });
}, 300);
},
initQRCode(link) {
const target = this.$refs.qrElement;
if (!target) {
this.renderTimer = setTimeout(() => {
this.initQRCode(link);
}, 300);
return;
}
this.$nextTick(() => {
new QRCode(this.$refs.qrElement, {
width: 70,
height: 70,
text: link,
correctLevel: QRCode.CorrectLevel.L,
render: "canvas",
});
});
},
captureCanvas() {
html2canvas(this.$refs.posterNode, {
scrollY: 0,
scrollX: 0,
useCORS: true
}).then(canvas => {
uni.hideLoading();
const base64Data = canvas.toDataURL("image/png").split(",")[1];
this.finalImage = "data:image/png;base64," + base64Data;
this.isGenerated = true;
});
},
downloadFile() {
if (!this.finalImage) {
uni.$showMsg('Vui lòng đợi ảnh hoàn thành');
return;
}
saveAs(this.finalImage, 'Poster_Chia_Se');
},
handleClose() {
this.isGenerated = false;
this.clearTimers();
},
clearTimers() {
if (this.renderTimer) {
clearTimeout(this.renderTimer);
}
}
},
beforeDestroy() {
this.clearTimers();
}
};
</script>