初始化
This commit is contained in:
152
client/src/pages/Snippets.vue
Normal file
152
client/src/pages/Snippets.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<section id="snippets" class="page-section block animate-slide-down">
|
||||
<div class="max-w-7xl mx-auto pt-32 px-6 pb-20">
|
||||
<div class="mb-12 text-center">
|
||||
<h2 class="font-serif text-5xl italic text-white mb-6">代码实验室</h2>
|
||||
<p class="text-art-muted">点击下方卡片查看代码与实时效果。</p>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-if="loading" class="flex justify-center items-center h-64">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-art-accent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div v-else-if="error" class="text-center text-red-500 py-20">
|
||||
<p class="mb-4">{{ error }}</p>
|
||||
<button @click="fetchSnippetsData" class="px-4 py-2 bg-art-accent text-white rounded hover:bg-opacity-80 transition-colors">
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Snippets list -->
|
||||
<div v-else class="grid grid-cols-1 lg:grid-cols-2 gap-8" id="snippets-grid">
|
||||
<!-- Snippets will be rendered here -->
|
||||
<div
|
||||
v-for="snippet in snippets"
|
||||
:key="snippet.id"
|
||||
class="art-card rounded-2xl p-8 group cursor-pointer transition-all hover:translate-y-[-5px]"
|
||||
@click="openSnippet(snippet)"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<i data-lucide="code-2" class="w-6 h-6 text-art-accent"></i>
|
||||
<span class="font-mono text-sm text-art-muted">{{ getSnippetType(snippet.type) }}</span>
|
||||
</div>
|
||||
<h3 class="text-2xl font-serif text-white mb-4 group-hover:text-art-accent transition-colors">{{ snippet.title }}</h3>
|
||||
<pre class="font-mono text-sm text-art-muted leading-relaxed overflow-hidden text-ellipsis line-clamp-4">{{ snippet.code }}</pre>
|
||||
<div class="flex items-center justify-end mt-6">
|
||||
<i data-lucide="arrow-up-right" class="w-5 h-5 text-white/50 transform group-hover:translate-x-1 group-hover:-translate-y-1 transition-transform"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeMount } from 'vue'
|
||||
import { fetchSnippets, Snippet } from '../services/api'
|
||||
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||||
|
||||
const snippets = ref<Snippet[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
const fetchSnippetsData = async () => {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const snippetsData = await fetchSnippets()
|
||||
snippets.value = snippetsData
|
||||
} catch (err) {
|
||||
console.error('Error fetching snippets:', err)
|
||||
error.value = '获取代码片段失败,请稍后重试'
|
||||
// 如果API调用失败,使用静态数据作为备选
|
||||
snippets.value = [
|
||||
{
|
||||
id: 'mouse',
|
||||
title: 'React 鼠标追踪 Hook',
|
||||
code: `import { useState, useEffect } from 'react';
|
||||
|
||||
export const useMousePosition = () => {
|
||||
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||
useEffect(() => {
|
||||
const update = (e) => setPos({ x: e.clientX, y: e.clientY });
|
||||
window.addEventListener('mousemove', update);
|
||||
return () => window.removeEventListener('mousemove', update);
|
||||
}, []);
|
||||
return pos;
|
||||
};`,
|
||||
type: 'mouse'
|
||||
},
|
||||
{
|
||||
id: 'glass',
|
||||
title: 'CSS 极致毛玻璃效果',
|
||||
code: `.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
}`,
|
||||
type: 'glass'
|
||||
},
|
||||
{
|
||||
id: 'noise',
|
||||
title: 'SVG 噪点纹理滤镜',
|
||||
code: `<filter id="noise">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.8" /
|
||||
</filter>`,
|
||||
type: 'noise'
|
||||
},
|
||||
{
|
||||
id: 'animate',
|
||||
title: 'CSS 流畅动画',
|
||||
code: `.animate-float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-20px); }
|
||||
}`,
|
||||
type: 'animate'
|
||||
}
|
||||
]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getSnippetType = (type: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
javascript: 'JavaScript',
|
||||
css: 'CSS',
|
||||
html: 'HTML',
|
||||
mouse: 'React Hook',
|
||||
glass: 'CSS',
|
||||
noise: 'SVG',
|
||||
animate: 'CSS Animation'
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
|
||||
const openSnippet = (snippet: any) => {
|
||||
// 使用全局函数打开代码片段模态框
|
||||
if (window.showSnippet) {
|
||||
window.showSnippet(snippet.title, snippet.code)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
fetchSnippetsData()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 启用滚动动画
|
||||
useScrollAnimation()
|
||||
|
||||
// 初始化Lucide图标
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user