174 lines
5.6 KiB
Vue
174 lines
5.6 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'
|
|
|
|
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('视频加载失败,请稍后重试')
|
|
}
|
|
}
|
|
|
|
const handleBuy = async () => {
|
|
if (buying.value) return
|
|
buying.value = true
|
|
try {
|
|
const response = await postApi.buy(route.params.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
|
|
} catch (error) {
|
|
console.error('Failed to fetch article:', error)
|
|
}
|
|
}
|
|
|
|
const handleBack = () => {
|
|
router.back()
|
|
}
|
|
|
|
onMounted(async () => {
|
|
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 ref="videoElement" :poster="article.head_images[0]"
|
|
class="w-full aspect-video object-cover plyr-video" playsinline 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 p-4">
|
|
<div class="flex items-center justify-between max-w-md mx-auto">
|
|
<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> |