Files
quyun/frontend/wechat/src/views/ArticleDetail.vue
yanghao05 b9ed187274 fix: ui
2025-04-29 14:30:05 +08:00

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">&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>