3d68f56a94
✨ Work 详情页 (/work/[slug]): - Hero 大图 + 渐变遮罩 - 项目概述、挑战、解决方案 - 项目截图画廊 - 右侧信息卡片(客户、年份、周期、服务) - 访问项目按钮 - 相关项目推荐 - 返回列表按钮 - 完整中英文内容 ✨ News 详情页 (/news/[slug]): - Hero 大图 + 渐变遮罩 - 分类标签 + 日期 + 阅读时间 + 作者 - 文章正文(多段落 + 引用 + 图片) - 分享按钮(Twitter、LinkedIn、复制链接) - 相关文章推荐 - 返回列表按钮 - 完整中英文内容 🔧 列表页更新: - work.vue → work/index.vue - news.vue → news/index.vue - 卡片链接改为 NuxtLink 到详情页 🌍 国际化: - 6 个作品详情(中文 + 英文) - 6 篇文章详情(中文 + 英文) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
290 lines
10 KiB
Vue
290 lines
10 KiB
Vue
<template>
|
||
<main class="min-h-screen">
|
||
<!-- 返回按钮 -->
|
||
<div class="fixed top-28 left-4 md:left-8 z-30">
|
||
<NuxtLink
|
||
to="/work"
|
||
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-full bg-white/90 backdrop-blur-sm shadow-lg hover:shadow-xl transition-all duration-300 text-sm font-semibold text-black hover:-translate-x-1"
|
||
>
|
||
<Icon name="ph:arrow-left" class="w-5 h-5" />
|
||
{{ t('work.backToList') }}
|
||
</NuxtLink>
|
||
</div>
|
||
|
||
<!-- Hero 大图 -->
|
||
<section class="relative h-[60vh] md:h-[70vh] overflow-hidden">
|
||
<img
|
||
:src="project.image"
|
||
:alt="project.title"
|
||
class="w-full h-full object-cover"
|
||
/>
|
||
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent"></div>
|
||
<div class="absolute bottom-0 left-0 right-0 p-8 md:p-16">
|
||
<div class="max-w-[1200px] mx-auto">
|
||
<div class="flex flex-wrap gap-2 mb-4">
|
||
<span
|
||
v-for="tag in project.tags"
|
||
:key="tag"
|
||
class="px-3 py-1 text-xs md:text-sm bg-white/20 backdrop-blur-sm text-white rounded-full"
|
||
>
|
||
{{ tag }}
|
||
</span>
|
||
</div>
|
||
<h1 class="text-4xl md:text-6xl lg:text-7xl font-bold text-white mb-4 tracking-tight">
|
||
{{ project.title }}
|
||
</h1>
|
||
<p class="text-lg md:text-xl text-white/80 max-w-2xl">
|
||
{{ project.description }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 项目信息 -->
|
||
<section class="max-w-[1200px] mx-auto px-6 md:px-10 py-16 md:py-24">
|
||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12 lg:gap-16">
|
||
<!-- 左侧:项目详情 -->
|
||
<div class="lg:col-span-2 space-y-12">
|
||
<div>
|
||
<h2 class="text-2xl md:text-3xl font-bold text-black mb-6">{{ t('work.detail.overview') }}</h2>
|
||
<p class="text-gray-700 text-base md:text-lg leading-relaxed">
|
||
{{ t(`work.detail.items.${slug}.overview`) }}
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<h2 class="text-2xl md:text-3xl font-bold text-black mb-6">{{ t('work.detail.challenge') }}</h2>
|
||
<p class="text-gray-700 text-base md:text-lg leading-relaxed">
|
||
{{ t(`work.detail.items.${slug}.challenge`) }}
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<h2 class="text-2xl md:text-3xl font-bold text-black mb-6">{{ t('work.detail.solution') }}</h2>
|
||
<p class="text-gray-700 text-base md:text-lg leading-relaxed">
|
||
{{ t(`work.detail.items.${slug}.solution`) }}
|
||
</p>
|
||
</div>
|
||
|
||
<!-- 项目截图 -->
|
||
<div class="space-y-6">
|
||
<h2 class="text-2xl md:text-3xl font-bold text-black">{{ t('work.detail.gallery') }}</h2>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div
|
||
v-for="(img, index) in project.gallery"
|
||
:key="index"
|
||
class="rounded-2xl overflow-hidden shadow-lg hover:shadow-2xl transition-shadow duration-300"
|
||
>
|
||
<img :src="img" :alt="`${project.title} screenshot ${index + 1}`" class="w-full h-auto" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:项目信息卡片 -->
|
||
<div class="space-y-8">
|
||
<div class="bg-gray-50 rounded-2xl p-6 md:p-8 sticky top-32">
|
||
<h3 class="text-lg font-bold text-black mb-6">{{ t('work.detail.projectInfo') }}</h3>
|
||
|
||
<div class="space-y-5">
|
||
<div>
|
||
<p class="text-sm text-gray-500 mb-1">{{ t('work.detail.client') }}</p>
|
||
<p class="text-base font-semibold text-black">{{ t(`work.detail.items.${slug}.client`) }}</p>
|
||
</div>
|
||
<div>
|
||
<p class="text-sm text-gray-500 mb-1">{{ t('work.detail.year') }}</p>
|
||
<p class="text-base font-semibold text-black">{{ project.year }}</p>
|
||
</div>
|
||
<div>
|
||
<p class="text-sm text-gray-500 mb-1">{{ t('work.detail.duration') }}</p>
|
||
<p class="text-base font-semibold text-black">{{ t(`work.detail.items.${slug}.duration`) }}</p>
|
||
</div>
|
||
<div>
|
||
<p class="text-sm text-gray-500 mb-1">{{ t('work.detail.services') }}</p>
|
||
<div class="flex flex-wrap gap-2 mt-2">
|
||
<span
|
||
v-for="tag in project.tags"
|
||
:key="tag"
|
||
class="px-3 py-1 text-xs bg-black text-white rounded-full"
|
||
>
|
||
{{ tag }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 访问项目按钮 -->
|
||
<a
|
||
v-if="project.url"
|
||
:href="project.url"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
class="mt-8 w-full inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-full bg-black text-white font-semibold hover:bg-gray-800 transition-colors duration-300"
|
||
>
|
||
{{ t('work.detail.visitProject') }}
|
||
<Icon name="ph:arrow-up-right" class="w-5 h-5" />
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 相关项目 -->
|
||
<section class="bg-gray-50 py-16 md:py-24">
|
||
<div class="max-w-[1200px] mx-auto px-6 md:px-10">
|
||
<h2 class="text-2xl md:text-3xl font-bold text-black mb-10 text-center">{{ t('work.detail.relatedProjects') }}</h2>
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<NuxtLink
|
||
v-for="related in relatedProjects"
|
||
:key="related.id"
|
||
:to="`/work/${related.id}`"
|
||
class="group block rounded-2xl overflow-hidden bg-white shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-1"
|
||
>
|
||
<div class="relative aspect-[4/3] overflow-hidden">
|
||
<img :src="related.image" :alt="related.title" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" />
|
||
</div>
|
||
<div class="p-5">
|
||
<h3 class="text-lg font-bold text-black group-hover:text-gray-600 transition-colors">{{ related.title }}</h3>
|
||
<p class="text-sm text-gray-500 mt-1">{{ related.tags[0] }}</p>
|
||
</div>
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { useSeoMeta, createError } from '#imports'
|
||
|
||
const { t } = useI18n()
|
||
const route = useRoute()
|
||
const slug = route.params.slug as string
|
||
|
||
interface WorkItem {
|
||
id: string
|
||
title: string
|
||
description: string
|
||
image: string
|
||
tags: string[]
|
||
category: string
|
||
year: string
|
||
url?: string
|
||
gallery: string[]
|
||
}
|
||
|
||
const workData: Record<string, WorkItem> = {
|
||
'forma-digital': {
|
||
id: 'forma-digital',
|
||
title: 'Forma Digital',
|
||
description: 'A modern e-commerce platform with seamless user experience and dynamic product showcases.',
|
||
image: 'https://picsum.photos/id/201/1200/800',
|
||
tags: ['Web Design', 'E-commerce', 'React'],
|
||
category: 'webDesign',
|
||
year: '2026',
|
||
url: '#',
|
||
gallery: [
|
||
'https://picsum.photos/id/201/800/600',
|
||
'https://picsum.photos/id/202/800/600',
|
||
'https://picsum.photos/id/203/800/600',
|
||
'https://picsum.photos/id/204/800/600',
|
||
]
|
||
},
|
||
'one-step': {
|
||
id: 'one-step',
|
||
title: 'One Step',
|
||
description: 'Brand identity and marketing campaign for a fitness app focused on sustainable habits.',
|
||
image: 'https://picsum.photos/id/180/1200/800',
|
||
tags: ['Branding', 'Marketing', 'UI/UX'],
|
||
category: 'branding',
|
||
year: '2024',
|
||
gallery: [
|
||
'https://picsum.photos/id/180/800/600',
|
||
'https://picsum.photos/id/181/800/600',
|
||
'https://picsum.photos/id/182/800/600',
|
||
'https://picsum.photos/id/183/800/600',
|
||
]
|
||
},
|
||
'nero-vision': {
|
||
id: 'nero-vision',
|
||
title: 'Nero Vision',
|
||
description: 'Full-stack web application for creative professionals to manage their portfolios.',
|
||
image: 'https://picsum.photos/id/119/1200/800',
|
||
tags: ['Development', 'Vue.js', 'Node.js'],
|
||
category: 'development',
|
||
year: '2025',
|
||
gallery: [
|
||
'https://picsum.photos/id/119/800/600',
|
||
'https://picsum.photos/id/120/800/600',
|
||
'https://picsum.photos/id/121/800/600',
|
||
'https://picsum.photos/id/122/800/600',
|
||
]
|
||
},
|
||
'bold-moves': {
|
||
id: 'bold-moves',
|
||
title: 'Bold Moves',
|
||
description: 'Interactive website showcasing innovative architectural designs with 3D visualizations.',
|
||
image: 'https://picsum.photos/id/164/1200/800',
|
||
tags: ['Web Design', '3D', 'Three.js'],
|
||
category: 'webDesign',
|
||
year: '2024',
|
||
gallery: [
|
||
'https://picsum.photos/id/164/800/600',
|
||
'https://picsum.photos/id/165/800/600',
|
||
'https://picsum.photos/id/166/800/600',
|
||
'https://picsum.photos/id/167/800/600',
|
||
]
|
||
},
|
||
'echo-labs': {
|
||
id: 'echo-labs',
|
||
title: 'Echo Labs',
|
||
description: 'Corporate branding and visual identity for a tech startup in the AI space.',
|
||
image: 'https://picsum.photos/id/101/1200/800',
|
||
tags: ['Branding', 'Visual Identity', 'Corporate'],
|
||
category: 'branding',
|
||
year: '2025',
|
||
gallery: [
|
||
'https://picsum.photos/id/101/800/600',
|
||
'https://picsum.photos/id/102/800/600',
|
||
'https://picsum.photos/id/103/800/600',
|
||
'https://picsum.photos/id/104/800/600',
|
||
]
|
||
},
|
||
'flux-api': {
|
||
id: 'flux-api',
|
||
title: 'Flux API',
|
||
description: 'Scalable REST API with comprehensive documentation and developer tools.',
|
||
image: 'https://picsum.photos/id/326/1200/800',
|
||
tags: ['Development', 'API', 'Python'],
|
||
category: 'development',
|
||
year: '2025',
|
||
gallery: [
|
||
'https://picsum.photos/id/326/800/600',
|
||
'https://picsum.photos/id/327/800/600',
|
||
'https://picsum.photos/id/328/800/600',
|
||
'https://picsum.photos/id/329/800/600',
|
||
]
|
||
}
|
||
}
|
||
|
||
const project = workData[slug]
|
||
|
||
if (!project) {
|
||
throw createError({ statusCode: 404, statusMessage: 'Project not found' })
|
||
}
|
||
|
||
const relatedProjects = computed(() => {
|
||
return Object.values(workData)
|
||
.filter(p => p.id !== slug)
|
||
.slice(0, 3)
|
||
})
|
||
|
||
useSeoMeta({
|
||
title: () => `${project.title} - ${t('work.title')}`,
|
||
description: () => project.description,
|
||
})
|
||
</script>
|