117 lines
4.5 KiB
Vue
117 lines
4.5 KiB
Vue
<template>
|
|
<section id="works" class="page-section block animate-slide-down">
|
|
<div class="max-w-7xl mx-auto pt-32 px-6 pb-20">
|
|
<div class="mb-32">
|
|
<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">
|
|
<p>{{ error }}</p>
|
|
<button @click="fetchWorksData" class="mt-4 px-4 py-2 bg-art-accent text-white rounded hover:bg-opacity-80 transition-colors">
|
|
重试
|
|
</button>
|
|
</div>
|
|
<!-- Works list -->
|
|
<div v-else class="grid grid-cols-1 gap-20" id="works-list-container">
|
|
<div
|
|
v-for="(work, index) in works"
|
|
:key="work.id"
|
|
class="group grid grid-cols-1 md:grid-cols-2 gap-10 items-center cursor-pointer"
|
|
@click="$router.push('/works/' + work.id)"
|
|
>
|
|
<!-- 作品图片 -->
|
|
<div
|
|
:class="['md:order-1', index % 2 === 1 ? 'md:order-2' : 'md:order-1']"
|
|
class="relative aspect-[4/3] overflow-hidden rounded-sm bg-gray-900 border border-white/5"
|
|
>
|
|
<div
|
|
class="absolute inset-0 z-10"
|
|
:class="[
|
|
index % 2 === 0 ? 'bg-gradient-to-tr from-purple-900/40 to-blue-900/40' : 'bg-gradient-to-tr from-orange-900/40 to-red-900/40',
|
|
'mix-blend-color-burn'
|
|
]"
|
|
></div>
|
|
<div class="absolute inset-0 bg-black/30 group-hover:bg-transparent transition-colors z-20"></div>
|
|
<img
|
|
:src="work.heroImg"
|
|
:alt="work.title"
|
|
class="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-700 opacity-80"
|
|
>
|
|
</div>
|
|
<!-- 作品信息 -->
|
|
<div
|
|
:class="['md:order-2', index % 2 === 1 ? 'md:order-1 md:text-right' : 'md:order-2']"
|
|
class="space-y-6"
|
|
>
|
|
<!-- 年份和分类 -->
|
|
<div class="font-mono text-xs text-art-accent">{{ work.year }} · {{ work.category }}</div>
|
|
<!-- 作品标题 -->
|
|
<h3
|
|
class="text-4xl font-serif text-white group-hover:text-art-accent transition-colors"
|
|
>
|
|
{{ work.title }}
|
|
</h3>
|
|
<!-- 作品描述 -->
|
|
<p class="text-art-muted font-light leading-relaxed" v-html="work.desc"></p>
|
|
<!-- 技术栈 -->
|
|
<div class="flex flex-wrap gap-3" :class="[index % 2 === 1 ? 'md:justify-end' : '']">
|
|
<span
|
|
v-for="(stack, stackIndex) in work.techStack.flatMap(tech => tech.items)"
|
|
:key="stackIndex"
|
|
class="px-3 py-1 border border-white/10 rounded-full text-xs text-white/70"
|
|
>
|
|
{{ stack }}
|
|
</span>
|
|
</div>
|
|
<!-- 查看详情按钮 -->
|
|
<div class="pt-4" :class="[index % 2 === 1 ? 'flex md:justify-end' : '']">
|
|
<span class="inline-flex items-center gap-2 text-sm font-medium text-white border-b border-transparent hover:border-art-accent hover:text-art-accent transition-all">
|
|
查看项目详情 <i data-lucide="arrow-right" class="w-4 h-4"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { fetchWorks, Work } from '../services/api'
|
|
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
|
|
|
const works = ref<Work[]>([])
|
|
const loading = ref(false)
|
|
const error = ref('')
|
|
|
|
const { initObserver } = useScrollAnimation()
|
|
|
|
const fetchWorksData = async () => {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
works.value = await fetchWorks()
|
|
// 初始化动画 - 在数据加载完成后
|
|
initObserver()
|
|
} catch (err) {
|
|
error.value = '获取作品失败,请稍后重试'
|
|
console.error('Error fetching works:', err)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
fetchWorksData()
|
|
// 初始化Lucide图标
|
|
if (window.lucide) {
|
|
window.lucide.createIcons()
|
|
}
|
|
})
|
|
</script> |