2026-09-10 10:57:40 +08:00
|
|
|
<template>
|
2026-09-15 16:16:55 +08:00
|
|
|
<view class="page" :style="pageStyle()">
|
2026-09-18 12:59:43 +08:00
|
|
|
<AppHeader title="新闻" :back="false" />
|
|
|
|
|
<view class="news-tabs">
|
2026-10-01 20:26:41 +08:00
|
|
|
<view v-for="item in tabs" :key="item" :class="tab == item ? 'news-tab active-tab' : 'news-tab'" @click="changeTab(item)"><text :class="tab == item ? 'tab-text active-text' : 'tab-text'">{{ item }}</text></view>
|
2026-09-18 12:59:43 +08:00
|
|
|
</view>
|
|
|
|
|
<scroll-view scroll-y :style="contentStyle(314, false)">
|
2026-09-10 10:57:40 +08:00
|
|
|
<view class="news-content">
|
2026-10-01 20:26:41 +08:00
|
|
|
<view v-if="tab == '推荐' && articles.length > 0" class="headline" @click="openArticle(headline.id)">
|
|
|
|
|
<view class="headline-copy"><text class="headline-tag">{{ headline.category }}</text><text class="headline-title">{{ headline.title }}</text><text class="headline-date">{{ headline.date }}</text></view>
|
2026-09-18 12:59:43 +08:00
|
|
|
<image class="headline-art" :src="headline.image" mode="aspectFill"></image>
|
|
|
|
|
</view>
|
2026-10-01 20:26:41 +08:00
|
|
|
<view class="list-head"><text class="list-title">{{ tab == '推荐' ? '最新资讯' : tab }}</text><text class="list-count">{{ total }} 篇</text></view>
|
|
|
|
|
<view v-if="loading" class="content-state"><text>正在加载资讯…</text></view>
|
|
|
|
|
<view v-else-if="error.length > 0" class="content-state"><text>{{ error }}</text><text class="content-retry" @click="reload">重新加载</text></view>
|
|
|
|
|
<view v-else-if="articles.length == 0" class="content-state"><text>暂无资讯</text></view>
|
2026-09-18 12:59:43 +08:00
|
|
|
<view class="article-list">
|
|
|
|
|
<view class="article" v-for="(item, index) in filteredArticles" :key="item.id" :class="index == filteredArticles.length - 1 ? 'last-article' : ''" @click="openArticle(item.id)">
|
|
|
|
|
<view class="article-copy"><text class="article-title">{{ item.title }}</text><text class="article-summary">{{ item.summary }}</text><text class="article-meta">{{ item.category }} · {{ item.date }}</text></view>
|
|
|
|
|
<image class="article-art" :src="item.image" mode="aspectFill"></image>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
2026-10-01 20:26:41 +08:00
|
|
|
<text v-if="moreError.length > 0" class="content-state">{{ moreError }}</text>
|
|
|
|
|
<text v-if="hasMore" class="content-retry" @click="more">{{ loadingMore ? '正在加载…' : '加载更多' }}</text>
|
2026-09-10 10:57:40 +08:00
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
2026-09-18 12:59:43 +08:00
|
|
|
<BottomNav active="news" />
|
2026-09-10 10:57:40 +08:00
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="uts">
|
2026-10-01 20:26:41 +08:00
|
|
|
import { usePageLayout } from '@/services/layout.uts'
|
2026-10-07 10:01:05 +08:00
|
|
|
import { fetchArticles, emptyArticle, NewsArticle, validateContentAppend } from '@/services/content.uts'
|
2026-10-01 20:26:41 +08:00
|
|
|
import AppHeader from '@/components/AppHeader.uvue'
|
|
|
|
|
import BottomNav from '@/components/BottomNav.uvue'
|
|
|
|
|
const { contentStyle, pageStyle } = usePageLayout()
|
|
|
|
|
const tabs: string[] = ['推荐', '企业融资', '个人融资', '政策解读']
|
|
|
|
|
const tab = ref('推荐'); const articles = ref<NewsArticle[]>([]); const total = ref(0); const hasMore = ref(false)
|
|
|
|
|
const loading = ref(false); const loadingMore = ref(false); const error = ref(''); const moreError = ref('')
|
|
|
|
|
let generation = 0; let visible = false; let page = 1
|
|
|
|
|
const headline = computed((): NewsArticle => articles.value.length > 0 ? articles.value[0] : emptyArticle())
|
|
|
|
|
const filteredArticles = computed((): NewsArticle[] => tab.value == '推荐' ? articles.value.slice(1) : articles.value)
|
|
|
|
|
async function load(append: boolean) {
|
|
|
|
|
if (!visible || loading.value || loadingMore.value || (append && !hasMore.value)) return
|
|
|
|
|
const request = append ? generation : ++generation
|
|
|
|
|
if (append) { loadingMore.value = true; moreError.value = '' }
|
2026-10-07 10:01:05 +08:00
|
|
|
else { loading.value = true; error.value = ''; moreError.value = ''; articles.value = []; total.value = 0; hasMore.value = false; page = 1 }
|
2026-10-01 20:26:41 +08:00
|
|
|
try {
|
|
|
|
|
const result = await fetchArticles(tab.value == '推荐' ? '' : tab.value, append ? page + 1 : 1)
|
|
|
|
|
// 换分类或离页后,旧列表响应不能覆盖新选择。
|
|
|
|
|
if (!visible || request != generation) return
|
2026-10-07 10:01:05 +08:00
|
|
|
if (append) validateContentAppend(articles.value.map((item: NewsArticle): string => item.id), result.items.map((item: NewsArticle): string => item.id))
|
2026-10-01 20:26:41 +08:00
|
|
|
articles.value = append ? articles.value.concat(result.items) : result.items
|
|
|
|
|
total.value = result.total; hasMore.value = result.hasMore
|
|
|
|
|
if (append) page++
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (visible && request == generation) {
|
|
|
|
|
const message = err instanceof Error ? err.message : '资讯加载失败,请重试'
|
|
|
|
|
if (append) moreError.value = message; else error.value = message
|
|
|
|
|
}
|
|
|
|
|
} finally { if (request == generation) { loading.value = false; loadingMore.value = false } }
|
|
|
|
|
}
|
|
|
|
|
function reload() { load(false) }
|
|
|
|
|
const more = () => { load(true) }
|
|
|
|
|
const changeTab = (value: string) => { if (value == tab.value) return; generation++; loading.value = false; loadingMore.value = false; tab.value = value; reload() }
|
|
|
|
|
const openArticle = (id: string) => { uni.navigateTo({ url: '/packages/content/article/article?id=' + id }) }
|
|
|
|
|
onShow(() => { visible = true; loading.value = false; loadingMore.value = false; reload() })
|
|
|
|
|
onHide(() => { visible = false; generation++ })
|
|
|
|
|
onUnload(() => { visible = false; generation++ })
|
2026-09-10 10:57:40 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style>
|
2026-10-01 20:26:41 +08:00
|
|
|
.content-state { padding: 30rpx 0; color: #66758a; font-size: 25rpx; line-height: 40rpx; }
|
|
|
|
|
.content-retry { color: #0868f7; font-size: 25rpx; padding: 22rpx 0; }
|
2026-09-18 12:59:43 +08:00
|
|
|
.news-tabs { height: 88rpx; flex-direction: row; align-items: center; background-color: #ffffff; border-bottom: 1rpx solid #e8eef6; padding: 0 22rpx; }
|
|
|
|
|
.news-tab { flex: 1; height: 88rpx; justify-content: center; align-items: center; border-bottom: 4rpx solid transparent; }
|
|
|
|
|
.active-tab { border-bottom-color: #0868f7; }
|
|
|
|
|
.tab-text { color: #66758a; font-size: 26rpx; }
|
|
|
|
|
.active-text { color: #0868f7; font-weight: 700; }
|
|
|
|
|
.news-content { padding: 24rpx 28rpx 90rpx; }
|
|
|
|
|
.headline { min-height: 256rpx; border-radius: 20rpx; background-color: #123c7b; flex-direction: row; overflow: hidden; }
|
|
|
|
|
.headline-copy { flex: 1; padding: 26rpx 20rpx 26rpx 26rpx; }
|
|
|
|
|
.headline-tag { color: #b8ddff; font-size: 22rpx; font-weight: 600; }
|
|
|
|
|
.headline-title { color: #ffffff; font-size: 31rpx; line-height: 45rpx; font-weight: 700; margin-top: 12rpx; }
|
|
|
|
|
.headline-date { color: #c4d7ed; font-size: 22rpx; margin-top: 16rpx; }
|
|
|
|
|
.headline-art { width: 212rpx; height: 282rpx; flex-shrink: 0; align-self: center; }
|
|
|
|
|
.list-head { flex-direction: row; justify-content: space-between; align-items: center; margin: 30rpx 0 16rpx; }
|
|
|
|
|
.list-title { color: #172640; font-size: 30rpx; font-weight: 700; }
|
|
|
|
|
.list-count { color: #66758a; font-size: 22rpx; }
|
|
|
|
|
.article-list { background-color: #ffffff; border-radius: 18rpx; padding: 0 22rpx; }
|
|
|
|
|
.article { padding: 26rpx 0; flex-direction: row; border-bottom: 1rpx solid #e8eef6; align-items: flex-start; }
|
|
|
|
|
.last-article { border-bottom-width: 0; }
|
|
|
|
|
.article-copy { flex: 1; padding-right: 18rpx; }
|
|
|
|
|
.article-title { color: #172640; font-size: 29rpx; line-height: 42rpx; font-weight: 600; }
|
|
|
|
|
.article-summary { color: #66758a; font-size: 24rpx; line-height: 36rpx; margin-top: 10rpx; }
|
|
|
|
|
.article-meta { color: #66758a; font-size: 22rpx; line-height: 32rpx; margin-top: 14rpx; }
|
|
|
|
|
.article-art { width: 148rpx; height: 132rpx; border-radius: 12rpx; flex-shrink: 0; margin-top: 4rpx; }
|
2026-09-10 10:57:40 +08:00
|
|
|
</style>
|