mach-cms的前台,用vue写的
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

197 lines
4.6 KiB

<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useTheme } from '@/composables/useTheme'
const auth = useAuthStore()
const router = useRouter()
const route = useRoute()
const theme = useTheme()
const pageTitle = computed(() => {
const t = route.meta.title as string | undefined
return t || ''
})
const isAdminRoute = computed(() => route.path.startsWith('/admin'))
async function handleLogout() {
try {
await auth.logout()
} catch (e) {
console.error(e)
}
router.push('/')
}
</script>
<template>
<div>
<header class="app-header">
<router-link to="/" class="brand">Mach-CMS</router-link>
<span v-if="pageTitle" class="header-page-title"><span class="header-title-sep">×</span>{{ pageTitle }}</span>
<nav class="nav-links">
<router-link to="/">首页</router-link>
<router-link to="/search">搜索</router-link>
<router-link v-if="auth.isLoggedIn" to="/admin">管理后台</router-link>
<router-link v-if="auth.isAdmin" to="/admin/comments">评论审核</router-link>
<span v-if="auth.username" class="username">{{ auth.username }}</span>
<a v-if="auth.isLoggedIn" href="#" @click.prevent="handleLogout">退出</a>
<router-link v-else to="/login">登录</router-link>
</nav>
<button
class="theme-toggle"
type="button"
:title="theme.isDark ? '切至天青昼' : '切至黛墨夜'"
@click="theme.toggle()"
>{{ theme.isDark ? '昼' : '夜' }}</button>
</header>
<main class="app-main" :class="{ 'app-main--admin': isAdminRoute }">
<router-view v-slot="{ Component }">
<transition name="page" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</main>
<footer class="wave-decoration" aria-hidden="true">
<svg viewBox="0 0 1440 80" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 40c240 40 480-40 720 0s480 40 720 0v40H0z" fill="currentColor"/>
</svg>
</footer>
</div>
</template>
<style>
* {
box-sizing: border-box;
}
.app-header {
position: sticky;
top: 0;
z-index: 1000;
display: flex;
align-items: center;
gap: 20px;
padding: 0 24px;
height: 60px;
background-color: var(--ac-header-bg);
color: var(--ac-header-text);
border-bottom: 1px solid var(--ac-border);
transition: background-color var(--song-dur-2) var(--song-ease), color var(--song-dur-2) var(--song-ease);
}
.header-page-title {
font-family: var(--song-font-serif);
font-size: 16px;
font-weight: 600;
color: var(--ac-header-text);
}
.header-title-sep {
margin: 0 8px;
color: var(--ac-header-muted);
font-weight: 400;
}
.app-header .brand {
display: flex;
align-items: center;
height: 100%;
color: var(--ac-header-text);
font-family: var(--song-font-display);
font-size: 18px;
font-weight: 400;
letter-spacing: 0.06em;
text-decoration: none;
}
.nav-links {
display: flex;
align-items: center;
height: 100%;
gap: 6px;
margin-left: auto;
}
.nav-links a,
.nav-links .username {
color: var(--ac-header-muted);
text-decoration: none;
font-size: 14px;
}
.nav-links a {
display: inline-flex;
align-items: center;
height: 100%;
padding: 0 12px;
color: var(--ac-header-muted);
}
.nav-links a:hover {
color: var(--ac-header-text);
}
.nav-links a.router-link-active {
color: var(--ac-header-text);
background: var(--ac-header-active-bg);
}
.nav-links .username {
margin-left: 12px;
}
.theme-toggle {
width: 34px;
height: 34px;
flex-shrink: 0;
border: 1px solid var(--ac-border);
border-radius: var(--ac-radius-m);
background: transparent;
color: var(--ac-header-muted);
font-family: var(--song-font-serif);
font-size: 15px;
letter-spacing: 0.1em;
cursor: pointer;
transition: color var(--song-dur-1) var(--song-ease), border-color var(--song-dur-1) var(--song-ease), background var(--song-dur-1) var(--song-ease);
}
.theme-toggle:hover {
color: var(--ac-header-text);
border-color: var(--ac-header-muted);
background: var(--ac-header-active-bg);
}
.app-main {
margin: 24px auto;
padding: 0 16px;
min-height: calc(100vh - 60px - 80px);
max-width: 960px;
}
.app-main--admin {
max-width: none;
margin: 0;
padding: 0;
min-height: calc(100vh - 60px);
}
.wave-decoration {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 80px;
pointer-events: none;
z-index: -1;
color: var(--ac-primary);
opacity: 0.08;
}
.wave-decoration svg {
width: 100%;
height: 100%;
}
</style>