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>
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user