Files
ai-dimension/client/src/pages/news/news.vue
T
Yuzhiran Dev c1d6dd3b29 refactor: 项目架构重构 - 目录标准化 + 清理冗余
- 目录重命名: backend/wdkj-server/ → server/, frontend/ai-dimension/ → client/
- 删除 20+ 冗余文件(WDKJ 旧脚本、Windows 脚本、过时文档、设计稿)
- 更新 package.json 元数据(移除 wdkj 命名)
- 完善三级 .gitignore(根 + server + client)
- 重写 README.md 和 CHANGELOG.md
- 工具脚本移至 scripts/
2026-07-11 12:45:37 +08:00

357 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page-news-detail">
<view class="status-bar-placeholder"></view>
<!-- 导航 -->
<view class="nav-bar">
<view class="nav-back" @click="goBack"></view>
<text class="nav-title">AI 资讯</text>
<view class="nav-action">
<text class="nav-share" @click="onShare"></text>
</view>
</view>
<scroll-view scroll-y class="content">
<!-- 资讯头图 -->
<view class="news-hero" :style="{background: heroBg}">
<text class="hero-emoji">{{ heroEmoji }}</text>
</view>
<!-- 分类标签 -->
<view class="category-tag">
<text class="tag-text">{{ categoryLabel }}</text>
</view>
<!-- 标题 -->
<text class="news-title">{{ news.title }}</text>
<!-- 元信息 -->
<view class="meta-info">
<view class="meta-item">
<text class="meta-label">来源</text>
<text class="meta-value">{{ news.source }}</text>
</view>
<view class="meta-item">
<text class="meta-label">时间</text>
<text class="meta-value">{{ formatDate(news.newsDate) }}</text>
</view>
<view class="meta-item">
<text class="meta-label">阅读</text>
<text class="meta-value">{{ news.viewCount || 0 }} </text>
</view>
<view class="meta-item" v-if="news.hot">
<text class="meta-label hot">🔥</text>
<text class="meta-value">{{ news.hotCount || 0 }}</text>
</view>
</view>
<!-- 摘要 -->
<view class="summary-card">
<text class="summary-label">摘要</text>
<text class="summary-text">{{ news.summary }}</text>
</view>
<!-- 正文 -->
<view class="news-body" v-if="newsBody">
<rich-text :nodes="newsBody"></rich-text>
</view>
<!-- 相关话题 -->
<view class="related-tags" v-if="relatedTags.length">
<text class="related-label">相关话题</text>
<view class="tags-row">
<view class="tag-item" v-for="(tag, i) in relatedTags" :key="i">
<text class="tag-text">{{ tag }}</text>
</view>
</view>
</view>
<!-- 操作栏 -->
<view class="action-bar">
<view class="action-btn" @click="onFavorite">
<text class="action-icon">{{ isCollected ? '❤️' : '🤍' }}</text>
<text class="action-text">{{ isCollected ? '已收藏' : '收藏' }}</text>
</view>
<view class="action-btn" @click="onShare">
<text class="action-icon"></text>
<text class="action-text">分享</text>
</view>
</view>
<view class="footer-spacer"></view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { trendApi, knowledgeApi } from '@/utils/api'
const news = ref({})
const isCollected = ref(false)
const loading = ref(true)
const heroBg = computed(() => {
const c = news.value.category || 'product'
return {
product: 'linear-gradient(135deg, rgba(179,136,255,0.3), rgba(239,83,80,0.15))',
paper: 'linear-gradient(135deg, rgba(77,208,225,0.3), rgba(77,182,172,0.15))',
tool: 'linear-gradient(135deg, rgba(255,213,79,0.3), rgba(239,83,80,0.1))',
company: 'linear-gradient(135deg, rgba(77,182,172,0.3), rgba(179,136,255,0.15))'
}[c] || 'linear-gradient(135deg, rgba(179,136,255,0.3), rgba(77,208,225,0.15))'
})
const heroEmoji = computed(() => {
return { product: '🚀', paper: '📄', tool: '🔧', company: '🏢' }[news.value.category || 'product'] || '📰'
})
const categoryLabel = computed(() => {
const c = news.value.category || 'product'
return { product: '产品动态', paper: '研究论文', tool: '工具发布', company: '公司动态' }[c] || '资讯'
})
function formatDate(dateStr) {
if (!dateStr) return '—'
try {
const d = new Date(dateStr)
if (isNaN(d)) return dateStr
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
} catch (e) {
return dateStr
}
}
const newsBody = computed(() => {
const content = news.value.body || ''
if (!content) {
return `<p>${news.value.summary || ''}</p><p style="margin-top:16px;color:rgba(255,255,255,0.35)">完整内容暂未提供,将持续更新...</p>`
}
return content
})
const relatedTags = computed(() => {
return news.value.tags || []
})
async function loadDetail() {
const id = uni.getStorageSync('__news_id__')
loading.value = false
// Try API detail first, fallback to local data from trend page
if (id) {
try {
const data = await trendApi.detail(id)
news.value = data
isCollected.value = data.collected || false
return
} catch (e) {
// fall through to local
}
}
// Fallback: read from trend page's stored item
try {
const raw = uni.getStorageSync('__news_item__')
if (raw) {
news.value = JSON.parse(raw)
}
} catch (e) {
// ignore
}
}
function goBack() {
uni.navigateBack()
}
function onFavorite() {
isCollected.value = !isCollected.value
uni.showToast({ title: isCollected.value ? '已收藏' : '已取消收藏', icon: 'none' })
}
function onShare() {
uni.setClipboardData({
data: news.value.link || `https://ai-dimension.yuzhiran.com.cn/news/${uni.getStorageSync('__news_id__')}`,
success: () => uni.showToast({ title: '链接已复制', icon: 'none' })
})
}
onMounted(() => {
loadDetail()
})
</script>
<style lang="scss" scoped>
.page-news-detail {
height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-primary);
}
.status-bar-placeholder { height: 44px; }
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
height: 44px;
}
.nav-back {
font-size: 28px;
color: #fff;
padding: 0 8px;
line-height: 1;
}
.nav-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.nav-share { font-size: 22px; color: rgba(255,255,255,0.5); }
.content {
flex: 1;
background: var(--bg-primary);
}
// Hero
.news-hero {
height: 180px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 0 24px 24px;
}
.hero-emoji { font-size: 72px; }
// Category
.category-tag {
margin: 24px 20px 0;
}
.tag-text {
font-size: 11px;
font-weight: 600;
color: var(--dim1-color);
letter-spacing: 2px;
text-transform: uppercase;
}
// Title
.news-title {
display: block;
font-size: 22px;
font-weight: 700;
line-height: 1.45;
color: var(--text-primary);
margin: 12px 20px 16px;
}
// Meta
.meta-info {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 0 20px 20px;
padding: 16px;
background: rgba(255,255,255,0.02);
border-radius: 12px;
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
}
.meta-label {
font-size: 11px;
color: rgba(255,255,255,0.35);
}
.meta-label.hot { font-size: 13px; }
.meta-value { font-size: 12px; color: rgba(255,255,255,0.7); }
// Summary
.summary-card {
margin: 0 20px 20px;
padding: 16px;
background: linear-gradient(135deg, rgba(179,136,255,0.08), rgba(77,208,225,0.05));
border: 1px solid rgba(179,136,255,0.12);
border-radius: 12px;
}
.summary-label {
display: block;
font-size: 11px;
color: var(--dim1-color);
letter-spacing: 1px;
margin-bottom: 8px;
}
.summary-text {
font-size: 14px;
line-height: 1.7;
color: rgba(255,255,255,0.85);
}
// Body
.news-body {
margin: 0 20px 20px;
font-size: 14px;
line-height: 1.8;
color: rgba(255,255,255,0.7);
}
// Related tags
.related-tags {
margin: 0 20px 20px;
}
.related-label {
display: block;
font-size: 12px;
color: rgba(255,255,255,0.4);
margin-bottom: 10px;
}
.tags-row { display: flex; flex-wrap: wrap; gap: 8px; }
.tag-item {
padding: 6px 12px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 20px;
}
.tag-text { font-size: 12px; color: rgba(255,255,255,0.6); }
// Action bar
.action-bar {
margin: 0 20px;
display: flex;
gap: 12px;
}
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 14px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 12px;
}
.action-icon { font-size: 18px; }
.action-text { font-size: 13px; color: rgba(255,255,255,0.7); }
.footer-spacer { height: 60px; }
</style>