86 lines
2.8 KiB
Vue
86 lines
2.8 KiB
Vue
<template>
|
||
<div>
|
||
<div class="mag-toolbar">
|
||
<input v-model="keyword" class="mag-input" placeholder="搜索文章…" @keyup.enter="load(1)" />
|
||
<button class="mag-btn" @click="load(1)">搜索</button>
|
||
<select v-model="selectedCat" class="mag-input" @change="load(1)">
|
||
<option :value="0">全部分类</option>
|
||
<option v-for="c in categories" :key="c.id" :value="c.id">{{ c.name }}</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div v-if="loading" style="padding:40px 0;color:var(--m-muted);">排版中…</div>
|
||
|
||
<template v-else>
|
||
<div class="mag-grid">
|
||
<router-link v-for="(p, i) in posts" :key="p.id" :to="`/blog/${p.id}`" class="mag-article" :data-index="String(i + 1).padStart(2, '0')">
|
||
<div class="mag-article-cat">{{ p.categoryName || '未分类' }}</div>
|
||
<h2 class="mag-article-title">{{ p.title }}</h2>
|
||
<p class="mag-article-excerpt">{{ p.excerpt || '(暂无摘要)' }}</p>
|
||
<div class="mag-article-meta">{{ p.date }}</div>
|
||
</router-link>
|
||
</div>
|
||
<div v-if="!posts.length" style="padding:40px 0;color:var(--m-muted);">暂无文章</div>
|
||
|
||
<div v-if="totalPages > 1" class="mag-pager">
|
||
<button class="mag-btn" :disabled="page <= 1" @click="load(page - 1)">‹ 上一页</button>
|
||
<span style="font-size:13px;color:var(--m-muted);align-self:center;">{{ page }} / {{ totalPages }}</span>
|
||
<button class="mag-btn" :disabled="page >= totalPages" @click="load(page + 1)">下一页 ›</button>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { fetchPosts, fetchCategories, type Post } from '../../../services/api'
|
||
|
||
const posts = ref<Post[]>([])
|
||
const categories = ref<Awaited<ReturnType<typeof fetchCategories>>>([])
|
||
const keyword = ref('')
|
||
const selectedCat = ref(0)
|
||
const page = ref(1)
|
||
const totalPages = ref(1)
|
||
const loading = ref(true)
|
||
const PAGE_SIZE = 10
|
||
|
||
const load = async (p: number) => {
|
||
loading.value = true
|
||
const res = await fetchPosts(keyword.value || undefined, selectedCat.value > 0 ? selectedCat.value : undefined, undefined, undefined, p, PAGE_SIZE)
|
||
const list = 'list' in res ? res.list : (Array.isArray(res) ? res : [])
|
||
const total = 'total' in res ? res.total : list.length
|
||
posts.value = list
|
||
totalPages.value = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
||
page.value = p
|
||
loading.value = false
|
||
}
|
||
|
||
onMounted(async () => {
|
||
categories.value = await fetchCategories()
|
||
await load(1)
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.mag-toolbar {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 30px;
|
||
border-bottom: 2px solid var(--m-ink);
|
||
padding-bottom: 18px;
|
||
}
|
||
|
||
.mag-pager {
|
||
display: flex;
|
||
gap: 16px;
|
||
justify-content: center;
|
||
margin-top: 34px;
|
||
}
|
||
|
||
.mag-btn:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
}
|
||
</style>
|