refactor: 项目架构重构 - 目录标准化 + 清理冗余
- 目录重命名: backend/wdkj-server/ → server/, frontend/ai-dimension/ → client/ - 删除 20+ 冗余文件(WDKJ 旧脚本、Windows 脚本、过时文档、设计稿) - 更新 package.json 元数据(移除 wdkj 命名) - 完善三级 .gitignore(根 + server + client) - 重写 README.md 和 CHANGELOG.md - 工具脚本移至 scripts/
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user