96 lines
2.8 KiB
Vue
96 lines
2.8 KiB
Vue
<template>
|
||
<div>
|
||
<div class="gf-toolbar">
|
||
<input v-model="keyword" class="gf-input" placeholder="搜文章…" @keyup.enter="load(1)" />
|
||
<button class="gf-btn" @click="load(1)">检索</button>
|
||
<select v-model="selectedCat" class="gf-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(--g-muted);">研墨中…</div>
|
||
|
||
<template v-else>
|
||
<div class="ink-grid">
|
||
<InkCard v-for="p in posts" :key="p.id" :to="`/blog/${p.id}`" :seed="p.id">
|
||
<span class="ink-cat">{{ p.categoryName || '未分类' }} · {{ p.date }}</span>
|
||
<span class="ink-title">{{ p.title }}</span>
|
||
<span class="ink-desc">{{ p.excerpt || '(暂无摘要)' }}</span>
|
||
</InkCard>
|
||
</div>
|
||
<div v-if="!posts.length" style="padding:40px 0;color:var(--g-muted);">暂无文章</div>
|
||
|
||
<div v-if="totalPages > 1" class="ink-pager">
|
||
<button class="gf-btn" :disabled="page <= 1" @click="load(page - 1)">‹ 上一页</button>
|
||
<span style="align-self:center;color:var(--g-muted);font-size:13px;letter-spacing:0.2em;">第 {{ page }} / {{ totalPages }} 页</span>
|
||
<button class="gf-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'
|
||
import InkCard from '../components/InkCard.vue'
|
||
|
||
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 = 8
|
||
|
||
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>
|
||
.gf-toolbar {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.ink-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 22px;
|
||
}
|
||
|
||
.ink-pager {
|
||
display: flex;
|
||
gap: 14px;
|
||
justify-content: center;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.gf-btn:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
@media (max-width: 760px) {
|
||
.ink-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|