265 lines
8.6 KiB
Vue
265 lines
8.6 KiB
Vue
<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">¥</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>
|