2026-08-04 15:05:50 +08:00

286 lines
9.8 KiB
Vue

<template>
<div class="news-view-page">
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
<section class="news-hero">
<div class="news-hero-glow news-hero-glow-1"></div>
<div class="news-hero-glow news-hero-glow-2"></div>
<div class="news-shell">
<div class="hero-content">
<span class="hero-badge">{{ $t('newsView.badge') }}</span>
<div class="hero-title-row">
<h1>{{ $t('newsView.title') }}</h1>
</div>
<p>{{ $t('newsView.intro') }}</p>
</div>
</div>
</section>
<section class="filter-section">
<div class="news-shell">
<div class="filter-tabs">
<button
v-for="item in localizedFilterTabs"
:key="item.value"
type="button"
class="filter-tab"
:class="{ active: activeCategory === item.value }"
@click="handleCategoryChange(item.value)"
>
{{ item.label }}
</button>
</div>
</div>
</section>
<section class="featured-section">
<div class="news-shell">
<article v-if="featuredNews" class="news-featured" @click="goNewsDetail(featuredNews)">
<div class="news-item-glow"></div>
<span class="hover-detail">{{ $t('newsView.detail') }} <i class="el-icon-arrow-right"></i></span>
<div class="news-featured-inner">
<div class="news-featured-img">
<img :src="featuredNews.img || fallbackNewsImage" :alt="featuredNews.title" @error="handleImageError">
</div>
<div class="news-featured-body">
<div class="news-featured-date">
<span class="category-pill" :style="{ background: featuredNews.tagColor }">{{ featuredNews.tag }}</span>
{{ featuredNews.date }}
</div>
<h2>{{ featuredNews.title }}</h2>
<p>{{ featuredNews.desc }}</p>
</div>
</div>
</article>
</div>
</section>
<section class="news-list-section">
<div class="news-shell">
<div class="news-grid">
<article
v-for="(item, index) in filteredNewsList"
:key="item.id"
class="news-item"
:style="{ animationDelay: `${(index + 1) * 0.08}s` }"
@click="goNewsDetail(item)"
>
<div class="news-item-glow"></div>
<span class="hover-detail">{{ $t('newsView.detail') }} <i class="el-icon-arrow-right"></i></span>
<div class="news-item-img">
<img :src="item.img || fallbackNewsImage" :alt="item.title" @error="handleImageError">
<div class="news-item-img-overlay"></div>
<span class="news-item-tag" :style="{ background: item.tagColor }">{{ item.tag }}</span>
</div>
<div class="news-item-body">
<div class="news-item-date">{{ item.date }}</div>
<h3>{{ item.title }}</h3>
<p>{{ item.desc }}</p>
</div>
</article>
</div>
<div class="pagination">
<button type="button" class="pagination-btn" :disabled="page <= 1" @click="changePage(page - 1)">
<i class="el-icon-arrow-left"></i>
</button>
<button
v-for="item in visiblePages"
:key="item"
type="button"
class="pagination-btn"
:class="{ active: page === item }"
@click="changePage(item)"
>
{{ item }}
</button>
<button type="button" class="pagination-btn" :disabled="page >= totalPages" @click="changePage(page + 1)">
<i class="el-icon-arrow-right"></i>
</button>
</div>
</div>
</section>
</div>
</template>
<script>
import { reqNewsList } from '@/api/newsapi/newsapi'
export default {
name: 'NewsView',
data() {
return {
fallbackNewsImage: require('@/assets/image/news.jpg'),
activeCategory: 'all',
page: 1,
pageSize: 10,
total: 0,
filterTabValues: [
{ key: 'all', value: 'all', zh: '', en: '' },
{ key: 'corporate', value: 'corporate', zh: '企业动态', en: 'Corporate News' },
{ key: 'product', value: 'product', zh: '产品动态', en: 'Product News' },
{ key: 'industry', value: 'industry', zh: '行业洞察', en: 'Industry Insights' },
{ key: 'event', value: 'event', zh: '活动资讯', en: 'Event News' }
],
newsList: []
}
},
computed: {
isEnglish() {
return this.$i18n && this.$i18n.locale === 'en-US'
},
featuredNews() {
return this.newsList[0]
},
filteredNewsList() {
return this.newsList.slice(1)
},
localizedFilterTabs() {
return this.filterTabValues.map(item => ({
...item,
label: item.key === 'all' ? this.$t('newsView.all') : this.$t(`newsView.categories.${item.key}`)
}))
},
totalPages() {
return Math.max(Math.ceil(this.total / this.pageSize), 1)
},
visiblePages() {
return Array.from({ length: Math.min(this.totalPages, 3) }, (_, index) => index + 1)
}
},
watch: {
isEnglish() {
this.getNewsList()
}
},
created() {
this.getNewsList()
},
methods: {
goAbout() {
this.$router.push('/homePage/about')
},
goNewsDetail(item) {
if (!item || !item.id) return
this.$router.push({
name: 'homePageNewsDetail',
params: { id: item.id }
}).catch(() => {
this.$router.push(`/homePage/news/detail/${item.id}`)
})
},
handleImageError(event) {
if (event && event.target && event.target.src !== this.fallbackNewsImage) {
event.target.src = this.fallbackNewsImage
}
},
handleCategoryChange(value) {
this.activeCategory = value
this.page = 1
this.getNewsList()
},
changePage(page) {
if (page < 1 || page > this.totalPages || page === this.page) return
this.page = page
this.getNewsList()
},
getActiveCategoryConfig() {
return this.filterTabValues.find(item => item.value === this.activeCategory) || this.filterTabValues[0]
},
buildNewsParams() {
const category = this.getActiveCategoryConfig()
return {
url_link: window.location.href.split('#')[0] || window.location.href,
title_zh: '',
title_en: '',
article_type_zh: category.zh || '',
article_type_en: category.en || '',
current_page: String(this.page),
page_size: String(this.pageSize)
}
},
getTagColor(type) {
const map = {
corporate: 'rgba(16,185,129,0.85)',
product: 'rgba(99,102,241,0.85)',
industry: 'rgba(13,148,136,0.85)',
event: 'rgba(245,158,11,0.85)'
}
return map[this.getCategoryKey(type)] || 'rgba(99,102,241,0.85)'
},
getCategoryKey(type) {
const map = {
企业动态: 'corporate',
'Corporate News': 'corporate',
产品动态: 'product',
'Product News': 'product',
行业洞察: 'industry',
'Industry Insights': 'industry',
活动资讯: 'event',
'Event News': 'event'
}
if (map[type]) return map[type]
const matchedLabel = Object.keys(map).find(label => String(type || '').startsWith(label))
return matchedLabel ? map[matchedLabel] : ''
},
getCategorySuffix(type) {
const labels = ['企业动态', 'Corporate News', '产品动态', 'Product News', '行业洞察', 'Industry Insights', '活动资讯', 'Event News']
const matchedLabel = labels.find(label => String(type || '').startsWith(label))
return matchedLabel ? String(type).slice(matchedLabel.length) : ''
},
getLocalizedCategory(type) {
const key = this.getCategoryKey(type)
return key ? `${this.$t(`newsView.categories.${key}`)}${this.getCategorySuffix(type)}` : type
},
getLocalizedField(row, zhField, enField, legacyField) {
if (this.isEnglish && row[enField]) return row[enField]
return row[zhField] || row[legacyField] || row[enField] || ''
},
normalizeImageUrl(url) {
if (!url) return ''
if (/^(https?:|blob:|data:|\/\/)/.test(url)) return url
return `${window.location.origin}/idfile?path=${url}`
},
normalizeNewsItem(row) {
const type = this.getLocalizedField(row, 'article_type_zh', 'article_type_en', 'article_type') || '企业动态'
const localizedType = this.getLocalizedCategory(type)
return {
id: row.id,
category: localizedType,
tag: localizedType,
tagColor: this.getTagColor(type),
date: this.getLocalizedField(row, 'publish_time_zh', 'publish_time_en', 'publish_time'),
title: this.getLocalizedField(row, 'title_zh', 'title_en', 'title') || '未命名文章',
desc: this.getLocalizedField(row, 'summary_zh', 'summary_en', 'summary'),
img: this.normalizeImageUrl(this.getLocalizedField(row, 'cover_img_zh', 'cover_img_en', 'cover_img')),
views: row.read_count || 0
}
},
async getNewsList() {
try {
const res = await reqNewsList(this.buildNewsParams())
if (res && res.status === true) {
this.newsList = Array.isArray(res.data) ? res.data.map(item => this.normalizeNewsItem(item)) : []
const pagination = res.pagination || {}
this.total = Number(pagination.total || this.newsList.length)
this.page = Number(pagination.current_page || this.page)
this.pageSize = Number(pagination.page_size || this.pageSize)
return
}
this.newsList = []
this.total = 0
} catch (error) {
this.newsList = []
this.total = 0
this.$message.error(this.$t('newsView.loadFail'))
}
}
}
}
</script>
<style scoped lang="less">
@import url('../../../assets/less/news/news.less');
</style>