225 lines
7.2 KiB
Vue
225 lines
7.2 KiB
Vue
<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">¥</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>
|