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:
Yuzhiran Dev
2026-07-11 12:45:37 +08:00
parent 66ac576082
commit c1d6dd3b29
106 changed files with 17436 additions and 2311 deletions
+356
View File
@@ -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>