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,220 @@
|
||||
<template>
|
||||
<main class="page-container min-h-screen pt-32 pb-20 px-4 md:px-8 mx-auto max-w-[1200px]">
|
||||
<div
|
||||
ref="headerWrapRef"
|
||||
class="flex flex-col items-center justify-center text-center mb-10 md:mb-16"
|
||||
>
|
||||
<h1 ref="titleRef" class="text-3xl md:text-5xl font-bold mb-4 md:mb-6 tracking-tight text-black">{{ t('news.title') }}</h1>
|
||||
<p ref="descRef" class="text-base md:text-lg text-gray-600 max-w-2xl">{{ t('news.description') }}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="filterWrapRef"
|
||||
class="flex justify-center gap-3 md:gap-4 mb-10 md:mb-14 flex-wrap"
|
||||
>
|
||||
<button
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
@click="activeCategory = category"
|
||||
class="px-5 md:px-6 py-2 md:py-2.5 rounded-full text-sm md:text-base font-semibold transition-all duration-300"
|
||||
:class="activeCategory === category
|
||||
? 'bg-black text-white shadow-lg'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
|
||||
>
|
||||
{{ t(`news.categories.${category}`) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="articlesWrapRef"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8"
|
||||
>
|
||||
<NuxtLink
|
||||
v-for="article in filteredNewsData"
|
||||
:key="article.id"
|
||||
:to="`/news/${article.id}`"
|
||||
class="news-card group relative flex flex-col rounded-2xl md:rounded-3xl overflow-hidden bg-white border border-gray-100 hover:shadow-xl transition-all duration-500 cursor-pointer hover:-translate-y-2"
|
||||
>
|
||||
<div class="relative aspect-[16/10] overflow-hidden bg-gray-100">
|
||||
<img
|
||||
:src="article.image"
|
||||
:alt="article.title"
|
||||
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
||||
/>
|
||||
<div class="absolute top-4 left-4">
|
||||
<span
|
||||
class="px-3 py-1.5 text-xs md:text-sm font-semibold rounded-full shadow-sm"
|
||||
:class="article.badge"
|
||||
>
|
||||
{{ article.category }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-grow p-5 md:p-6">
|
||||
<div class="flex items-center gap-3 text-xs md:text-sm text-gray-500 mb-3">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg md:text-xl font-bold text-black mb-3 group-hover:text-gray-700 transition-colors duration-300 line-clamp-2">
|
||||
{{ article.title }}
|
||||
</h3>
|
||||
|
||||
<p class="text-sm md:text-base text-gray-600 mb-4 flex-grow line-clamp-3">
|
||||
{{ article.excerpt }}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-between pt-3 border-t border-gray-100">
|
||||
<span class="text-sm font-semibold text-black group-hover:text-gray-700 transition-colors duration-300">
|
||||
{{ t('news.readMore') }}
|
||||
</span>
|
||||
<div class="w-8 h-8 rounded-full flex items-center justify-center bg-gray-50 text-gray-400 group-hover:bg-black group-hover:text-white transition-all duration-300">
|
||||
<Icon name="ph:arrow-right" class="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useSeoMeta } from '#imports'
|
||||
import gsap from 'gsap'
|
||||
import { useAppStore } from '~/stores/app'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const headerWrapRef = ref<HTMLElement | null>(null)
|
||||
const titleRef = ref<HTMLElement | null>(null)
|
||||
const descRef = ref<HTMLElement | null>(null)
|
||||
const filterWrapRef = ref<HTMLElement | null>(null)
|
||||
const articlesWrapRef = ref<HTMLElement | null>(null)
|
||||
const activeCategory = ref('all')
|
||||
|
||||
const categories = ['all', 'insights', 'projects', 'announcements']
|
||||
|
||||
const filteredNewsData = computed(() => {
|
||||
if (activeCategory.value === 'all') {
|
||||
return newsData
|
||||
}
|
||||
return newsData.filter(item => item.category.toLowerCase() === activeCategory.value.toLowerCase())
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: () => t('news.title'),
|
||||
description: () => t('news.description'),
|
||||
})
|
||||
|
||||
const playPageIntro = async () => {
|
||||
await nextTick()
|
||||
if (!headerWrapRef.value || !titleRef.value || !descRef.value || !filterWrapRef.value || !articlesWrapRef.value) return
|
||||
|
||||
const tl = gsap.timeline({ defaults: { ease: 'power3.out' } })
|
||||
|
||||
tl.fromTo(titleRef.value, { autoAlpha: 0, y: 20 }, { autoAlpha: 1, y: 0, duration: 0.85, ease: 'power4.out', clearProps: 'transform' })
|
||||
tl.fromTo(descRef.value, { autoAlpha: 0, y: 16 }, { autoAlpha: 1, y: 0, duration: 0.7, ease: 'power4.out', clearProps: 'transform' }, '-=0.45')
|
||||
|
||||
const filterButtons = gsap.utils.toArray<HTMLElement>('button', filterWrapRef.value!)
|
||||
tl.fromTo(filterButtons, { autoAlpha: 0, y: 20 }, { autoAlpha: 1, y: 0, duration: 0.7, stagger: 0.08, clearProps: 'transform' }, '-=0.3')
|
||||
|
||||
const newsCards = gsap.utils.toArray<HTMLElement>('.news-card', articlesWrapRef.value!)
|
||||
tl.fromTo(newsCards, { autoAlpha: 0, y: 34, scale: 0.965 }, { autoAlpha: 1, y: 0, scale: 1, duration: 0.95, stagger: 0.11, clearProps: 'transform' }, '+=0.08')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!appStore.isAppLoading) {
|
||||
playPageIntro()
|
||||
} else {
|
||||
window.addEventListener('app-loading-done', playPageIntro, { once: true })
|
||||
}
|
||||
})
|
||||
|
||||
interface NewsItem {
|
||||
id: string
|
||||
title: string
|
||||
excerpt: string
|
||||
image: string
|
||||
category: string
|
||||
badge: string
|
||||
date: string
|
||||
readTime: string
|
||||
}
|
||||
|
||||
const newsData: NewsItem[] = [
|
||||
{
|
||||
id: 'design-trends-2026',
|
||||
title: 'Design Trends Shaping 2026',
|
||||
excerpt: 'Explore the emerging design trends that are defining digital experiences this year, from minimalism to bold typography.',
|
||||
image: 'https://picsum.photos/id/1/800/500',
|
||||
category: 'Insights',
|
||||
badge: 'bg-purple-100 text-purple-700',
|
||||
date: 'May 28, 2026',
|
||||
readTime: '5 min'
|
||||
},
|
||||
{
|
||||
id: 'forma-digital-launch',
|
||||
title: 'Forma Digital Platform Launch',
|
||||
excerpt: 'We are excited to announce the successful launch of Forma Digital, a revolutionary e-commerce experience.',
|
||||
image: 'https://picsum.photos/id/201/800/500',
|
||||
category: 'Projects',
|
||||
badge: 'bg-blue-100 text-blue-700',
|
||||
date: 'May 15, 2026',
|
||||
readTime: '3 min'
|
||||
},
|
||||
{
|
||||
id: 'studio-expansion',
|
||||
title: 'Studio Expansion Announcement',
|
||||
excerpt: 'ArcticNewOne is growing! We are expanding our team and capabilities to serve you better.',
|
||||
image: 'https://picsum.photos/id/164/800/500',
|
||||
category: 'Announcements',
|
||||
badge: 'bg-green-100 text-green-700',
|
||||
date: 'May 10, 2026',
|
||||
readTime: '2 min'
|
||||
},
|
||||
{
|
||||
id: 'ux-research-methods',
|
||||
title: 'Modern UX Research Methods',
|
||||
excerpt: 'Dive into the latest user experience research methodologies that inform our design decisions.',
|
||||
image: 'https://picsum.photos/id/180/800/500',
|
||||
category: 'Insights',
|
||||
badge: 'bg-purple-100 text-purple-700',
|
||||
date: 'Apr 28, 2026',
|
||||
readTime: '7 min'
|
||||
},
|
||||
{
|
||||
id: 'bold-moves-case-study',
|
||||
title: 'Bold Moves: A Case Study',
|
||||
excerpt: 'Behind the scenes of our award-winning architectural visualization project featuring cutting-edge 3D technology.',
|
||||
image: 'https://picsum.photos/id/119/800/500',
|
||||
category: 'Projects',
|
||||
badge: 'bg-blue-100 text-blue-700',
|
||||
date: 'Apr 20, 2026',
|
||||
readTime: '6 min'
|
||||
},
|
||||
{
|
||||
id: 'new-tools-integration',
|
||||
title: 'New Tools Added to Our Platform',
|
||||
excerpt: 'Check out the latest additions to our toolbox, designed to streamline your workflow and boost productivity.',
|
||||
image: 'https://picsum.photos/id/326/800/500',
|
||||
category: 'Announcements',
|
||||
badge: 'bg-green-100 text-green-700',
|
||||
date: 'Apr 12, 2026',
|
||||
readTime: '4 min'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p { word-break: break-word; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user