Files
ai-write/src/components/HomePage.vue
empty 4212b63df2 feat: 添加主页组件并设为默认首页
- 新增 HomePage.vue 主页组件 (玻璃态设计风格)
- GlobalSidebar logo 改为可点击跳转主页
- 默认页面从 writer 改为 home
- 新增玻璃态 CSS 变量支持

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 03:56:40 +08:00

542 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="home-page">
<!-- Dynamic Background Decor -->
<div class="bg-decor">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
</div>
<!-- Hero Section -->
<header class="home-hero glass">
<div class="hero-content">
<div class="hero-icon-wrapper">
<div class="hero-icon-glow"></div>
<div class="hero-icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<h1 class="hero-title">AI 写作工坊</h1>
<p class="hero-subtitle">结构化内容生成平台</p>
<p class="hero-description">
通过智能范式系统实现高质量规范化的内容生成
</p>
</div>
</header>
<!-- Stats Section -->
<section class="home-stats">
<div v-for="stat in statConfig" :key="stat.label" class="stat-card glass-card">
<div class="stat-icon" :style="{ color: stat.color }">
<IconLibrary :name="stat.icon" :size="24" />
</div>
<div class="stat-info">
<span class="stat-value text-glow" :style="{ '--glow-color': stat.color }">{{ stats[stat.key] }}</span>
<span class="stat-label">{{ stat.label }}</span>
</div>
</div>
</section>
<!-- Content Sections Grid -->
<div class="content-grid">
<!-- Quick Actions -->
<section class="home-section quick-actions-section">
<h2 class="section-title">
<IconLibrary name="sparkles" :size="18" />
<span>快速开始</span>
</h2>
<div class="action-list">
<button
v-for="action in quickActions"
:key="action.id"
@click="navigateTo(action.id)"
class="action-card glass-card"
>
<div class="action-icon-box" :style="{ background: action.gradient }">
<IconLibrary :name="action.icon" :size="24" />
</div>
<div class="action-content">
<h3 class="action-title">{{ action.title }}</h3>
<p class="action-desc">{{ action.description }}</p>
</div>
<div class="action-arrow-box">
<IconLibrary name="expand" :size="16" class="action-arrow" />
</div>
</button>
</div>
</section>
<!-- All Features -->
<section class="home-section all-features-section">
<h2 class="section-title">
<IconLibrary name="list" :size="18" />
<span>全部功能</span>
</h2>
<div class="feature-grid">
<button
v-for="feature in features"
:key="feature.id"
@click="navigateTo(feature.id)"
class="feature-card glass-card"
>
<div class="feature-icon-wrapper">
<IconLibrary :name="feature.icon" :size="24" />
</div>
<span class="feature-name">{{ feature.name }}</span>
</button>
</div>
</section>
</div>
<!-- Footer -->
<footer class="home-footer">
<div class="footer-divider"></div>
<p class="footer-text">Pro · 基于 DeepSeek API · 极简高效</p>
</footer>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useAppStore } from '../stores/app'
import { useDatabaseStore } from '../stores/database'
import { getParadigmList } from '../config/paradigms'
import IconLibrary from './icons/IconLibrary.vue'
const appStore = useAppStore()
const dbStore = useDatabaseStore()
// 统计数据
const stats = ref({
documents: 0,
materials: 0,
paradigms: 0
})
const statConfig = [
{ key: 'documents', label: '文稿', icon: 'folder', color: '#60a5fa' },
{ key: 'materials', label: '素材', icon: 'chart', color: '#34d399' },
{ key: 'paradigms', label: '范式', icon: 'analysis', color: '#c084fc' }
]
// 快速操作
const quickActions = [
{
id: 'writer',
title: 'AI 写作',
description: '智能生成高质量内容',
icon: 'edit',
gradient: 'linear-gradient(135deg, #3b82f6, #6366f1)'
},
{
id: 'rewrite',
title: '范式润色',
description: '按范式标准优化文章',
icon: 'sparkles',
gradient: 'linear-gradient(135deg, #f59e0b, #ef4444)'
},
{
id: 'analysis',
title: '范式库',
description: '管理和创建写作范式',
icon: 'analysis',
gradient: 'linear-gradient(135deg, #10b981, #06b6d4)'
}
]
// 全部功能
const features = [
{ id: 'writer', name: 'AI 写作', icon: 'edit' },
{ id: 'analysis', name: '范式库', icon: 'analysis' },
{ id: 'paradigmWriter', name: '范式写作', icon: 'article' },
{ id: 'documents', name: '文稿库', icon: 'folder' },
{ id: 'materials', name: '素材库', icon: 'chart' },
{ id: 'rewrite', name: '范式润色', icon: 'sparkles' },
{ id: 'compare', name: '对照检查', icon: 'compare' },
{ id: 'diffAnnotation', name: '差异标注', icon: 'chart' }
]
// 导航
const navigateTo = (page) => {
appStore.setCurrentPage(page)
}
// 加载统计数据
onMounted(() => {
// 获取范式数量
const defaultParadigms = getParadigmList()
const customParadigms = JSON.parse(localStorage.getItem('customParadigms') || '[]')
stats.value.paradigms = defaultParadigms.length + customParadigms.length
// 获取文稿和素材数量
stats.value.documents = dbStore.documents?.length || 0
stats.value.materials = dbStore.references?.length || 0
})
</script>
<style scoped>
.home-page {
flex: 1;
overflow-y: auto;
padding: var(--space-8);
background: var(--bg-primary);
position: relative;
z-index: 1;
}
/* Background Decor */
.bg-decor {
position: fixed;
inset: 0;
overflow: hidden;
z-index: -1;
pointer-events: none;
}
.blob {
position: absolute;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(96, 165, 250, 0.15) 0%, transparent 70%);
border-radius: 50%;
filter: blur(60px);
animation: float 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
}
.blob-1 { top: -100px; left: -100px; animation-delay: 0s; }
.blob-2 { bottom: -150px; right: -100px; background: radial-gradient(circle, rgba(192, 132, 252, 0.1) 0%, transparent 70%); animation-delay: -5s; }
.blob-3 { top: 20%; right: 10%; background: radial-gradient(circle, rgba(52, 211, 153, 0.08) 0%, transparent 70%); animation-delay: -10s; }
@keyframes float {
from { transform: translate(0, 0) scale(1); }
to { transform: translate(100px, 50px) scale(1.1); }
}
/* Glass Effect */
.glass {
background: var(--glass-bg);
backdrop-filter: blur(var(--glass-blur));
-webkit-backdrop-filter: blur(var(--glass-blur));
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-xl);
}
.glass-card {
background: var(--glass-bg);
backdrop-filter: blur(var(--glass-blur));
-webkit-backdrop-filter: blur(var(--glass-blur));
border: 1px solid var(--glass-border);
transition: all var(--transition-normal);
}
.glass-card:hover {
background: var(--glass-bg-hover);
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-4px);
box-shadow: var(--shadow-xl), var(--shadow-glow);
}
/* Hero Section */
.home-hero {
text-align: center;
padding: var(--space-12) var(--space-6);
margin-bottom: var(--space-10);
border-radius: var(--radius-2xl);
position: relative;
overflow: hidden;
}
.home-hero::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(96, 165, 250, 0.05) 0%, transparent 50%, rgba(192, 132, 252, 0.05) 100%);
}
.hero-content {
position: relative;
z-index: 2;
max-width: 600px;
margin: 0 auto;
}
.hero-icon-wrapper {
position: relative;
display: inline-flex;
margin-bottom: var(--space-6);
}
.hero-icon-glow {
position: absolute;
inset: -10px;
background: var(--accent-primary);
filter: blur(20px);
opacity: 0.3;
border-radius: var(--radius-xl);
}
.hero-icon {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--accent-primary), #c084fc);
border-radius: var(--radius-xl);
color: white;
box-shadow: 0 10px 25px rgba(96, 165, 250, 0.4);
}
.hero-title {
font-size: var(--text-3xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--space-2);
letter-spacing: -0.02em;
}
.hero-subtitle {
font-size: var(--text-lg);
font-weight: var(--font-medium);
color: var(--accent-primary);
margin-bottom: var(--space-4);
background: linear-gradient(90deg, var(--accent-primary), #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero-description {
font-size: var(--text-base);
color: var(--text-secondary);
line-height: 1.6;
}
/* Stats Section */
.home-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-6);
margin-bottom: var(--space-10);
}
.stat-card {
display: flex;
align-items: center;
gap: var(--space-4);
padding: var(--space-6);
border-radius: var(--radius-xl);
}
.stat-icon {
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
background: rgba(255, 255, 255, 0.05);
border-radius: var(--radius-lg);
flex-shrink: 0;
}
.stat-info {
display: flex;
flex-direction: column;
}
.stat-value {
font-size: var(--text-2xl);
font-weight: var(--font-bold);
color: var(--text-primary);
line-height: 1;
margin-bottom: var(--space-1);
}
.text-glow {
text-shadow: 0 0 10px var(--glow-color, rgba(96, 165, 250, 0.3));
}
.stat-label {
font-size: var(--text-xs);
color: var(--text-muted);
font-weight: var(--font-medium);
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* Content Grid */
.content-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-8);
}
.section-title {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-sm);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: var(--space-5);
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 0.8;
}
/* Quick Actions */
.action-list {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.action-card {
display: flex;
align-items: center;
gap: var(--space-5);
padding: var(--space-5);
border-radius: var(--radius-xl);
cursor: pointer;
text-align: left;
width: 100%;
}
.action-icon-box {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
border-radius: var(--radius-lg);
color: white;
flex-shrink: 0;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.action-content {
flex: 1;
min-width: 0;
}
.action-title {
font-size: var(--text-base);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: var(--space-1);
}
.action-desc {
font-size: var(--text-sm);
color: var(--text-muted);
}
.action-arrow-box {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-full);
background: rgba(255, 255, 255, 0.05);
color: var(--text-muted);
transition: all var(--transition-fast);
}
.action-card:hover .action-arrow-box {
background: var(--accent-primary);
color: white;
transform: translateX(4px);
}
/* Feature Grid */
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: var(--space-4);
}
.feature-card {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-3);
padding: var(--space-6) var(--space-4);
border-radius: var(--radius-xl);
cursor: pointer;
}
.feature-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: rgba(255, 255, 255, 0.05);
border-radius: var(--radius-lg);
color: var(--text-secondary);
transition: all var(--transition-normal);
}
.feature-card:hover .feature-icon-wrapper {
background: var(--accent-primary);
color: white;
transform: scale(1.1) rotate(5deg);
}
.feature-name {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-secondary);
text-align: center;
}
.feature-card:hover .feature-name {
color: var(--text-primary);
}
/* Footer */
.home-footer {
margin-top: var(--space-12);
text-align: center;
}
.footer-divider {
height: 1px;
background: linear-gradient(90deg, transparent, var(--border-default), transparent);
margin-bottom: var(--space-6);
}
.footer-text {
font-size: var(--text-xs);
color: var(--text-muted);
letter-spacing: 0.02em;
}
/* Responsive */
@media (max-width: 1024px) {
.content-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.home-stats {
grid-template-columns: 1fr;
gap: var(--space-4);
}
.hero-title {
font-size: var(--text-2xl);
}
.feature-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>