wx 14935dfbf4 fix(news): 新闻动态详情页 + 列表卡片可点击跳转
- 移除 pages/news.vue,改为 pages/news/index.vue (列表) + [slug].vue (详情)
- 列表卡片包 NuxtLink 跳 /news/{slug},正文编辑器内容现在能渲染
- 详情页含面包屑/侧栏/上下篇导航/JSON-LD NewsArticle schema
2026-05-08 14:01:23 +08:00

315 行
7.9 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<div class="page-news">
<LayoutPageHero
:title="seo.h1"
:subtitle="heroes?.news || '品牌动态、产品发布、活动公告,及时了解呼籁旅行最新资讯'"
:breadcrumbs="[{ text: '品牌资讯' }]"
background-image="/images/seasons/autumn-hero.jpg"
/>
<!-- TL;DR -->
<section class="page-tldr" aria-label="品牌资讯摘要">
<div class="container">
<div class="page-tldr-inner">
<p>呼籁旅行品牌资讯品牌公告产品发布活动资讯行业合作等信息及时了解呼伦贝尔亲子游相关资讯</p>
</div>
</div>
</section>
<section v-if="newsData" class="section">
<div class="container">
<!-- Category filter -->
<div class="news-filter">
<button
v-for="cat in categories"
:key="cat"
:class="['filter-btn', { 'filter-btn--active': activeCategory === cat }]"
@click="activeCategory = cat"
>
{{ cat }}
</button>
</div>
<!-- News list -->
<div class="news-list">
<NuxtLink
v-for="article in filteredArticles"
:key="article.id"
:to="`/news/${article.id}`"
class="news-card"
:class="{ 'news-card--with-cover': article.coverImage }"
>
<div v-if="article.coverImage" class="news-card__cover">
<img
:src="article.coverImage"
:alt="article.title"
loading="lazy"
referrerpolicy="no-referrer"
/>
</div>
<div class="news-card__body">
<div class="news-meta">
<span :class="['news-cat', `news-cat--${article.category}`]">{{ article.category }}</span>
<time class="news-date" :datetime="article.date || article.createdAt">{{ formatDate(article.date || article.createdAt) }}</time>
</div>
<h2 class="news-title">{{ article.title }}</h2>
<p class="news-summary">{{ article.summary }}</p>
<span class="news-readmore">查看全文 </span>
</div>
</NuxtLink>
</div>
<!-- CTA -->
<div class="news-cta">
<h3>想了解更多</h3>
<p>草原的风已经在等您了</p>
<div class="news-cta-links">
<CommonInternalLink to="/products" text="查看2026旅行产品" />
<CommonInternalLink to="/summer-camp" text="小蒙马亲子游学营报名" />
<CommonInternalLink to="/contact" text="联系定制师" />
</div>
</div>
</div>
</section>
</div>
</template>
<script setup>
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
const { data: newsData } = await usePublicApi('news')
const { data: heroes } = await usePublicApi('heroes')
const seo = useSEO('news')
useBreadcrumbSchema([
{ text: '首页', to: '/' },
{ text: '品牌资讯' },
])
// NewsArticle JSON-LD for each article
;(newsData.value?.articles || []).forEach((article) => {
useHead({
script: [{
key: `news-article-${article.id}`,
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'NewsArticle',
headline: article.title,
description: article.summary,
articleBody: article.content,
datePublished: (article.date || article.createdAt) + 'T00:00:00+08:00',
dateModified: (article.date || article.createdAt) + 'T00:00:00+08:00',
articleSection: article.category,
inLanguage: 'zh-CN',
url: `https://1814.love/news/${article.id}`,
author: { '@type': 'Organization', name: '呼籁旅行', url: 'https://1814.love' },
publisher: { '@id': 'https://1814.love/#organization' },
about: { '@type': 'TravelAgency', name: '呼籁旅行', url: 'https://1814.love' },
keywords: '呼籁旅行,呼伦贝尔旅游,额吉的故乡,小蒙马亲子游学营',
}),
}],
})
})
const categories = ['全部', '产品', '活动', '公告']
const activeCategory = ref('全部')
const filteredArticles = computed(() => {
if (activeCategory.value === '全部') return newsData.value?.articles || []
return (newsData.value?.articles || []).filter(a => a.category === activeCategory.value)
})
function formatDate(dateStr) {
if (!dateStr) return ''
const d = new Date(dateStr.includes('T') ? dateStr : dateStr + 'T00:00:00')
if (isNaN(d.getTime())) return ''
return `${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}`
}
</script>
<style lang="less" scoped>
.news-filter {
display: flex;
gap: @space-sm;
margin-bottom: @space-2xl;
flex-wrap: wrap;
}
.filter-btn {
padding: @space-sm @space-lg;
border: 1px solid @color-border;
border-radius: @border-radius-full;
background: @color-bg-white;
color: @color-text-secondary;
font-size: @font-size-sm;
cursor: pointer;
transition: all @transition-base;
&:hover {
border-color: @color-primary;
color: @color-primary;
}
&--active {
background: @color-primary;
border-color: @color-primary;
color: @color-bg-white;
font-weight: @font-weight-medium;
}
}
.news-list {
display: flex;
flex-direction: column;
gap: @space-lg;
margin-bottom: @space-3xl;
}
.news-card {
display: block;
background: @color-bg-white;
border: 1px solid @color-border;
border-radius: @border-radius-md;
padding: @space-xl;
text-decoration: none;
color: inherit;
transition: box-shadow @transition-base, border-color @transition-base, transform @transition-base;
cursor: pointer;
&:hover {
box-shadow: @shadow-md;
border-color: @color-primary-lighter;
transform: translateY(-2px);
.news-title {
color: @color-primary;
}
.news-readmore {
color: @color-primary-dark;
}
}
}
.news-card--with-cover {
display: grid;
grid-template-columns: 1fr;
gap: @space-md;
padding: @space-lg;
.respond-md({
grid-template-columns: 240px 1fr;
gap: @space-xl;
});
}
.news-card__cover {
width: 100%;
aspect-ratio: 4 / 3;
overflow: hidden;
border-radius: @border-radius-sm;
background: @color-bg-gray;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.news-card__body {
display: flex;
flex-direction: column;
justify-content: center;
}
.news-meta {
display: flex;
align-items: center;
gap: @space-md;
margin-bottom: @space-md;
}
.news-cat {
display: inline-block;
padding: 2px @space-sm;
border-radius: @border-radius-sm;
font-size: @font-size-xs;
font-weight: @font-weight-medium;
&--产品 {
background: @color-primary-bg;
color: @color-primary;
}
&--活动 {
background: #FFF3E6;
color: @color-accent;
}
&--公告 {
background: @color-bg-gray;
color: @color-text-secondary;
}
}
.news-date {
font-size: @font-size-sm;
color: @color-text-muted;
}
.news-title {
font-size: @font-size-lg;
font-weight: @font-weight-bold;
color: @color-text-primary;
margin: 0 0 @space-sm;
line-height: @line-height-tight;
transition: color @transition-base;
}
.news-summary {
font-size: @font-size-base;
color: @color-text-secondary;
line-height: @line-height-base;
margin: 0 0 @space-sm;
}
.news-readmore {
display: inline-block;
font-size: @font-size-sm;
color: @color-primary;
font-weight: @font-weight-medium;
transition: color @transition-base;
}
.news-cta {
text-align: center;
padding: @space-2xl;
background: @color-primary-bg;
border-radius: @border-radius-lg;
h3 {
font-size: @font-size-xl;
margin-bottom: @space-sm;
color: @color-primary;
}
p {
color: @color-text-secondary;
margin-bottom: @space-xl;
}
}
.news-cta-links {
display: flex;
justify-content: center;
gap: @space-xl;
flex-wrap: wrap;
}
</style>