初始化
This commit is contained in:
173
client/src/pages/WorkDetail.vue
Normal file
173
client/src/pages/WorkDetail.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user