339 lines
10 KiB
Vue
339 lines
10 KiB
Vue
|
|
<template>
|
|||
|
|
<van-popup v-model:show="show" round position="bottom">
|
|||
|
|
<div class="max-h-[calc(100vh-100px)] m-4">
|
|||
|
|
<div class="p-4">
|
|||
|
|
<van-swipe
|
|||
|
|
class="poster-swiper rounded-xl shadow"
|
|||
|
|
indicator-color="white"
|
|||
|
|
@change="onSwipeChange"
|
|||
|
|
>
|
|||
|
|
<van-swipe-item
|
|||
|
|
v-for="(_, index) in posterImages"
|
|||
|
|
:key="index"
|
|||
|
|
>
|
|||
|
|
<canvas
|
|||
|
|
:ref="(el) => (posterCanvasRefs[index] = el)"
|
|||
|
|
class="poster-canvas rounded-xl h-[800px] m-auto"
|
|||
|
|
></canvas>
|
|||
|
|
</van-swipe-item>
|
|||
|
|
</van-swipe>
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
v-if="mode === 'promote'"
|
|||
|
|
class="swipe-tip text-center text-gray-700 text-sm mb-2"
|
|||
|
|
>
|
|||
|
|
<span class="swipe-icon">←</span> 左右滑动切换海报
|
|||
|
|
<span class="swipe-icon">→</span>
|
|||
|
|
</div>
|
|||
|
|
<van-divider>分享到好友</van-divider>
|
|||
|
|
|
|||
|
|
<div class="flex items-center justify-around">
|
|||
|
|
<div
|
|||
|
|
class="flex flex-col items-center justify-center"
|
|||
|
|
@click="savePoster"
|
|||
|
|
>
|
|||
|
|
<img
|
|||
|
|
src="@/assets/images/icon_share_img.svg"
|
|||
|
|
class="w-10 h-10 rounded-full"
|
|||
|
|
/>
|
|||
|
|
<div class="text-center mt-1 text-gray-600 text-xs">
|
|||
|
|
保存图片
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
class="flex flex-col items-center justify-center"
|
|||
|
|
@click="copyUrl"
|
|||
|
|
>
|
|||
|
|
<img
|
|||
|
|
src="@/assets/images/icon_share_url.svg"
|
|||
|
|
class="w-10 h-10 rounded-full"
|
|||
|
|
/>
|
|||
|
|
<div class="text-center mt-1 text-gray-600 text-xs">
|
|||
|
|
复制链接
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</van-popup>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, watch, nextTick, computed, onMounted, toRefs } from "vue";
|
|||
|
|
import QRCode from "qrcode";
|
|||
|
|
import { showToast } from "vant";
|
|||
|
|
|
|||
|
|
const props = defineProps({
|
|||
|
|
linkIdentifier: {
|
|||
|
|
type: String,
|
|||
|
|
required: true,
|
|||
|
|
},
|
|||
|
|
mode: {
|
|||
|
|
type: String,
|
|||
|
|
default: "promote", // 例如 "promote" | "invitation"
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
const { linkIdentifier, mode } = toRefs(props);
|
|||
|
|
const posterCanvasRefs = ref([]); // 用于绘制海报的canvas数组
|
|||
|
|
const currentIndex = ref(0); // 当前显示的海报索引
|
|||
|
|
const postersGenerated = ref([]); // 标记海报是否已经生成过,将在onMounted中初始化
|
|||
|
|
const show = defineModel("show");
|
|||
|
|
const url = computed(() => {
|
|||
|
|
const baseUrl = window.location.origin; // 获取当前站点的域名
|
|||
|
|
return mode.value === "promote"
|
|||
|
|
? `${baseUrl}/agent/promotionInquire/` // 使用动态的域名
|
|||
|
|
: `${baseUrl}/agent/invitationAgentApply/`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 海报图片数组
|
|||
|
|
const posterImages = ref([]);
|
|||
|
|
|
|||
|
|
// QR码位置配置(为每个海报单独配置)
|
|||
|
|
const qrCodePositions = ref({
|
|||
|
|
// promote模式的配置 (tg_qrcode)
|
|||
|
|
promote: [
|
|||
|
|
{ x: 180, y: 1440, size: 300 }, // tg_qrcode_1.png
|
|||
|
|
{ x: 525, y: 1955, size: 500 }, // tg_qrcode_2.jpg
|
|||
|
|
{ x: 525, y: 1955, size: 500 }, // tg_qrcode_3.jpg
|
|||
|
|
{ x: 525, y: 1955, size: 500 }, // tg_qrcode_4.jpg
|
|||
|
|
{ x: 525, y: 1955, size: 500 }, // tg_qrcode_5.jpg
|
|||
|
|
{ x: 525, y: 1955, size: 500 }, // tg_qrcode_6.jpg
|
|||
|
|
{ x: 255, y: 940, size: 250 }, // tg_qrcode_7.jpg
|
|||
|
|
{ x: 255, y: 940, size: 250 }, // tg_qrcode_8.jpg
|
|||
|
|
],
|
|||
|
|
// invitation模式的配置 (yq_qrcode)
|
|||
|
|
invitation: [
|
|||
|
|
{ x: 360, y: -1370, size: 360 }, // yq_qrcode_1.png
|
|||
|
|
],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 处理轮播图切换事件
|
|||
|
|
const onSwipeChange = (index) => {
|
|||
|
|
currentIndex.value = index;
|
|||
|
|
if (!postersGenerated.value[index]) {
|
|||
|
|
generatePoster(index);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 加载海报图片
|
|||
|
|
const loadPosterImages = async () => {
|
|||
|
|
const images = [];
|
|||
|
|
const basePrefix = mode.value === "promote" ? "tg_qrcode_" : "yq_qrcode_";
|
|||
|
|
|
|||
|
|
// 根据模式确定要加载的图片数量
|
|||
|
|
const imageCount = mode.value === "promote" ? 8 : 1;
|
|||
|
|
|
|||
|
|
// 加载图片
|
|||
|
|
for (let i = 1; i <= imageCount; i++) {
|
|||
|
|
// 尝试加载 .png 文件
|
|||
|
|
try {
|
|||
|
|
const module = await import(
|
|||
|
|
`@/assets/images/${basePrefix}${i}.png`
|
|||
|
|
);
|
|||
|
|
images.push(module.default);
|
|||
|
|
continue; // 如果成功加载了 png,则跳过后续的 jpg 尝试
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(
|
|||
|
|
`Image ${basePrefix}${i}.png not found, trying jpg...`
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果 .png 不存在,尝试加载 .jpg 文件
|
|||
|
|
try {
|
|||
|
|
const module = await import(
|
|||
|
|
`@/assets/images/${basePrefix}${i}.jpg`
|
|||
|
|
);
|
|||
|
|
images.push(module.default);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn(
|
|||
|
|
`Image ${basePrefix}${i}.jpg not found either, using fallback.`
|
|||
|
|
);
|
|||
|
|
if (i === 1) {
|
|||
|
|
// 如果第一张也不存在,创建一个空白图片
|
|||
|
|
const emptyImg = new Image();
|
|||
|
|
emptyImg.width = 600;
|
|||
|
|
emptyImg.height = 800;
|
|||
|
|
images.push(emptyImg.src);
|
|||
|
|
} else if (images.length > 0) {
|
|||
|
|
images.push(images[0]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return images;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
onMounted(async () => {
|
|||
|
|
posterImages.value = await loadPosterImages();
|
|||
|
|
// 根据加载的图片数量初始化postersGenerated数组
|
|||
|
|
postersGenerated.value = Array(posterImages.value.length).fill(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 生成海报并合成二维码
|
|||
|
|
const generatePoster = async (index) => {
|
|||
|
|
// 如果已经生成过海报,就直接返回
|
|||
|
|
if (postersGenerated.value[index]) return;
|
|||
|
|
|
|||
|
|
// 确保 DOM 已经渲染完成
|
|||
|
|
await nextTick();
|
|||
|
|
|
|||
|
|
const canvas = posterCanvasRefs.value[index];
|
|||
|
|
if (!canvas) return; // 如果 canvas 元素为空则直接返回
|
|||
|
|
|
|||
|
|
const ctx = canvas.getContext("2d");
|
|||
|
|
|
|||
|
|
// 1. 加载海报图片
|
|||
|
|
const posterImg = new Image();
|
|||
|
|
posterImg.src = posterImages.value[index];
|
|||
|
|
|
|||
|
|
posterImg.onload = () => {
|
|||
|
|
// 设置 canvas 尺寸与海报图一致
|
|||
|
|
canvas.width = posterImg.width;
|
|||
|
|
canvas.height = posterImg.height;
|
|||
|
|
|
|||
|
|
// 2. 绘制海报图片
|
|||
|
|
ctx.drawImage(posterImg, 0, 0);
|
|||
|
|
|
|||
|
|
// 3. 生成二维码
|
|||
|
|
QRCode.toDataURL(
|
|||
|
|
generalUrl(),
|
|||
|
|
{ width: 150, margin: 0 },
|
|||
|
|
(err, qrCodeUrl) => {
|
|||
|
|
if (err) {
|
|||
|
|
console.error(err);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 加载二维码图片
|
|||
|
|
const qrCodeImg = new Image();
|
|||
|
|
qrCodeImg.src = qrCodeUrl;
|
|||
|
|
qrCodeImg.onload = () => {
|
|||
|
|
// 获取当前海报的二维码位置配置
|
|||
|
|
const positions = qrCodePositions.value[mode.value];
|
|||
|
|
const position = positions[index] || positions[0]; // 如果没有对应索引的配置,则使用第一个配置
|
|||
|
|
|
|||
|
|
// 计算Y坐标(负值表示从底部算起的位置)
|
|||
|
|
const qrY =
|
|||
|
|
position.y < 0
|
|||
|
|
? posterImg.height + position.y
|
|||
|
|
: position.y;
|
|||
|
|
|
|||
|
|
// 绘制二维码
|
|||
|
|
ctx.drawImage(
|
|||
|
|
qrCodeImg,
|
|||
|
|
position.x,
|
|||
|
|
qrY,
|
|||
|
|
position.size,
|
|||
|
|
position.size
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 标记海报已生成
|
|||
|
|
postersGenerated.value[index] = true;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 监听 show 变化,show 为 true 时生成海报
|
|||
|
|
watch(show, (newVal) => {
|
|||
|
|
if (newVal && !postersGenerated.value[currentIndex.value]) {
|
|||
|
|
generatePoster(currentIndex.value); // 当弹窗显示且当前海报未生成时生成海报
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 分享到微信
|
|||
|
|
const toPromote = () => {
|
|||
|
|
// 这里可以实现微信分享的功能,比如调用微信JS-SDK等
|
|||
|
|
console.log("分享到微信好友");
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存海报图片
|
|||
|
|
const savePoster = () => {
|
|||
|
|
const canvas = posterCanvasRefs.value[currentIndex.value];
|
|||
|
|
const dataURL = canvas.toDataURL("image/png"); // 获取 canvas 内容为图片
|
|||
|
|
const a = document.createElement("a");
|
|||
|
|
a.href = dataURL;
|
|||
|
|
a.download = "天远数据查询.png";
|
|||
|
|
a.click();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const generalUrl = () => {
|
|||
|
|
return url.value + encodeURIComponent(linkIdentifier.value);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const copyUrl = () => {
|
|||
|
|
copyToClipboard(generalUrl());
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 复制链接
|
|||
|
|
const copyToClipboard = (text) => {
|
|||
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|||
|
|
// 支持 Clipboard API
|
|||
|
|
navigator.clipboard
|
|||
|
|
.writeText(text)
|
|||
|
|
.then(() => {
|
|||
|
|
showToast({ message: "链接已复制!" });
|
|||
|
|
})
|
|||
|
|
.catch((err) => {
|
|||
|
|
console.error("复制失败:", err);
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// 对于不支持 Clipboard API 的浏览器,使用 fallback 方法
|
|||
|
|
const textArea = document.createElement("textarea");
|
|||
|
|
textArea.value = text;
|
|||
|
|
document.body.appendChild(textArea);
|
|||
|
|
textArea.select();
|
|||
|
|
try {
|
|||
|
|
document.execCommand("copy");
|
|||
|
|
showToast({ message: "链接已复制!" });
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error("复制失败:", err);
|
|||
|
|
} finally {
|
|||
|
|
document.body.removeChild(textArea);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
.poster-swiper {
|
|||
|
|
height: 500px;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.poster-canvas {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
object-fit: contain;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.swipe-tip {
|
|||
|
|
animation: fadeInOut 2s infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.swipe-icon {
|
|||
|
|
display: inline-block;
|
|||
|
|
animation: slideLeftRight 1.5s infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes fadeInOut {
|
|||
|
|
0%,
|
|||
|
|
100% {
|
|||
|
|
opacity: 0.5;
|
|||
|
|
}
|
|||
|
|
50% {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes slideLeftRight {
|
|||
|
|
0%,
|
|||
|
|
100% {
|
|||
|
|
transform: translateX(0);
|
|||
|
|
}
|
|||
|
|
50% {
|
|||
|
|
transform: translateX(5px);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|