Files
quyun/frontend/wechat/src/views/ArticleDetail.vue
2025-04-30 18:50:11 +08:00

225 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import Plyr from "plyr";
import "plyr/dist/plyr.css";
import { onMounted, onUnmounted, ref } from "vue";
import { BsChevronLeft } from "vue-icons-plus/bs";
import { useRoute, useRouter } from "vue-router";
import { postApi } from "../api/postApi";
import { wechatApi } from "../api/wechatApi";
import { useWxSDK } from "../hooks/useWxSDK";
const wx = useWxSDK();
const route = useRoute();
const router = useRouter();
const article = ref(null);
const buying = ref(false);
const player = ref(null);
const videoElement = ref(null);
const mediaLoaded = ref(false);
const initializePlayer = () => {
if (videoElement.value) {
player.value = new Plyr(videoElement.value, {
controls: [
"play",
"progress",
"current-time",
"duration",
"settings",
"fullscreen",
],
settings: ["speed"],
speed: { selected: 1, options: [0.5, 0.75, 1] },
autoplay: false,
});
player.value.on("play", async () => {
if (!mediaLoaded.value) {
await loadVideoSource();
}
});
player.value.on("ended", () => {
mediaLoaded.value = false;
videoElement.value.src = "";
});
}
};
const loadVideoSource = async () => {
try {
const { data } = await postApi.play(route.params.id);
if (videoElement.value) {
mediaLoaded.value = true;
videoElement.value.src = data.url;
await player.value.restart();
await player.value.play();
}
} catch (error) {
console.error("Failed to load video:", error);
alert("视频加载失败: " + error.response.data);
await player.value.stop();
}
};
const handleBuy = async () => {
if (buying.value) return;
buying.value = true;
try {
const response = await postApi.buy(article.value.id);
const payData = response.data;
// 调用微信支付
window.WeixinJSBridge.invoke(
"getBrandWCPayRequest",
{
...payData,
},
function (res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
// 支付成功,刷新文章数据
fetchArticle();
} else {
// 支付失败或取消
console.error("Payment failed:", res.err_msg);
alert(
"支付失败:" +
(res.err_msg === "get_brand_wcpay_request:cancel"
? "支付已取消"
: "支付异常")
);
}
}
);
} catch (error) {
console.error("Failed to initiate payment:", error);
alert("发起支付失败,请稍后重试");
} finally {
buying.value = false;
}
};
const fetchArticle = async () => {
try {
const { id } = route.params;
const { data } = await postApi.show(id);
article.value = data;
document.title = article.value.title;
// 调用微信 JS SDK 分享接口
wx.setShareInfo({
title: article.value.title,
desc: article.value.content,
link: window.location.href,
imgUrl: data.head_images[0],
});
} catch (error) {
console.error("Failed to fetch article:", error);
alert("加载失败!");
}
};
const handleBack = () => {
router.back();
};
onMounted(async () => {
wechatApi
.jsSdk()
.then((resp) => {
wx.initConfig(resp.data).then(() => {
wx.setShareInfo({
title: article.value.title,
desc: article.value.content,
link: window.location.href,
imgUrl: article.value.head_images[0],
});
});
})
.catch((error) => {
console.error("Failed to initialize WeChat SDK:", error);
})
.finally(() => {
});
await fetchArticle();
initializePlayer();
});
onUnmounted(() => {
if (player.value) {
player.value.destroy();
}
});
</script>
<template>
<div class="min-h-screen bg-gray-50 flex flex-col">
<div class="bg-white h-12 flex items-center px-4 border-b border-gray-100">
<button @click="handleBack" class="text-gray-700">
<BsChevronLeft class="w-6 h-6" />
</button>
<template v-if="article">
<h1 class="text-lg font-semibold text-gray-800 ml-4">
{{ article.title }}
</h1>
</template>
</div>
<div class="flex-1">
<div v-if="article">
<video webkit-playsinline playsinline ref="videoElement" :poster="article.head_images[0]"
class="w-full aspect-video object-cover plyr-video" preload="none">
<source type="video/mp4" />
Your browser does not support the video tag.
</video>
<div v-if="article && !article.bought" class="bg-white border-b border-gray-200">
<div class="text-sm bg-orange-500 text-white px-4 py-2">
注意未购买视频仅可预览 1 分钟购买后可观看全集
</div>
<div class="flex items-center justify-between max-w-md mx-auto p-4">
<div class="text-orange-600 text-2xl">
<span class="mr-2 text-xl">&yen;</span>
<span class="font-bold font-mono">
{{ (article.price / 100).toFixed(2) }}
</span>
</div>
<button @click="handleBuy" :disabled="buying"
class="bg-orange-600 text-white px-8 py-2 rounded hover:bg-orange-500 active:bg-orange-600 transition-colors disabled:opacity-50">
<span v-if="buying">处理中...</span>
<span v-else>立即购买</span>
</button>
</div>
</div>
<div class="p-4">
<h1 class="text-2xl my-1.5 break-all">{{ article.title }}</h1>
<div class="py-2 flex items-center gap-2">
<span v-for="tag in article.tags" :key="tag"
class="px-1.5 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">
<span class="mr-1">#</span>{{ tag }}
</span>
</div>
<p class="text-gray-600">{{ article.content }}</p>
</div>
</div>
<div v-else class="flex justify-center items-center py-8">
<div class="animate-spin rounded-full h-8 w-8 border-4 border-gray-200 border-t-blue-600"></div>
</div>
</div>
</div>
</template>
<style>
.plyr-video {
--plyr-color-main: #2563eb;
width: 100%;
max-height: 100vh;
}
</style>