173 lines
7.1 KiB
Vue
173 lines
7.1 KiB
Vue
|
|
<template>
|
||
|
|
<section id="work-detail" class="work-detail-page page-section block">
|
||
|
|
<button @click="$router.push('/works')" class="fixed top-8 right-8 z-[60] mix-blend-difference text-white hover:scale-110 transition-transform">
|
||
|
|
<div class="rounded-full border border-white/20 p-4 backdrop-blur-md bg-white/5"><i data-lucide="x" class="w-6 h-6"></i></div>
|
||
|
|
</button>
|
||
|
|
<div class="fixed top-0 left-0 h-1 bg-art-accent z-[60] w-0 transition-all duration-100" id="scroll-progress"></div>
|
||
|
|
|
||
|
|
<!-- Loading state -->
|
||
|
|
<div v-if="loading" class="fixed inset-0 bg-black flex items-center justify-center z-[99]">
|
||
|
|
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-art-accent"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Error state -->
|
||
|
|
<div v-else-if="error" class="fixed inset-0 bg-black flex flex-col items-center justify-center z-[99] p-8">
|
||
|
|
<p class="text-red-500 text-xl mb-4">{{ error }}</p>
|
||
|
|
<button @click="fetchWorkDetails" class="px-6 py-3 bg-art-accent text-white rounded hover:bg-opacity-80 transition-colors">
|
||
|
|
重试
|
||
|
|
</button>
|
||
|
|
<button @click="$router.push('/works')" class="mt-4 px-6 py-3 border border-white/30 text-white rounded hover:bg-white/5 transition-colors">
|
||
|
|
返回作品列表
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Work content -->
|
||
|
|
<div v-else class="relative w-full" id="work-detail-content">
|
||
|
|
<div class="relative h-[85vh] w-full overflow-hidden group">
|
||
|
|
<img
|
||
|
|
id="work-hero-img"
|
||
|
|
:src="work.heroImg"
|
||
|
|
class="absolute inset-0 w-full h-full object-cover filter grayscale group-hover:grayscale-0 transition-all duration-1000 scale-105"
|
||
|
|
:alt="work.title"
|
||
|
|
>
|
||
|
|
<div class="absolute inset-0 bg-black/40"></div>
|
||
|
|
<div class="absolute bottom-0 left-0 p-8 md:p-20 w-full">
|
||
|
|
<div class="flex items-end justify-between border-t border-white/30 pt-8 animate-slide-down">
|
||
|
|
<div>
|
||
|
|
<span id="work-category" class="font-mono text-art-accent text-sm tracking-[0.2em] uppercase mb-2 block">{{ work.category }}</span>
|
||
|
|
<h1 id="work-title" class="font-serif text-5xl md:text-8xl text-white leading-[0.9] mix-blend-overlay">{{ work.title }}</h1>
|
||
|
|
</div>
|
||
|
|
<span id="work-year" class="hidden md:block font-mono text-white/50 text-xl">{{ work.year }}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="flex flex-col md:flex-row max-w-[1920px] mx-auto min-h-screen">
|
||
|
|
<div class="md:w-1/3 p-8 md:p-16 md:sticky md:top-0 md:h-screen md:max-h-screen overflow-y-auto custom-scrollbar flex flex-col justify-between border-r border-white/5 bg-[#050505]">
|
||
|
|
<div class="space-y-12">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-mono text-xs text-art-muted uppercase tracking-widest mb-4">关于项目</h3>
|
||
|
|
<div id="work-desc" class="text-white/80 font-light leading-relaxed text-lg font-serif" v-html="work.desc"></div>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-mono text-xs text-art-muted uppercase tracking-widest mb-6">技术全景</h3>
|
||
|
|
<div id="work-tech-stack" class="space-y-6">
|
||
|
|
<div v-for="(tech, index) in work.techStack" :key="index">
|
||
|
|
<div class="tech-category-title">{{ tech.category }}</div>
|
||
|
|
<div class="tech-grid">
|
||
|
|
<span v-for="(item, itemIndex) in tech.items" :key="itemIndex" class="tech-item">{{ item }}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="mt-12">
|
||
|
|
<a
|
||
|
|
id="work-link-live"
|
||
|
|
:href="work.links.live"
|
||
|
|
target="_blank"
|
||
|
|
class="group flex items-center justify-between w-full py-6 border-t border-white/10 hover:bg-white/5 transition-colors"
|
||
|
|
>
|
||
|
|
<span class="font-serif text-2xl italic text-white group-hover:text-art-accent transition-colors">访问线上项目</span>
|
||
|
|
<i data-lucide="arrow-up-right" class="w-6 h-6 text-white group-hover:rotate-45 transition-transform"></i>
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="md:w-2/3 bg-[#080808] flex flex-col">
|
||
|
|
<div id="work-gallery" class="flex flex-col flex-grow">
|
||
|
|
<img
|
||
|
|
v-for="(img, index) in work.gallery"
|
||
|
|
:key="index"
|
||
|
|
:src="img"
|
||
|
|
:alt="`${work.title} - 图片 ${index + 1}`"
|
||
|
|
class="gallery-image"
|
||
|
|
>
|
||
|
|
</div>
|
||
|
|
<div
|
||
|
|
class="h-[40vh] flex items-center justify-center border-t border-white/5 bg-[#050505] cursor-pointer group hover:bg-white/5 transition-colors"
|
||
|
|
@click="$router.push('/works')"
|
||
|
|
>
|
||
|
|
<div class="text-center">
|
||
|
|
<p class="font-mono text-xs text-art-muted mb-4 tracking-widest">下一个作品</p>
|
||
|
|
<h2 class="font-serif text-5xl text-white group-hover:italic transition-all">返回作品列表</h2>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<footer class="py-8 text-center border-t border-white/5 relative z-10 bg-[#050505]"><p class="text-art-muted text-xs font-mono tracking-widest opacity-50">© 2024 年糕崽崽. 保留所有权利.</p></footer>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||
|
|
import { useRoute, useRouter } from 'vue-router'
|
||
|
|
import { fetchWork, Work } from '../services/api'
|
||
|
|
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||
|
|
|
||
|
|
const route = useRoute()
|
||
|
|
const router = useRouter()
|
||
|
|
const workId = route.params.id as string
|
||
|
|
|
||
|
|
const work = ref<Work>({
|
||
|
|
id: workId,
|
||
|
|
title: '加载中...',
|
||
|
|
category: '',
|
||
|
|
year: '',
|
||
|
|
heroImg: 'https://via.placeholder.com/1600x900',
|
||
|
|
desc: '<p>加载中...</p>',
|
||
|
|
techStack: [],
|
||
|
|
gallery: [],
|
||
|
|
links: { live: '#' },
|
||
|
|
next: ''
|
||
|
|
})
|
||
|
|
|
||
|
|
const loading = ref(true)
|
||
|
|
const error = ref('')
|
||
|
|
|
||
|
|
const updateScrollProgress = () => {
|
||
|
|
const scrollTop = window.scrollY
|
||
|
|
const docHeight = document.documentElement.scrollHeight
|
||
|
|
const winHeight = window.innerHeight
|
||
|
|
const scrollPercent = scrollTop / (docHeight - winHeight)
|
||
|
|
const progressBar = document.getElementById('scroll-progress')
|
||
|
|
if (progressBar) {
|
||
|
|
progressBar.style.width = `${scrollPercent * 100}%`
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const fetchWorkDetails = async () => {
|
||
|
|
loading.value = true
|
||
|
|
error.value = ''
|
||
|
|
try {
|
||
|
|
const workData = await fetchWork(workId)
|
||
|
|
if (workData) {
|
||
|
|
work.value = workData
|
||
|
|
} else {
|
||
|
|
error.value = '未找到该作品'
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
error.value = '获取作品详情失败,请稍后重试'
|
||
|
|
console.error('Error fetching work details:', err)
|
||
|
|
} finally {
|
||
|
|
loading.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
// 启用滚动动画
|
||
|
|
useScrollAnimation()
|
||
|
|
|
||
|
|
fetchWorkDetails()
|
||
|
|
// 初始化Lucide图标
|
||
|
|
if (window.lucide) {
|
||
|
|
window.lucide.createIcons()
|
||
|
|
}
|
||
|
|
// 添加滚动事件监听
|
||
|
|
window.addEventListener('scroll', updateScrollProgress)
|
||
|
|
})
|
||
|
|
|
||
|
|
onBeforeUnmount(() => {
|
||
|
|
// 移除滚动事件监听
|
||
|
|
window.removeEventListener('scroll', updateScrollProgress)
|
||
|
|
})
|
||
|
|
</script>
|