1 changed files with 151 additions and 0 deletions
@ -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…
Reference in new issue