设计前端页面和布局框架

This commit is contained in:
2026-03-24 14:08:29 +08:00
parent 3afbc78c06
commit b7e9699dbd
49 changed files with 3188 additions and 6 deletions

View File

@@ -0,0 +1,19 @@
<template>
<div class="about">
<nav class="glass fixed top-0 left-0 right-0 z-50">
<div class="max-w-4xl mx-auto px-4 py-4">
<RouterLink to="/" class="text-acg-pink hover:underline"> 返回首页</RouterLink>
</div>
</nav>
<main class="pt-20 px-4 max-w-4xl mx-auto">
<div class="glass rounded-xl p-8">
<h1 class="text-3xl font-bold mb-4">关于 ACG Blog</h1>
<p class="text-gray-600 dark:text-gray-400 leading-relaxed">
ACG Blog 是一个专注于二次元文化的博客平台使用 Vue 3 + TypeScript + Naive UI 构建
在这里你可以分享你的二次元生活动漫评论游戏攻略等内容
</p>
</div>
</main>
</div>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
const categoryId = route.params.id as string
</script>
<template>
<div class="category">
<nav class="glass fixed top-0 left-0 right-0 z-50">
<div class="max-w-6xl mx-auto px-4 py-4">
<RouterLink to="/" class="text-acg-pink hover:underline"> 返回首页</RouterLink>
</div>
</nav>
<main class="pt-20 px-4 max-w-6xl mx-auto">
<div class="glass rounded-xl p-8">
<h1 class="text-2xl font-bold mb-4">分类 - {{ categoryId }}</h1>
<div class="text-gray-600 dark:text-gray-400">
分类内容加载中...
</div>
</div>
</main>
</div>
</template>

243
frontend/src/views/Home.vue Normal file
View File

