Browse Source

feat(pages): add discover directory page

Made-with: Cursor
main
npmrun 11 hours ago
parent
commit
d87b82de5c
  1. 151
      app/pages/discover/index.vue

151
app/pages/discover/index.vue

@ -0,0 +1,151 @@
<script setup lang="ts">
definePageMeta({ title: '发现' })
const route = useRoute()
const router = useRouter()
const { fetchData, getApiErrorMessage } = useClientApi()
function parsePage(raw: unknown): number {
const n =
typeof raw === 'string'
? Number.parseInt(raw, 10)
: typeof raw === 'number'
? raw
: Number.NaN
if (!Number.isFinite(n) || n < 1) {
return 1
}
return Math.floor(n)
}
const page = ref(parsePage(route.query.page))
watch(
() => route.query.page,
() => {
page.value = parsePage(route.query.page)
},
)
type DiscoverUser = {
publicSlug: string
displayName: string
avatar: string | null
location: string | null
}
type Payload = {
items: DiscoverUser[]
total: number
page: number
pageSize: number
}
const PAGE_SIZE = 10
const data = ref<Payload | null>(null)
const pending = ref(false)
const loadError = ref('')
async function load() {
pending.value = true
loadError.value = ''
try {
const url = page.value > 1 ? `/api/discover/users?page=${page.value}` : '/api/discover/users'
data.value = await fetchData<Payload>(url, { notify: false })
} catch (e: unknown) {
data.value = null
loadError.value = getApiErrorMessage(e)
} finally {
pending.value = false
}
}
watch(page, () => {
void load()
})
function onPageChange(p: number) {
page.value = p
const query: Record<string, string | string[] | undefined> = { ...route.query }
if (p > 1) {
query.page = String(p)
}
else {
delete query.page
}
void router.replace({ query })
}
onMounted(() => {
void load()
})
</script>
<template>
<UContainer class="max-w-6xl py-8">
<h1 class="mb-6 text-2xl font-semibold text-highlighted">
发现
</h1>
<div v-if="pending && !data" class="text-muted">
加载中
</div>
<UAlert
v-else-if="loadError"
color="error"
:title="loadError"
class="mb-6"
/>
<template v-else-if="data">
<UEmpty
v-if="data.total === 0"
title="暂时还没有用户"
description="尚无用户开启「出现在发现页」。可在个人资料中打开该选项并设置公开主页 slug。"
/>
<template v-else>
<ul class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<li
v-for="u in data.items"
:key="u.publicSlug"
class="border border-default rounded-xl bg-elevated/20 overflow-hidden transition-shadow hover:shadow-md"
>
<NuxtLink
:to="`/@${encodeURIComponent(u.publicSlug)}`"
class="flex flex-col gap-3 p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-default rounded-xl"
>
<div class="flex items-center gap-3 min-w-0">
<UAvatar
:src="u.avatar || undefined"
:alt="u.displayName"
size="md"
class="shrink-0 ring-1 ring-default"
/>
<div class="min-w-0 flex-1">
<div class="font-medium text-highlighted truncate">
{{ u.displayName }}
</div>
<div class="text-xs text-muted truncate">
@{{ u.publicSlug }}
</div>
</div>
</div>
<p v-if="u.location" class="text-sm text-muted line-clamp-2">
{{ u.location }}
</p>
</NuxtLink>
</li>
</ul>
<div v-if="data.total > PAGE_SIZE" class="flex justify-end mt-8">
<UPagination
:page="page"
:total="data.total"
:items-per-page="PAGE_SIZE"
size="sm"
@update:page="onPageChange"
/>
</div>
</template>
</template>
</UContainer>
</template>
Loading…
Cancel
Save