Files
陈春 3d68f56a94 feat: 新增作品和动态详情页
 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>
2026-06-03 20:35:02 +08:00

237 lines
8.2 KiB
Vue

<template>
<main class="min-h-screen">
<!-- 返回按钮 -->
<div class="fixed top-28 left-4 md:left-8 z-30">
<NuxtLink
to="/news"
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('news.backToList') }}
</NuxtLink>
</div>
<!-- 文章 Hero -->
<section class="relative h-[50vh] md:h-[60vh] overflow-hidden">
<img
:src="article.image"
:alt="article.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-[800px] mx-auto">
<span
class="inline-block px-3 py-1.5 text-xs md:text-sm font-semibold rounded-full mb-4"
:class="article.badge"
>
{{ article.category }}
</span>
<h1 class="text-3xl md:text-5xl lg:text-6xl font-bold text-white mb-4 tracking-tight">
{{ article.title }}
</h1>
<div class="flex items-center gap-4 text-white/80 text-sm md:text-base">
<span class="flex items-center gap-1.5">
<Icon name="ph:calendar-blank" class="w-4 h-4" />
{{ article.date }}
</span>
<span class="flex items-center gap-1.5">
<Icon name="ph:clock" class="w-4 h-4" />
{{ article.readTime }}
</span>
<span class="flex items-center gap-1.5">
<Icon name="ph:user" class="w-4 h-4" />
{{ article.author }}
</span>
</div>
</div>
</div>
</section>
<!-- 文章内容 -->
<section class="max-w-[800px] mx-auto px-6 md:px-10 py-16 md:py-24">
<article class="prose prose-lg max-w-none">
<p class="text-gray-700 text-lg leading-relaxed mb-8">
{{ t(`news.detail.items.${slug}.content`) }}
</p>
<h2 class="text-2xl md:text-3xl font-bold text-black mb-6 mt-12">
{{ t(`news.detail.items.${slug}.section1Title`) }}
</h2>
<p class="text-gray-700 text-lg leading-relaxed mb-8">
{{ t(`news.detail.items.${slug}.section1Content`) }}
</p>
<!-- 引用 -->
<blockquote class="border-l-4 border-black pl-6 my-10 italic text-gray-600 text-lg">
"{{ t(`news.detail.items.${slug}.quote`) }}"
</blockquote>
<h2 class="text-2xl md:text-3xl font-bold text-black mb-6 mt-12">
{{ t(`news.detail.items.${slug}.section2Title`) }}
</h2>
<p class="text-gray-700 text-lg leading-relaxed mb-8">
{{ t(`news.detail.items.${slug}.section2Content`) }}
</p>
<!-- 文章内图片 -->
<div class="my-10 rounded-2xl overflow-hidden">
<img :src="article.image" :alt="article.title" class="w-full h-auto" />
</div>
<h2 class="text-2xl md:text-3xl font-bold text-black mb-6 mt-12">
{{ t(`news.detail.items.${slug}.section3Title`) }}
</h2>
<p class="text-gray-700 text-lg leading-relaxed mb-8">
{{ t(`news.detail.items.${slug}.section3Content`) }}
</p>
</article>
<!-- 分享按钮 -->
<div class="mt-16 pt-8 border-t border-gray-200">
<div class="flex items-center justify-between">
<p class="text-sm text-gray-500">{{ t('news.detail.shareArticle') }}</p>
<div class="flex gap-3">
<button class="w-10 h-10 rounded-full bg-gray-100 hover:bg-black hover:text-white flex items-center justify-center transition-all duration-300">
<Icon name="ph:x-logo" class="w-5 h-5" />
</button>
<button class="w-10 h-10 rounded-full bg-gray-100 hover:bg-black hover:text-white flex items-center justify-center transition-all duration-300">
<Icon name="ph:linkedin-logo" class="w-5 h-5" />
</button>
<button class="w-10 h-10 rounded-full bg-gray-100 hover:bg-black hover:text-white flex items-center justify-center transition-all duration-300">
<Icon name="ph:link" class="w-5 h-5" />
</button>
</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('news.detail.relatedArticles') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<NuxtLink
v-for="related in relatedArticles"
:key="related.id"
:to="`/news/${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-[16/10] 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">
<span class="text-xs text-gray-500">{{ related.date }}</span>
<h3 class="text-lg font-bold text-black group-hover:text-gray-600 transition-colors mt-1 line-clamp-2">
{{ related.title }}
</h3>
</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 NewsItem {
id: string
title: string
image: string
category: string
badge: string
date: string
readTime: string
author: string
}
const newsData: Record<string, NewsItem> = {
'design-trends-2026': {
id: 'design-trends-2026',
title: 'Design Trends Shaping 2026',
image: 'https://picsum.photos/id/1/1200/800',
category: 'Insights',
badge: 'bg-purple-100 text-purple-700',
date: 'May 28, 2026',
readTime: '5 min',
author: 'ArcticNewOne Team'
},
'forma-digital-launch': {
id: 'forma-digital-launch',
title: 'Forma Digital Platform Launch',
image: 'https://picsum.photos/id/201/1200/800',
category: 'Projects',
badge: 'bg-blue-100 text-blue-700',
date: 'May 15, 2026',
readTime: '3 min',
author: 'ArcticNewOne Team'
},
'studio-expansion': {
id: 'studio-expansion',
title: 'Studio Expansion Announcement',
image: 'https://picsum.photos/id/164/1200/800',
category: 'Announcements',
badge: 'bg-green-100 text-green-700',
date: 'May 10, 2026',
readTime: '2 min',
author: 'ArcticNewOne Team'
},
'ux-research-methods': {
id: 'ux-research-methods',
title: 'Modern UX Research Methods',
image: 'https://picsum.photos/id/180/1200/800',
category: 'Insights',
badge: 'bg-purple-100 text-purple-700',
date: 'Apr 28, 2026',
readTime: '7 min',
author: 'ArcticNewOne Team'
},
'bold-moves-case-study': {
id: 'bold-moves-case-study',
title: 'Bold Moves: A Case Study',
image: 'https://picsum.photos/id/119/1200/800',
category: 'Projects',
badge: 'bg-blue-100 text-blue-700',
date: 'Apr 20, 2026',
readTime: '6 min',
author: 'ArcticNewOne Team'
},
'new-tools-integration': {
id: 'new-tools-integration',
title: 'New Tools Added to Our Platform',
image: 'https://picsum.photos/id/326/1200/800',
category: 'Announcements',
badge: 'bg-green-100 text-green-700',
date: 'Apr 12, 2026',
readTime: '4 min',
author: 'ArcticNewOne Team'
}
}
const article = newsData[slug]
if (!article) {
throw createError({ statusCode: 404, statusMessage: 'Article not found' })
}
const relatedArticles = computed(() => {
return Object.values(newsData)
.filter(a => a.id !== slug)
.slice(0, 3)
})
useSeoMeta({
title: () => `${article.title} - ${t('news.title')}`,
description: () => article.title,
})
</script>