@@ -0,0 +1,243 @@
<script setup lang="ts">
import { ref } from 'vue'
import Navbar from '@/components/Navbar.vue'
import Hero from '@/components/Hero.vue'
import PostCard from '@/components/PostCard.vue'
import Sidebar from '@/components/Sidebar.vue'
import Footer from '@/components/Footer.vue'
import type { Post } from '@/types'
// 模拟文章数据 - 后端完成后替换为真实API调用
const posts = ref<Post[]>([
{
id: '1',
title: '《原神》4.2版本前瞻:芙宁娜技能演示与剧情预测',
slug: 'genshin-4-2-preview',
content: '',
summary: '4.2版本即将到来,让我们提前了解水神芙宁娜的技能机制以及可能的剧情发展...',
cover_image: 'https://images.unsplash.com/photo-1542751371-adc38448a05e?w=800',
author: { id: '1', username: 'Miyako', email: '', is_active: true, created_at: '', updated_at: '' },
category: { id: '1', name: '游戏攻略', slug: 'game', created_at: '' },
tags: [{ id: '1', name: '原神', created_at: '' }, { id: '2', name: '攻略', created_at: '' }],
view_count: 5200,
status: 'published',
created_at: '2024-03-15T10:00:00Z',
updated_at: '2024-03-15T10:00:00Z'
},
{
id: '2',
title: '2024年必追的10部春季新番推荐',
slug: 'spring-2024-anime',
content: '',
summary: '春天到了又到了补番的季节本期为大家带来2024年春季最值得期待的新番动画...',
cover_image: 'https://images.unsplash.com/photo-1535016120720-40c6874c3b1c?w=800',
author: { id: '2', username: 'Akari', email: '', is_active: true, created_at: '', updated_at: '' },
category: { id: '2', name: '动漫资讯', slug: 'anime', created_at: '' },
tags: [{ id: '3', name: '新番', created_at: '' }, { id: '4', name: '推荐', created_at: '' }],
view_count: 3800,
status: 'published',
created_at: '2024-03-14T08:00:00Z',
updated_at: '2024-03-14T08:00:00Z'
},
{
id: '3',
title: '《崩坏星穹铁道》角色强度榜 - 3月版',
slug: 'honkai-star-rail-tier-list',
content: '',
summary: '版本强势角色有哪些本期强度榜为你详细分析当前版本的T0、T1角色...',
cover_image: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=800',
author: { id: '1', username: 'Miyako', email: '', is_active: true, created_at: '', updated_at: '' },
category: { id: '1', name: '游戏攻略', slug: 'game', created_at: '' },
tags: [{ id: '2', name: '崩坏星穹铁道', created_at: '' }, { id: '5', name: '强度榜', created_at: '' }],
view_count: 2900,
status: 'published',
created_at: '2024-03-13T15:30:00Z',
updated_at: '2024-03-13T15:30:00Z'
},
{
id: '4',
title: '手办入坑指南:从萌新到进阶的全方位攻略',
slug: 'figure-beginner-guide',
content: '',
summary: '想入手第一只手办但不知道如何选择?这篇指南将带你了解手办的各种分类、选购渠道...',
cover_image: 'https://images.unsplash.com/photo-1608889175123-8ee362201f81?w=800',
author: { id: '3', username: 'Sakura', email: '', is_active: true, created_at: '', updated_at: '' },
category: { id: '3', name: '二次元美图', slug: 'pictures', created_at: '' },
tags: [{ id: '6', name: '手办', created_at: '' }, { id: '7', name: '教程', created_at: '' }],
view_count: 2100,
status: 'published',
created_at: '2024-03-12T12:00:00Z',
updated_at: '2024-03-12T12:00:00Z'
},
{
id: '5',
title: '同人创作分享:用画笔描绘心中的二次元世界',
slug: 'fanart-sharing',
content: '',
summary: '本期收录了多位优秀画师的作品,让我们一起欣赏这些充满想象力的同人创作...',
cover_image: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=800',
author: { id: '2', username: 'Akari', email: '', is_active: true, created_at: '', updated_at: '' },
category: { id: '4', name: '同人创作', slug: 'fanwork', created_at: '' },
tags: [{ id: '8', name: '同人', created_at: '' }, { id: '9', name: '绘画', created_at: '' }],
view_count: 1800,
status: 'published',
created_at: '2024-03-11T09:00:00Z',
updated_at: '2024-03-11T09:00:00Z'
},
{
id: '6',
title: '《约定的梦幻岛》最新话解析:剧情走向深度分析',
slug: 'neverland-analysis',
content: '',
summary: '最新一话的剧情信息量巨大,让我们来深度分析一下剧情走向和角色心理...',
cover_image: 'https://images.unsplash.com/photo-1518709268805-4e9042af9f23?w=800',
author: { id: '4', username: 'Ken', email: '', is_active: true, created_at: '', updated_at: '' },
category: { id: '2', name: '动漫资讯', slug: 'anime', created_at: '' },
tags: [{ id: '10', name: '约定的梦幻岛', created_at: '' }, { id: '11', name: '分析', created_at: '' }],
view_count: 1500,
status: 'published',
created_at: '2024-03-10T20:00:00Z',
updated_at: '2024-03-10T20:00:00Z'
}
])
const categories = [
{ name: '全部', slug: '' },
{ name: '动漫资讯', slug: 'anime' },
{ name: '游戏攻略', slug: 'game' },
{ name: '二次元美图', slug: 'pictures' },
{ name: '同人创作', slug: 'fanwork' },
]
const selectedCategory = ref('')
</script>
<template>
<div class="home">
<Navbar />
<Hero />
<main class="main-container">
<!-- 分类筛选 -->
<section class="category-section">
<div class="category-buttons">
<button
v-for="cat in categories"
:key="cat.slug"
@click="selectedCategory = cat.slug"
class="category-btn"
:class="{ active: selectedCategory === cat.slug }"
>
{{ cat.name }}
</button>
</div>
</section>
<!-- 文章列表 + 侧边栏 -->
<div class="content-layout">
<!-- 文章列表 -->
<section class="posts-section">
<h2 class="section-title">最新文章</h2>
<div class="posts-grid">
<PostCard v-for="post in posts" :key="post.id" :post="post" />
</div>
</section>
<!-- 侧边栏 -->
<Sidebar />
</div>
</main>
<Footer />
</div>
</template>
<style scoped>
.home {
min-height: 100vh;
}
.main-container {
max-width: 1280px;
margin: 0 auto;
padding: 3rem 1rem;
}
.category-section {
margin-bottom: 2rem;
}
.category-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.75rem;
}
.category-btn {
padding: 0.5rem 1.25rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
color: #6B7280;
background: #F3F4F6;
border: none;
cursor: pointer;
transition: all 0.2s;
}
:global(.dark) .category-btn {
background: #374151;
color: #9CA3AF;
}
.category-btn:hover {
background: rgba(255, 183, 197, 0.3);
color: #FFB7C5;
}
.category-btn.active {
background: linear-gradient(135deg, #FFB7C5, #D4B5E6);
color: white;
}
.content-layout {
display: flex;
flex-direction: column;
gap: 2rem;
}
@media (min-width: 1024px) {
.content-layout {
flex-direction: row;
align-items: flex-start;
}
}
.posts-section {
flex: 1;
min-width: 0;
}
.section-title {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 1.5rem;
background: linear-gradient(135deg, #FFB7C5, #D4B5E6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.posts-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
@media (min-width: 640px) {
.posts-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.posts-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
</script>
<template>
<div class="not-found min-h-screen flex items-center justify-center px-4">
<div class="text-center">
<h1 class="text-6xl font-bold text-acg-pink mb-4">404</h1>
<p class="text-xl mb-6">页面不存在</p>
<RouterLink to="/" class="btn-acg">返回首页</RouterLink>
</div>
</div>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
const postId = route.params.id as string
</script>
<template>
<div class="post-detail">
<nav class="glass fixed top-0 left-0 right-0 z-50">
<div class="max-w-4xl mx-auto px-4 py-4">
<RouterLink to="/" class="text-acg-pink hover:underline"> 返回首页</RouterLink>
</div>
</nav>
<main class="pt-20 px-4 max-w-4xl mx-auto">
<div class="glass rounded-xl p-8">
<h1 class="text-3xl font-bold mb-4">文章详情 - {{ postId }}</h1>
<div class="text-gray-600 dark:text-gray-400">
文章内容加载中...
</div>
</div>
</main>
</div>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<div class="category-manage">
<h1 class="text-2xl font-bold mb-6">分类管理</h1>
<div class="glass rounded-xl p-6">
<p class="text-gray-600 dark:text-gray-400">分类管理功能开发中...</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<template>
<div class="dashboard">
<h1 class="text-2xl font-bold mb-6">仪表盘</h1>
<div class="grid gap-4 md:grid-cols-3">
<div class="glass rounded-xl p-6">
<h3 class="text-gray-600 dark:text-gray-400">文章总数</h3>
<p class="text-3xl font-bold text-acg-pink">0</p>
</div>
<div class="glass rounded-xl p-6">
<h3 class="text-gray-600 dark:text-gray-400">用户总数</h3>
<p class="text-3xl font-bold text-acg-blue">0</p>
</div>
<div class="glass rounded-xl p-6">
<h3 class="text-gray-600 dark:text-gray-400">总访问量</h3>
<p class="text-3xl font-bold text-acg-purple">0</p>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<div class="post-manage">
<h1 class="text-2xl font-bold mb-6">文章管理</h1>
<div class="glass rounded-xl p-6">
<p class="text-gray-600 dark:text-gray-400">文章管理功能开发中...</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<div class="tag-manage">
<h1 class="text-2xl font-bold mb-6">标签管理</h1>
<div class="glass rounded-xl p-6">
<p class="text-gray-600 dark:text-gray-400">标签管理功能开发中...</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,55 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const email = ref('')
const password = ref('')
async function handleLogin() {
// TODO: 实现登录逻辑
console.log('Login:', email.value, password.value)
router.push('/')
}
</script>
<template>
<div class="login min-h-screen flex items-center justify-center px-4">
<div class="glass rounded-xl p-8 w-full max-w-md">
<h1 class="text-2xl font-bold text-center mb-6">登录</h1>
<form @submit.prevent="handleLogin" class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">邮箱</label>
<input
v-model="email"
type="email"
placeholder="请输入邮箱"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-acg-pink"
required
/>
</div>
<div>
<label class="block text-sm font-medium mb-2">密码</label>
<input
v-model="password"
type="password"
placeholder="请输入密码"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-acg-pink"
required
/>
</div>
<button type="submit" class="w-full btn-acg">
登录
</button>
</form>
<p class="text-center mt-4 text-sm">
还没有账号
<RouterLink to="/register" class="text-acg-pink hover:underline">立即注册</RouterLink>
</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const username = ref('')
const email = ref('')
const password = ref('')
async function handleRegister() {
// TODO: 实现注册逻辑
console.log('Register:', username.value, email.value, password.value)
router.push('/login')
}
</script>
<template>
<div class="register min-h-screen flex items-center justify-center px-4">
<div class="glass rounded-xl p-8 w-full max-w-md">
<h1 class="text-2xl font-bold text-center mb-6">注册</h1>
<form @submit.prevent="handleRegister" class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">用户名</label>
<input
v-model="username"
type="text"
placeholder="请输入用户名"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-acg-pink"
required
/>
</div>
<div>
<label class="block text-sm font-medium mb-2">邮箱</label>
<input
v-model="email"
type="email"
placeholder="请输入邮箱"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-acg-pink"
required
/>
</div>
<div>
<label class="block text-sm font-medium mb-2">密码</label>
<input
v-model="password"
type="password"
placeholder="请输入密码"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-acg-pink"
required
/>
</div>
<button type="submit" class="w-full btn-acg">
注册
</button>
</form>
<p class="text-center mt-4 text-sm">
已有账号
<RouterLink to="/login" class="text-acg-pink hover:underline">立即登录</RouterLink>
</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
</script>
<template>
<div class="profile">
<nav class="glass fixed top-0 left-0 right-0 z-50">
<div class="max-w-4xl mx-auto px-4 py-4">
<RouterLink to="/" class="text-acg-pink hover:underline"> 返回首页</RouterLink>
</div>
</nav>
<main class="pt-20 px-4 max-w-4xl mx-auto">
<div class="glass rounded-xl p-8">
<h1 class="text-2xl font-bold mb-4">个人中心</h1>
<div v-if="userStore.user">
<p>用户名: {{ userStore.user.username }}</p>
<p>邮箱: {{ userStore.user.email }}</p>
</div>
<div v-else>
加载中...
</div>
</div>
</main>
</div>
</template>