feat: update

This commit is contained in:
yanghao05
2025-04-17 09:36:09 +08:00
parent e03564631a
commit 0d0887b4fb
7 changed files with 234 additions and 376 deletions

View File

@@ -1,15 +1,54 @@
<script setup>
import { onMounted, ref } from 'vue'
import { AiOutlineLeft } from 'vue-icons-plus/ai'
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 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)
}
}
onMounted(async () => {
// TODO: Implement API call to fetch article details
const { id } = route.params
article.value = { id, title: '文章标题', content: '文章内容' }
await fetchArticle()
})
</script>
@@ -18,10 +57,7 @@ onMounted(async () => {
<header class="fixed top-0 left-0 right-0 h-14 bg-white border-b border-gray-200 flex items-center px-4 z-50">
<button @click="router.back()"
class="flex items-center justify-center w-10 h-10 mr-2 rounded-full hover:bg-gray-100 active:bg-gray-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
<AiOutlineLeft class="h-5 w-5" />
</button>
<h2 class="text-lg font-medium">{{ article?.title || '文章详情' }}</h2>
</header>
@@ -29,7 +65,19 @@ onMounted(async () => {
<main class="pt-14 px-4">
<div v-if="article" class="py-4">
<div class="prose max-w-none">
{{ article.content }}
<template v-if="article.purchased">
{{ article.content }}
</template>
<template v-else>
<div class="text-center py-8">
<p class="text-gray-600 mb-4">购买后即可阅读完整内容</p>
<button @click="handleBuy" :disabled="buying"
class="bg-blue-600 text-white px-6 py-2 rounded-full hover:bg-blue-700 active:bg-blue-800 transition-colors disabled:opacity-50">
<span v-if="buying">处理中...</span>
<span v-else>购买文章 ¥{{ article.price }}</span>
</button>
</div>
</template>
</div>
</div>
<div v-else class="flex justify-center items-center py-8">
@@ -37,8 +85,4 @@ onMounted(async () => {
</div>
</main>
</div>
</template>
<style scoped>
/* 可以移除所有样式,因为都使用了 Tailwind 类 */
</style>
</template>

View File

@@ -1,28 +1,51 @@
<script setup>
import { postApi } from '@/api/postApi'
import ArticleListItem from '@/components/ArticleListItem.vue'
import { useArticleStore } from '@/stores/article'
import { useIntersectionObserver } from '@vueuse/core'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const store = useArticleStore()
const searchInput = ref('')
const loadingTrigger = ref(null)
const articles = ref([])
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const limit = 10
const fetchArticles = async () => {
if (loading.value) return
loading.value = true
try {
const { data } = await postApi.list({
page: currentPage.value,
limit,
keyword: searchInput.value
})
if (data.items?.length === 0) {
hasMore.value = false
} else {
articles.value.push(...data.items)
currentPage.value += 1
}
} catch (error) {
console.error('Failed to fetch articles:', error)
} finally {
loading.value = false
}
}
// 优化 Intersection Observer 配置
useIntersectionObserver(
loadingTrigger,
([{ isIntersecting }]) => {
console.log('Intersection state:', { isIntersecting, loading: store.loading, hasMore: store.hasMore })
if (isIntersecting && !store.loading && store.hasMore) {
console.log('Fetching more articles...')
store.fetchArticles()
if (isIntersecting && !loading.value && hasMore.value) {
fetchArticles()
}
},
{
threshold: 0,
rootMargin: '100px' // 提前 100px 触发加载
rootMargin: '100px'
}
)
@@ -31,7 +54,10 @@ const showArticle = (id) => {
}
const handleSearch = () => {
store.setSearchQuery(searchInput.value)
articles.value = []
currentPage.value = 1
hasMore.value = true
fetchArticles()
}
const handleKeyup = (e) => {
@@ -41,9 +67,7 @@ const handleKeyup = (e) => {
}
onMounted(() => {
if (store.articles.length === 0) {
store.fetchArticles()
}
fetchArticles()
})
</script>
@@ -58,16 +82,15 @@ onMounted(() => {
<div class="flex-1 overflow-y-auto">
<div class="p-4">
<div v-if="store.articles.length === 0 && !store.loading" class="text-center text-gray-500 py-8">
<div v-if="articles.length === 0 && !loading" class="text-center text-gray-500 py-8">
暂无文章
</div>
<ArticleListItem v-for="article in store.articles" :key="article.id" :article="article"
<ArticleListItem v-for="article in articles" :key="article.id" :article="article"
@click="showArticle(article.id)" class="mb-4" />
<!-- 优化加载触发器位置和显示 -->
<div ref="loadingTrigger" class="py-4 text-center" v-show="store.hasMore || store.loading">
<div v-if="store.loading"
<div ref="loadingTrigger" class="py-4 text-center" v-show="hasMore || loading">
<div v-if="loading"
class="animate-spin rounded-full h-8 w-8 border-4 border-gray-200 border-t-blue-600 mx-auto">
</div>
<div v-else class="h-8">
@@ -75,7 +98,7 @@ onMounted(() => {
</div>
</div>
<div v-if="!store.hasMore && store.articles.length > 0" class="text-center text-gray-500 py-4">
<div v-if="!hasMore && articles.length > 0" class="text-center text-gray-500 py-4">
没有更多文章了
</div>
</div>

View File

@@ -1,18 +1,58 @@
<script setup>
import { postApi } from '@/api/postApi'
import ArticleListItem from '@/components/ArticleListItem.vue'
import { useIntersectionObserver } from '@vueuse/core'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const purchasedArticles = ref([])
const loadingTrigger = ref(null)
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const limit = 10
const showArticle = (id) => {
router.push(`/article/${id}`)
const fetchArticles = async () => {
if (loading.value) return
loading.value = true
try {
const response = await postApi.mine({
page: currentPage.value,
limit
})
if (response.data.data?.length === 0) {
hasMore.value = false
} else {
purchasedArticles.value.push(...response.data.data)
currentPage.value += 1
}
} catch (error) {
console.error('Failed to fetch purchased articles:', error)
} finally {
loading.value = false
}
}
onMounted(async () => {
// TODO: Implement API call to fetch purchased articles
purchasedArticles.value = []
useIntersectionObserver(
loadingTrigger,
([{ isIntersecting }]) => {
if (isIntersecting && !loading.value && hasMore.value) {
fetchArticles()
}
},
{
threshold: 0,
rootMargin: '100px'
}
)
const showArticle = (id) => {
router.push(`/posts/${id}`)
}
onMounted(() => {
fetchArticles()
})
</script>
@@ -20,23 +60,32 @@ onMounted(async () => {
<div class="h-full flex flex-col">
<div class="flex-none bg-white border-b border-gray-200 z-50 shadow">
<div class="p-4">
<h2 class="text-lg font-medium text-gray-800">已购买的文章</h2>
<h2 class="text-lg font-medium text-gray-800">已购买</h2>
</div>
</div>
<div class="flex-1 overflow-y-auto">
<div class="p-4">
<div v-if="purchasedArticles.length === 0" class="text-center text-gray-500 py-8">
暂无已购买的文章
<div v-if="purchasedArticles.length === 0 && !loading" class="text-center text-gray-500 py-8">
暂无已购买
</div>
<ArticleListItem v-for="article in purchasedArticles" :key="article.id" :article="article"
@click="showArticle(article.id)" class="mb-4" />
<div ref="loadingTrigger" class="py-4 text-center" v-show="hasMore || loading">
<div v-if="loading"
class="animate-spin rounded-full h-8 w-8 border-4 border-gray-200 border-t-blue-600 mx-auto">
</div>
<div v-else class="h-8">
<!-- 空白占位保持触发器可见 -->
</div>
</div>
<div v-if="!hasMore && purchasedArticles.length > 0" class="text-center text-gray-500 py-4">
没有更多了
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* Remove all styles as they're replaced by Tailwind classes */
</style>