Browse Source
- Introduced `GuestHomeSection` and `LoggedInHomeSection` components to provide tailored content for guests and logged-in users. - Added a quick create button for logged-in users to facilitate easy post creation. - Updated the AppShell component to include computed properties for visibility of the quick create button based on user login status. - Refactored the main index page to utilize the new components, improving the overall layout and user engagement. - Implemented a new utility function for normalizing post slugs to ensure consistent URL formatting. This update significantly enhances the homepage experience for both guests and registered users, promoting user interaction and content creation.main
16 changed files with 946 additions and 477 deletions
@ -0,0 +1,209 @@ |
|||
<script setup lang="ts"> |
|||
defineProps<{ |
|||
siteName?: string |
|||
allowRegister: boolean |
|||
}>() |
|||
|
|||
const features = [ |
|||
{ |
|||
title: '个人资料', |
|||
desc: '生平叙事、头像、社交链接与公开主页 slug,支持分块可见性。', |
|||
icon: 'i-lucide-user-round', |
|||
to: '/me/profile', |
|||
}, |
|||
{ |
|||
title: 'Markdown 文章', |
|||
desc: '自写内容、slug、摘要与封面,每条可公开、私密或仅链接分享。', |
|||
icon: 'i-lucide-file-text', |
|||
to: '/me/posts', |
|||
}, |
|||
{ |
|||
title: '时光机', |
|||
desc: '按时间轴记录里程碑事件,与文章、阅读流在同一叙事里呈现。', |
|||
icon: 'i-lucide-history', |
|||
to: '/me/timeline', |
|||
}, |
|||
{ |
|||
title: 'RSS 收件箱', |
|||
desc: '订阅源仅自己可见;定时拉取、去重,条目默认可再设为公开展示。', |
|||
icon: 'i-lucide-rss', |
|||
to: '/me/rss', |
|||
}, |
|||
] as const |
|||
|
|||
const highlights = [ |
|||
'多维内容管理', |
|||
'公开页可控展示', |
|||
'RSS 自动聚合', |
|||
'创作与记录一体化', |
|||
] as const |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="space-y-12"> |
|||
<section |
|||
class="relative overflow-hidden rounded-3xl border border-primary/30 bg-gradient-to-br from-primary/20 via-default to-elevated px-6 py-10 sm:px-10 sm:py-12" |
|||
> |
|||
<div class="pointer-events-none absolute -right-20 -top-20 size-72 rounded-full bg-primary/20 blur-3xl" /> |
|||
<div class="pointer-events-none absolute -bottom-24 -left-16 size-80 rounded-full bg-primary/10 blur-3xl" /> |
|||
<div class="pointer-events-none absolute inset-0 opacity-40" style="background-image: radial-gradient(circle at 20% 20%, rgba(255,255,255,0.35), transparent 35%), radial-gradient(circle at 80% 10%, rgba(212,106,49,0.18), transparent 30%);" /> |
|||
|
|||
<div class="relative grid gap-6 lg:grid-cols-[1.2fr_0.8fr] lg:items-start"> |
|||
<div class="space-y-5"> |
|||
<UBadge color="primary" variant="soft" size="md" class="ring-1 ring-primary/30"> |
|||
{{ siteName }} · Personal Control Center |
|||
</UBadge> |
|||
<h1 class="text-3xl font-extrabold tracking-tight text-highlighted sm:text-5xl"> |
|||
你的内容宇宙, |
|||
<span class="text-primary">从这里被看见</span> |
|||
</h1> |
|||
<p class="max-w-2xl text-base leading-relaxed text-toned sm:text-lg"> |
|||
一站式管理资料、文章、时间线与 RSS。可精细控制公开范围,用更专业的方式建立你的个人品牌主页。 |
|||
</p> |
|||
|
|||
<div class="flex flex-wrap gap-2"> |
|||
<UBadge |
|||
v-for="item in highlights" |
|||
:key="item" |
|||
color="neutral" |
|||
variant="subtle" |
|||
size="sm" |
|||
class="ring-1 ring-default/80" |
|||
> |
|||
{{ item }} |
|||
</UBadge> |
|||
</div> |
|||
|
|||
<div class="flex flex-wrap gap-3 pt-2"> |
|||
<UButton |
|||
v-if="allowRegister" |
|||
to="/register" |
|||
size="lg" |
|||
color="primary" |
|||
icon="i-lucide-sparkles" |
|||
class="shadow-lg shadow-primary/20" |
|||
> |
|||
立即注册,开始搭建主页 |
|||
</UButton> |
|||
<UButton |
|||
to="/login" |
|||
size="lg" |
|||
color="neutral" |
|||
variant="outline" |
|||
icon="i-lucide-log-in" |
|||
> |
|||
我已有账号,直接登录 |
|||
</UButton> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-1"> |
|||
<UCard class="border-primary/30 bg-default/80 backdrop-blur"> |
|||
<p class="text-xs text-muted"> |
|||
内容表达 |
|||
</p> |
|||
<p class="mt-1 text-lg font-semibold text-highlighted"> |
|||
文章 + 时间线双叙事 |
|||
</p> |
|||
</UCard> |
|||
<UCard class="border-primary/30 bg-default/80 backdrop-blur"> |
|||
<p class="text-xs text-muted"> |
|||
对外展示 |
|||
</p> |
|||
<p class="mt-1 text-lg font-semibold text-highlighted"> |
|||
专属公开主页地址 |
|||
</p> |
|||
</UCard> |
|||
<UCard class="border-primary/30 bg-default/80 backdrop-blur"> |
|||
<p class="text-xs text-muted"> |
|||
信息输入 |
|||
</p> |
|||
<p class="mt-1 text-lg font-semibold text-highlighted"> |
|||
RSS 自动同步与整理 |
|||
</p> |
|||
</UCard> |
|||
<UCard class="border-primary/30 bg-default/80 backdrop-blur"> |
|||
<p class="text-xs text-muted"> |
|||
权限机制 |
|||
</p> |
|||
<p class="mt-1 text-lg font-semibold text-highlighted"> |
|||
每条内容独立可见性 |
|||
</p> |
|||
</UCard> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="space-y-4"> |
|||
<div> |
|||
<h2 class="text-2xl font-semibold text-highlighted"> |
|||
你将获得的能力 |
|||
</h2> |
|||
<p class="mt-1 text-sm text-muted sm:text-base"> |
|||
从个人展示到持续创作,再到信息摄取,形成完整的个人内容工作流。 |
|||
</p> |
|||
</div> |
|||
<div class="grid gap-4 sm:grid-cols-2"> |
|||
<UCard |
|||
v-for="item in features" |
|||
:key="item.title" |
|||
class="group border-default/80 transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg hover:shadow-primary/10" |
|||
> |
|||
<div class="flex gap-4"> |
|||
<span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-primary/15 text-primary ring-1 ring-primary/20"> |
|||
<UIcon :name="item.icon" class="size-6" /> |
|||
</span> |
|||
<div class="min-w-0 flex-1 space-y-2"> |
|||
<h3 class="font-semibold text-highlighted"> |
|||
<NuxtLink |
|||
to="/login" |
|||
class="rounded-sm hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|||
> |
|||
{{ item.title }} |
|||
</NuxtLink> |
|||
</h3> |
|||
<p class="text-sm leading-relaxed text-muted"> |
|||
{{ item.desc }} |
|||
</p> |
|||
<UButton |
|||
to="/login" |
|||
size="xs" |
|||
variant="soft" |
|||
class="px-0" |
|||
trailing-icon="i-lucide-arrow-right" |
|||
> |
|||
登录后立即体验 |
|||
</UButton> |
|||
</div> |
|||
</div> |
|||
</UCard> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="rounded-2xl border border-primary/25 bg-gradient-to-r from-primary/10 via-elevated to-default px-6 py-8"> |
|||
<div class="flex flex-wrap items-center justify-between gap-4"> |
|||
<div> |
|||
<h3 class="text-lg font-semibold text-highlighted"> |
|||
准备好搭建你的公开主页了吗? |
|||
</h3> |
|||
<p class="mt-1 text-sm text-muted leading-relaxed"> |
|||
新账号采用邀请制开通,开通后即可开始创建并发布你的个人内容空间。 |
|||
</p> |
|||
</div> |
|||
<div class="flex flex-wrap gap-2"> |
|||
<UButton |
|||
v-if="allowRegister" |
|||
to="/register" |
|||
color="primary" |
|||
icon="i-lucide-user-plus" |
|||
> |
|||
立即注册 |
|||
</UButton> |
|||
<UButton to="/login" color="neutral" variant="outline"> |
|||
去登录 |
|||
</UButton> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,121 @@ |
|||
<script setup lang="ts"> |
|||
type HomeUser = { |
|||
username: string |
|||
nickname?: string | null |
|||
avatar?: string | null |
|||
role: 'admin' | 'user' | string |
|||
publicSlug?: string | null |
|||
} |
|||
|
|||
defineProps<{ |
|||
user: HomeUser |
|||
}>() |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="space-y-4"> |
|||
<section class="rounded-2xl border border-default bg-gradient-to-br from-primary/5 via-default to-elevated px-6 py-6 sm:px-8"> |
|||
<div class="grid gap-4 lg:grid-cols-3"> |
|||
<UCard class="ring-1 ring-default/60 lg:col-span-2"> |
|||
<div class="flex flex-wrap items-start justify-between gap-4"> |
|||
<div class="flex min-w-0 items-start gap-3"> |
|||
<UAvatar :src="user.avatar || undefined" :alt="user.username" size="lg" class="ring-1 ring-default" /> |
|||
<div class="min-w-0"> |
|||
<p class="text-xs font-medium text-muted"> |
|||
当前用户 |
|||
</p> |
|||
<p class="truncate text-lg font-semibold text-highlighted"> |
|||
{{ user.nickname?.trim() || user.username }} |
|||
</p> |
|||
<NuxtLink |
|||
to="/me/profile" |
|||
class="block truncate text-sm text-muted transition-colors hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|||
> |
|||
@{{ user.username }} · {{ user.role === 'admin' ? '管理员' : '用户' }} |
|||
</NuxtLink> |
|||
<NuxtLink |
|||
v-if="user.publicSlug" |
|||
:to="`/@${user.publicSlug}`" |
|||
target="_blank" |
|||
class="mt-1 block text-sm tabular-nums text-[#d46a31] transition-colors hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|||
> |
|||
{{ `/@${user.publicSlug}` }} |
|||
</NuxtLink> |
|||
<NuxtLink |
|||
v-else |
|||
to="/me/profile" |
|||
class="mt-1 block text-sm text-muted transition-colors hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|||
> |
|||
未设置公开主页地址(去设置) |
|||
</NuxtLink> |
|||
</div> |
|||
</div> |
|||
<div class="flex flex-wrap gap-2"> |
|||
<UButton to="/me" size="sm" icon="i-lucide-layout-dashboard"> |
|||
控制台 |
|||
</UButton> |
|||
<UButton to="/me/profile" size="sm" color="neutral" variant="outline"> |
|||
编辑资料 |
|||
</UButton> |
|||
<UButton |
|||
v-if="user.publicSlug" |
|||
:to="`/@${user.publicSlug}`" |
|||
target="_blank" |
|||
size="sm" |
|||
color="neutral" |
|||
variant="outline" |
|||
icon="i-lucide-external-link" |
|||
> |
|||
公开主页 |
|||
</UButton> |
|||
</div> |
|||
</div> |
|||
</UCard> |
|||
|
|||
<UCard> |
|||
<p class="text-xs font-medium text-muted"> |
|||
管理与常用 |
|||
</p> |
|||
<div class="mt-3 flex flex-wrap gap-2"> |
|||
<UButton to="/me/posts" size="xs" variant="soft"> |
|||
文章 |
|||
</UButton> |
|||
<UButton to="/me/timeline" size="xs" variant="soft"> |
|||
时光机 |
|||
</UButton> |
|||
<UButton to="/me/rss" size="xs" variant="soft"> |
|||
RSS |
|||
</UButton> |
|||
<UButton |
|||
v-if="user.role === 'admin'" |
|||
to="/me/admin/config" |
|||
size="xs" |
|||
color="primary" |
|||
variant="soft" |
|||
> |
|||
应用配置 |
|||
</UButton> |
|||
<UButton |
|||
v-if="user.role === 'admin'" |
|||
to="/me/admin/users" |
|||
size="xs" |
|||
color="primary" |
|||
variant="soft" |
|||
> |
|||
用户管理 |
|||
</UButton> |
|||
</div> |
|||
</UCard> |
|||
</div> |
|||
</section> |
|||
|
|||
<UAlert |
|||
v-if="!user.publicSlug" |
|||
color="warning" |
|||
variant="subtle" |
|||
title="还没有公开主页地址" |
|||
description="在「资料」中设置 public slug 后,访客即可通过 /@你的地址 访问你的公开内容。" |
|||
icon="i-lucide-alert-circle" |
|||
/> |
|||
</div> |
|||
</template> |
|||
@ -1,272 +1,18 @@ |
|||
<script setup lang="ts"> |
|||
import { useAuthSession } from '../../composables/useAuthSession' |
|||
|
|||
const { loggedIn, user, clear } = useAuthSession() |
|||
const { fetchData } = useClientApi() |
|||
import GuestHomeSection from '../../components/home/GuestHomeSection.vue' |
|||
import LoggedInHomeSection from '../../components/home/LoggedInHomeSection.vue' |
|||
|
|||
const { loggedIn, user } = useAuthSession() |
|||
const { allowRegister, siteName } = useGlobalConfig() |
|||
|
|||
usePageTitle('首页') |
|||
|
|||
const logoutLoading = ref(false) |
|||
|
|||
const features = [ |
|||
{ |
|||
title: '个人资料', |
|||
desc: '生平叙事、头像、社交链接与公开主页 slug,支持分块可见性。', |
|||
icon: 'i-lucide-user-round', |
|||
to: '/me/profile', |
|||
}, |
|||
{ |
|||
title: 'Markdown 文章', |
|||
desc: '自写内容、slug、摘要与封面,每条可公开、私密或仅链接分享。', |
|||
icon: 'i-lucide-file-text', |
|||
to: '/me/posts', |
|||
}, |
|||
{ |
|||
title: '时光机', |
|||
desc: '按时间轴记录里程碑事件,与文章、阅读流在同一叙事里呈现。', |
|||
icon: 'i-lucide-history', |
|||
to: '/me/timeline', |
|||
}, |
|||
{ |
|||
title: 'RSS 收件箱', |
|||
desc: '订阅源仅自己可见;定时拉取、去重,条目默认可再设为公开展示。', |
|||
icon: 'i-lucide-rss', |
|||
to: '/me/rss', |
|||
}, |
|||
] as const |
|||
|
|||
async function logout() { |
|||
logoutLoading.value = true |
|||
try { |
|||
await fetchData<{ success: boolean }>('/api/auth/logout', { method: 'POST' }) |
|||
clear() |
|||
await navigateTo('/') |
|||
} finally { |
|||
logoutLoading.value = false |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="mx-auto max-w-6xl space-y-12 pb-4"> |
|||
<!-- Hero --> |
|||
<section class="relative overflow-hidden rounded-2xl border border-default bg-gradient-to-br from-primary/5 via-default to-elevated px-6 py-12 sm:px-10 sm:py-16"> |
|||
<div class="pointer-events-none absolute -right-20 -top-20 size-64 rounded-full bg-primary/10 blur-3xl" /> |
|||
<div class="pointer-events-none absolute -bottom-24 -left-16 size-72 rounded-full bg-primary/5 blur-3xl" /> |
|||
<div class="relative max-w-2xl space-y-4"> |
|||
<UBadge color="primary" variant="subtle" size="md"> |
|||
个人数据中心 |
|||
</UBadge> |
|||
<h1 class="text-3xl font-bold tracking-tight text-highlighted sm:text-4xl"> |
|||
把你的故事、文章与阅读,收进一个地方 |
|||
</h1> |
|||
<p class="text-base text-muted sm:text-lg leading-relaxed"> |
|||
{{ siteName }} 面向「多用户、邀请制」场景:每位用户独立资料与订阅;公开主页可只展示你愿意公开的部分;RSS 在后台静默同步。 |
|||
</p> |
|||
<div class="flex flex-wrap gap-3 pt-2"> |
|||
<template v-if="loggedIn"> |
|||
<UButton to="/me" size="lg" icon="i-lucide-layout-dashboard"> |
|||
进入控制台 |
|||
</UButton> |
|||
<UButton |
|||
v-if="user?.publicSlug" |
|||
:to="`/@${user.publicSlug}`" |
|||
target="_blank" |
|||
size="lg" |
|||
color="neutral" |
|||
variant="outline" |
|||
icon="i-lucide-external-link" |
|||
> |
|||
预览公开主页 |
|||
</UButton> |
|||
<UButton |
|||
v-else |
|||
to="/me/profile" |
|||
size="lg" |
|||
color="neutral" |
|||
variant="outline" |
|||
icon="i-lucide-link" |
|||
> |
|||
设置公开链接 |
|||
</UButton> |
|||
</template> |
|||
<template v-else> |
|||
<UButton to="/login" size="lg" icon="i-lucide-log-in"> |
|||
登录使用 |
|||
</UButton> |
|||
<UButton |
|||
v-if="allowRegister" |
|||
to="/register" |
|||
size="lg" |
|||
color="neutral" |
|||
variant="outline" |
|||
> |
|||
注册账号 |
|||
</UButton> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<!-- 已登录:摘要条 --> |
|||
<section v-if="loggedIn && user" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> |
|||
<UCard class="ring-1 ring-default/60"> |
|||
<div class="flex items-start gap-3"> |
|||
<UAvatar :src="user.avatar || undefined" :alt="user.username" size="md" class="ring-1 ring-default" /> |
|||
<div class="min-w-0 flex-1"> |
|||
<p class="text-xs font-medium text-muted"> |
|||
当前用户 |
|||
</p> |
|||
<p class="truncate font-semibold text-highlighted"> |
|||
{{ user.nickname?.trim() || user.username }} |
|||
</p> |
|||
<p class="truncate text-xs text-muted"> |
|||
@{{ user.username }} · {{ user.role === 'admin' ? '管理员' : '用户' }} |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</UCard> |
|||
<UCard> |
|||
<p class="text-sm font-medium text-muted"> |
|||
公开主页 |
|||
</p> |
|||
<p |
|||
class="mt-1 text-base font-semibold leading-6 tabular-nums" |
|||
:class="user.publicSlug ? 'text-[#d46a31]' : 'text-muted'" |
|||
> |
|||
{{ user.publicSlug ? `/@${user.publicSlug}` : '尚未设置' }} |
|||
</p> |
|||
<UButton to="/me/profile" size="xs" variant="link" class="mt-2 px-0"> |
|||
去资料页配置 |
|||
</UButton> |
|||
</UCard> |
|||
<UCard class="sm:col-span-2"> |
|||
<p class="text-xs font-medium text-muted"> |
|||
快捷入口 |
|||
</p> |
|||
<div class="mt-2 flex flex-wrap gap-2"> |
|||
<UButton to="/me/posts" size="xs" variant="soft"> |
|||
文章 |
|||
</UButton> |
|||
<UButton to="/me/timeline" size="xs" variant="soft"> |
|||
时光机 |
|||
</UButton> |
|||
<UButton to="/me/rss" size="xs" variant="soft"> |
|||
RSS |
|||
</UButton> |
|||
<UButton to="/me/profile" size="xs" variant="soft"> |
|||
资料 |
|||
</UButton> |
|||
<UButton |
|||
v-if="user.role === 'admin'" |
|||
to="/me/admin/config" |
|||
size="xs" |
|||
color="primary" |
|||
variant="soft" |
|||
> |
|||
应用配置 |
|||
</UButton> |
|||
<UButton |
|||
v-if="user.role === 'admin'" |
|||
to="/me/admin/users" |
|||
size="xs" |
|||
color="primary" |
|||
variant="soft" |
|||
> |
|||
用户管理 |
|||
</UButton> |
|||
</div> |
|||
</UCard> |
|||
</section> |
|||
|
|||
<UAlert |
|||
v-if="loggedIn && user && !user.publicSlug" |
|||
color="warning" |
|||
variant="subtle" |
|||
title="还没有公开主页地址" |
|||
description="在「资料」中设置 public slug 后,访客即可通过 /@你的地址 访问你的公开内容。" |
|||
icon="i-lucide-alert-circle" |
|||
/> |
|||
|
|||
<!-- 功能矩阵 --> |
|||
<section class="space-y-4"> |
|||
<div> |
|||
<h2 class="text-xl font-semibold text-highlighted"> |
|||
你能做什么 |
|||
</h2> |
|||
<p class="mt-1 text-sm text-muted"> |
|||
四大模块覆盖展示、创作与阅读输入;权限与可见性按条目细粒度控制。 |
|||
</p> |
|||
</div> |
|||
<div class="grid gap-4 sm:grid-cols-2"> |
|||
<UCard |
|||
v-for="item in features" |
|||
:key="item.title" |
|||
class="group transition-shadow hover:shadow-md" |
|||
> |
|||
<div class="flex gap-4"> |
|||
<span class="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary"> |
|||
<UIcon :name="item.icon" class="size-6" /> |
|||
</span> |
|||
<div class="min-w-0 flex-1 space-y-2"> |
|||
<h3 class="font-semibold text-highlighted"> |
|||
<NuxtLink |
|||
:to="loggedIn ? item.to : '/login'" |
|||
class="rounded-sm hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" |
|||
> |
|||
{{ item.title }} |
|||
</NuxtLink> |
|||
</h3> |
|||
<p class="text-sm leading-relaxed text-muted"> |
|||
{{ item.desc }} |
|||
</p> |
|||
<UButton |
|||
:to="loggedIn ? item.to : '/login'" |
|||
size="xs" |
|||
variant="link" |
|||
class="px-0" |
|||
trailing-icon="i-lucide-arrow-right" |
|||
> |
|||
{{ loggedIn ? '打开' : '登录后使用' }} |
|||
</UButton> |
|||
</div> |
|||
</div> |
|||
</UCard> |
|||
</div> |
|||
</section> |
|||
|
|||
<!-- 访客说明 --> |
|||
<section v-if="!loggedIn" class="rounded-xl border border-dashed border-default bg-elevated/30 px-6 py-8"> |
|||
<h3 class="font-semibold text-highlighted"> |
|||
访客说明 |
|||
</h3> |
|||
<p class="mt-2 max-w-2xl text-sm text-muted leading-relaxed"> |
|||
本站默认需登录后使用控制台。若你已有账号,请点击「登录」;新账号由管理员在后台创建(邀请制)。 |
|||
</p> |
|||
<div class="mt-4 flex flex-wrap gap-2"> |
|||
<UButton to="/login"> |
|||
登录 |
|||
</UButton> |
|||
<UButton v-if="allowRegister" to="/register" color="neutral" variant="outline"> |
|||
注册 |
|||
</UButton> |
|||
</div> |
|||
</section> |
|||
|
|||
<!-- 已登录底部操作 --> |
|||
<section v-if="loggedIn" class="flex flex-wrap items-center justify-between gap-4 rounded-xl border border-default bg-elevated/20 px-4 py-4"> |
|||
<p class="text-sm text-muted"> |
|||
需要退出当前会话? |
|||
</p> |
|||
<div class="flex gap-2"> |
|||
<UButton to="/api/config/me" target="_blank" variant="outline" color="neutral" size="sm"> |
|||
配置 API(调试) |
|||
</UButton> |
|||
<UButton color="neutral" :loading="logoutLoading" size="sm" @click="logout"> |
|||
退出登录 |
|||
</UButton> |
|||
</div> |
|||
</section> |
|||
<div class="mx-auto max-w-6xl pb-4"> |
|||
<LoggedInHomeSection v-if="loggedIn && user" :user="user" /> |
|||
<GuestHomeSection v-else :site-name="siteName" :allow-register="allowRegister" /> |
|||
</div> |
|||
</template> |
|||
|
|||
@ -0,0 +1,67 @@ |
|||
<script setup lang="ts"> |
|||
import { useAuthSession } from '../../../composables/useAuthSession' |
|||
|
|||
const { user } = useAuthSession() |
|||
|
|||
usePageTitle('管理员中心') |
|||
</script> |
|||
|
|||
<template> |
|||
<UContainer class="py-8 space-y-6"> |
|||
<div> |
|||
<h1 class="text-2xl font-semibold"> |
|||
管理员中心 |
|||
</h1> |
|||
<p class="mt-1 text-sm text-muted"> |
|||
站点级配置、用户管理与媒体存储校验入口。 |
|||
</p> |
|||
</div> |
|||
|
|||
<UAlert |
|||
v-if="user?.role !== 'admin'" |
|||
color="error" |
|||
variant="subtle" |
|||
title="无权限访问" |
|||
description="该页面仅管理员可访问。" |
|||
icon="i-lucide-shield-alert" |
|||
/> |
|||
|
|||
<div v-else class="grid gap-3 sm:grid-cols-2"> |
|||
<UCard> |
|||
<div class="font-medium"> |
|||
应用配置 |
|||
</div> |
|||
<p class="mt-1 text-sm text-muted"> |
|||
站点名称、注册开关 |
|||
</p> |
|||
<UButton to="/me/admin/config" class="mt-3" size="sm"> |
|||
打开 |
|||
</UButton> |
|||
</UCard> |
|||
|
|||
<UCard> |
|||
<div class="font-medium"> |
|||
用户管理 |
|||
</div> |
|||
<p class="mt-1 text-sm text-muted"> |
|||
用户列表与权限管理 |
|||
</p> |
|||
<UButton to="/me/admin/users" class="mt-3" size="sm"> |
|||
打开 |
|||
</UButton> |
|||
</UCard> |
|||
|
|||
<UCard> |
|||
<div class="font-medium"> |
|||
文章媒体存储校验 |
|||
</div> |
|||
<p class="mt-1 text-sm text-muted"> |
|||
磁盘与 media_assets 一致性 |
|||
</p> |
|||
<UButton to="/me/admin/media-storage" class="mt-3" size="sm"> |
|||
打开 |
|||
</UButton> |
|||
</UCard> |
|||
</div> |
|||
</UContainer> |
|||
</template> |
|||
@ -0,0 +1,16 @@ |
|||
export const POST_SLUG_RE = /^[a-z0-9][a-z0-9-]{0,98}[a-z0-9]$|^[a-z0-9]$/ |
|||
|
|||
export function normalizePostSlugCandidate(input: string): string { |
|||
const ascii = input |
|||
.toLowerCase() |
|||
.normalize('NFKD') |
|||
.replace(/[\u0300-\u036f]/g, '') |
|||
.replace(/['"]/g, '') |
|||
.replace(/[^a-z0-9]+/g, '-') |
|||
.replace(/^-+|-+$/g, '') |
|||
.replace(/-{2,}/g, '-') |
|||
|
|||
const limited = ascii.slice(0, 100).replace(/-+$/g, '') |
|||
return limited |
|||
} |
|||
|
|||
Binary file not shown.
Loading…
Reference in new issue