78 lines
2.7 KiB
Vue
78 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, reactive } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { api, type ModelSummary } from '../api/client'
|
|
|
|
const router = useRouter()
|
|
|
|
const st = reactive({
|
|
q: '',
|
|
sort: 'new' as 'new' | 'old',
|
|
page: 1,
|
|
pageSize: 20,
|
|
total: 0,
|
|
items: [] as ModelSummary[],
|
|
loading: false,
|
|
error: '' as string | null,
|
|
})
|
|
|
|
async function load() {
|
|
st.loading = true; st.error = null
|
|
try {
|
|
const res = await api.listModels({ q: st.q || undefined, page: st.page, pageSize: st.pageSize, sort: st.sort })
|
|
st.items = res.items || []
|
|
st.total = res.total || st.items.length
|
|
} catch (e: any) {
|
|
st.error = e?.message || String(e)
|
|
} finally { st.loading = false }
|
|
}
|
|
|
|
function goDetail(id: string) { router.push(`/models/${id}`) }
|
|
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<template>
|
|
<div class="list-page">
|
|
<div class="toolbar">
|
|
<input v-model="st.q" placeholder="搜索标题/描述" @keydown.enter="st.page=1; load()" />
|
|
<select v-model="st.sort" @change="st.page=1; load()">
|
|
<option value="new">最新</option>
|
|
<option value="old">最早</option>
|
|
</select>
|
|
<button class="btn" @click="st.page=1; load()" :disabled="st.loading">搜索</button>
|
|
</div>
|
|
|
|
<div v-if="st.error" class="error">{{ st.error }}</div>
|
|
<div v-else class="grid">
|
|
<div v-for="m in st.items" :key="m.id" class="card" @click="goDetail(m.id)">
|
|
<img :src="api.previewUrl(m.id)" alt="preview" />
|
|
<div class="meta">
|
|
<h3>{{ m.title }}</h3>
|
|
<p>{{ m.desc }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pager" v-if="st.total > st.pageSize">
|
|
<button :disabled="st.page<=1" @click="st.page--; load()">上一页</button>
|
|
<span>第 {{ st.page }} 页</span>
|
|
<button :disabled="st.page*st.pageSize>=st.total" @click="st.page++; load()">下一页</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.list-page{padding:12px;height:100%;box-sizing:border-box;overflow:auto}
|
|
.toolbar{display:flex;gap:8px;margin-bottom:12px}
|
|
input,select{padding:6px 8px;border:1px solid #e5e7eb;border-radius:8px}
|
|
.btn{padding:6px 10px;border:1px solid #e5e7eb;border-radius:8px;background:#fff}
|
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
|
|
.card{border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;cursor:pointer;background:#fff;display:flex;flex-direction:column}
|
|
.card img{width:100%;height:140px;object-fit:cover;background:#f3f4f6}
|
|
.meta{padding:10px}
|
|
.meta h3{margin:0 0 6px 0;font-size:14px}
|
|
.meta p{margin:0;color:#6b7280;font-size:12px}
|
|
.pager{display:flex;gap:10px;align-items:center;justify-content:center;margin-top:12px}
|
|
.error{color:#b91c1c}
|
|
</style> |