111 lines
3.9 KiB
Vue
111 lines
3.9 KiB
Vue
<script lang='ts' setup>
|
|
import type { Treaty } from "@elysiajs/eden";
|
|
import type { InfiniteScrollCustomEvent } from "@ionic/vue";
|
|
import type { TreatyQuery } from "@/api/types";
|
|
import { eyeOutline, timeOutline } from "ionicons/icons";
|
|
import { client, safeClient } from "@/api";
|
|
|
|
type NewsItem = Treaty.Data<typeof client.api.news.get>["data"][number];
|
|
type NewsQuery = TreatyQuery<typeof client.api.news.get>;
|
|
|
|
const [query] = useResetRef<NewsQuery>({
|
|
offset: 0,
|
|
limit: 10,
|
|
});
|
|
const data = ref<NewsItem[]>([]);
|
|
const isFinished = ref(false);
|
|
const router = useRouter();
|
|
|
|
async function fetchNews() {
|
|
const { data: responseData } = await safeClient(client.api.news.get({ query: { ...query.value } }));
|
|
data.value.push(...(responseData.value?.data || []));
|
|
isFinished.value = responseData.value?.pagination.hasNextPage === false;
|
|
}
|
|
async function handleInfinite(event: InfiniteScrollCustomEvent) {
|
|
if (isFinished.value) {
|
|
event.target.complete();
|
|
event.target.disabled = true;
|
|
return;
|
|
}
|
|
query.value.offset! += query.value.limit!;
|
|
await fetchNews();
|
|
setTimeout(() => {
|
|
event.target.complete();
|
|
}, 500);
|
|
}
|
|
|
|
function handleNewsClick(news: any) {
|
|
router.push(`/news/${news.id}`);
|
|
}
|
|
|
|
onMounted(() => {
|
|
fetchNews();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<ion-page>
|
|
<ion-header class="ion-no-border">
|
|
<ion-toolbar class="ion-toolbar">
|
|
<img slot="start" src="@/assets/images/icon-1.png" class="h-8 w-8 ml-2" alt="Logo">
|
|
<ion-title>革新求进</ion-title>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
<ion-content :fullscreen="true">
|
|
<img src="@/assets/images/service-banner.jpg" class="h-50 w-full object-cover" alt="服务页横幅">
|
|
|
|
<!-- 新闻列表区域 -->
|
|
<section class="mb-5 -mt-5 ion-padding-horizontal">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<div class="flex items-center gap-2">
|
|
<img src="@/assets/images/icon.png" class="size-7">
|
|
<div class="text-xl font-bold text-[#1a1a1a]">
|
|
新闻动态
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<empty v-if="data.length === 0" class="my-10" />
|
|
<div v-else class="flex flex-col gap-4">
|
|
<div
|
|
v-for="item in data"
|
|
:key="item.id"
|
|
class="bg-white rounded-2xl overflow-hidden shadow-sm cursor-pointer transition-all active:translate-y-0.5 active:shadow-sm"
|
|
@click="handleNewsClick(item)"
|
|
>
|
|
<div class="relative w-full h-45 overflow-hidden">
|
|
<img v-if="item.thumbnailId" :src="item.thumbnailId" :alt="item.title" class="w-full h-full object-cover">
|
|
<div class="news-badge absolute top-3 left-3 bg-linear-to-br from-[#ff7878] to-[#aa1818] text-white px-3 py-1 rounded-xl text-xs font-semibold shadow-lg">
|
|
热点
|
|
</div>
|
|
</div>
|
|
<div class="p-4">
|
|
<div class="text-xl font-bold text-[#1a1a1a] mb-2 leading-snug">
|
|
{{ item.title }}
|
|
</div>
|
|
<p class="text-sm text-[#666] mb-3 leading-relaxed line-clamp-2">
|
|
{{ item.summary }}
|
|
</p>
|
|
<div class="flex items-center gap-4 text-xs text-[#999]">
|
|
<span class="flex items-center gap-1">
|
|
<ion-icon :icon="timeOutline" class="text-sm" />
|
|
{{ useDateFormat(item.createdAt, 'YYYY-MM-DD') }}
|
|
</span>
|
|
<span class="flex items-center gap-1">
|
|
<ion-icon :icon="eyeOutline" class="text-sm" />
|
|
{{ item.viewCount }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<ion-infinite-scroll threshold="100px" disabled @ion-infinite="handleInfinite">
|
|
<ion-infinite-scroll-content loading-spinner="bubbles" loading-text="加载更多..." />
|
|
</ion-infinite-scroll>
|
|
</ion-content>
|
|
</ion-page>
|
|
</template>
|
|
|
|
<style lang='css' scoped></style>
|