Files
quyun/frontend/wechat/src/views/ArticleDetail.vue
2025-05-12 19:45:47 +08:00

265 lines
8.6 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 Player, { Events, I18N } from 'xgplayer';
import 'xgplayer/dist/index.min.css';
import ZH from 'xgplayer/es/lang/zh-cn';
import MobilePreset from 'xgplayer/es/presets/mobile';
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);
// 启用中文
I18N.use(ZH)
const initializePlayer = async () => {
if (player.value) {
return
}
const { data } = await postApi.play(route.params.id);
if (!data.url) {
alert("视频地址不存在");
return;
}
try {
player.value = new Player({
id: 'player',
url: data.url,
width: '100%',
fitVideoSize: 'auto',
videoFillMode: 'fillWidth',
fluid: true,
presets: [MobilePreset],
playsinline: true,
playbackRate: [0.5, 0.75, 1],
autoplay: true,
// plugins: [Mp4Plugin],
// mp4plugin: {
// maxBufferLength: 15,
// minBufferLength: 5,
// },
poster: article.value.head_images[0],
});
player.value.on(Events.READY, async () => {
console.log("player ready")
})
player.value.on(Events.PLAY, async () => {
console.log("player play")
})
player.value.on(Events.ENDED, async () => {
console.log("player ended")
await updateMediaSource();
});
player.value.on(Events.ERROR, (error) => {
alert(`视频播放失败: ${error.errorCode} ${error.message} mediaError: ${error.mediaError?.code} ${error.mediaError?.message}`);
})
} catch (error) {
console.error("Failed to load video:", error);
alert("视频加载失败: " + error.response.data);
}
};
const updateMediaSource = async () => {
if (player.value) {
const { data } = await postApi.play(route.params.id);
if (!data.url) {
alert("视频地址不存在");
return;
}
player.value.switchURL(data.url)
}
};
const handleBuy = async () => {
if (buying.value) return;
buying.value = true;
try {
const response = await postApi.buy(article.value.id);
const payData = response.data;
if (payData.AppID != "balance") {
// 调用微信支付
window.WeixinJSBridge.invoke(
"getBrandWCPayRequest",
{
...payData,
},
async function (res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
// 支付成功,刷新文章数据
fetchArticle();
await updateMediaSource();
} else if (res.err_msg === "get_brand_wcpay_request:cancel") {
// 用户取消支付
console.log("Payment cancelled");
alert("支付已取消");
} else {
// 支付失败或取消
console.error("Payment failed:", res.err_msg);
alert(
"支付失败:" +
(res.err_msg === "get_brand_wcpay_request:cancel"
? "支付已取消"
: "支付异常")
);
}
}
);
} else {
alert("余额支付成功");
}
} 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;
// 调用微信 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 = () => {
try {
router.back();
} catch (error) {
router.replace('/');
}
};
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();
});
onUnmounted(() => {
if (player.value) {
player.value.destroy();
player.value = null;
}
});
</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 line-clamp-1 break-all">
{{ article.title }}
</h1>
</template>
</div>
<div class="flex-1">
<div v-if="article">
<div id="player">
<div class="relative flex items-center justify-center bg-gray-100">
<div @click="initializePlayer"
class="absolute z-1 rounded-full bg-orange-500 text-white border-2 border-yellow-500 font-bold px-4 py-2">
点击播放
</div>
<img class="z-0 w-full brightness-50" :src="article.head_images[0]" :alt="article.title" />
</div>
</div>
<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 * article.discount / (100 * 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>