diff --git a/client/src/pages/index/index.vue b/client/src/pages/index/index.vue index a68936f..9746b8e 100644 --- a/client/src/pages/index/index.vue +++ b/client/src/pages/index/index.vue @@ -82,6 +82,20 @@ + + + + + 今日知识卡 + {{ todayDimName }} + + {{ todayKnowledge.title }} + {{ todayKnowledge.content }} + + 去读一读 · 答对点亮 → + + + 今日快讯 @@ -114,6 +128,18 @@ const dimensions = ref(DIMENSIONS) const newsList = ref([]) const byDim = ref({}) // 各星域知识总数 +const todayKnowledge = ref(null) + +const todayDimName = computed(() => { + const d = DIMENSIONS.find(x => x.id === todayKnowledge.value?.dim) + return d ? d.name : '' +}) + +function goTodayKnowledge() { + if (todayKnowledge.value?._id) { + uni.navigateTo({ url: `/pages/detail/detail?id=${todayKnowledge.value._id}` }) + } +} const userStats = ref(null) // { totalLit, litByDim, streak } const lastRead = ref(null) @@ -128,9 +154,21 @@ const todayLit = computed(() => !!uni.getStorageSync('lit_today')) onShow(() => { loadStats() loadNews() + loadTodayKnowledge() lastRead.value = uni.getStorageSync('last_read') || null }) +// 今日知识卡:按日期取当日第 N 条(同一天所有人看到同一条,形成"每日一词"共识感) +async function loadTodayKnowledge() { + try { + const data = await knowledgeApi.list({ limit: 50, page: 1 }) + const items = (data.items || []).filter((x) => x.content) + if (!items.length) return + const dayIndex = Math.floor(Date.now() / 86400000) + todayKnowledge.value = items[dayIndex % items.length] + } catch (e) { /* 静默 */ } +} + onPullDownRefresh(async () => { await Promise.all([loadStats(), loadNews()]) uni.stopPullDownRefresh() @@ -468,3 +506,42 @@ onShareTimeline(() => ({ .bottom-spacer { height: 30px; } + + diff --git a/client/src/pages/trend/trend.vue b/client/src/pages/trend/trend.vue index 68a31a0..920bac5 100644 --- a/client/src/pages/trend/trend.vue +++ b/client/src/pages/trend/trend.vue @@ -41,6 +41,18 @@ {{ item.summary }} + + + + 人话 + {{ item.plainExplain }} + + + 对你 + {{ item.takeAway }} + + + {{ formatDate(item.newsDate) }} @@ -403,3 +415,29 @@ onShareAppMessage(() => { display: block; } + + diff --git a/server/src/models/Trend.js b/server/src/models/Trend.js index 072976a..e7f06ab 100644 --- a/server/src/models/Trend.js +++ b/server/src/models/Trend.js @@ -14,6 +14,9 @@ const TrendSchema = new Schema({ }, tags: [String], hot: { type: Boolean, default: false }, + // 人话解读(AI 生成,治"看不懂AI新闻"的焦虑) + plainExplain: { type: String, default: '' }, // 一句话说清这是什么 + takeAway: { type: String, default: '' }, // 对普通人意味着什么 picked: { type: Boolean, default: false }, newsDate: { type: Date, default: Date.now, index: true }, viewCount: { type: Number, default: 0 }, diff --git a/server/src/routes/seo.js b/server/src/routes/seo.js index 546f097..cd3bd4e 100644 --- a/server/src/routes/seo.js +++ b/server/src/routes/seo.js @@ -66,7 +66,7 @@ router.get('/sitemap.xml', async (req, res) => { // ===== SSR 趋势页(SEO/GEO:真实内容直出,搜索引擎可收录) ===== -const TREND_PAGE_CSS = 'body{font-family:-apple-system,"PingFang SC",sans-serif;color:#1f2333;max-width:720px;margin:0 auto;padding:24px 18px;line-height:1.7}h1{font-size:22px}.meta{color:#9aa0b4;font-size:12px;margin:4px 0 20px}a{color:#5b5ff8;text-decoration:none}.item{padding:14px 0;border-bottom:1px solid #eef0f6}.item h2{font-size:16px;margin:0 0 6px}.item p{margin:0;font-size:13px;color:#6b7080}.item .m{font-size:11px;color:#a6abbb;margin-top:4px}.tag{display:inline-block;background:rgba(91,95,248,.1);color:#5b5ff8;border-radius:99px;padding:1px 8px;font-size:11px;margin-right:6px}.foot{margin-top:28px;color:#b6bac9;font-size:12px}' +const TREND_PAGE_CSS = 'body{font-family:-apple-system,"PingFang SC",sans-serif;color:#1f2333;max-width:720px;margin:0 auto;padding:24px 18px;line-height:1.7}h1{font-size:22px}.meta{color:#9aa0b4;font-size:12px;margin:4px 0 20px}a{color:#5b5ff8;text-decoration:none}.item{padding:14px 0;border-bottom:1px solid #eef0f6}.item h2{font-size:16px;margin:0 0 6px}.item p{margin:0;font-size:13px;color:#6b7080}.item .m{font-size:11px;color:#a6abbb;margin-top:4px}.tag{display:inline-block;background:rgba(91,95,248,.1);color:#5b5ff8;border-radius:99px;padding:1px 8px;font-size:11px;margin-right:6px}.foot{margin-top:28px;color:#b6bac9;font-size:12px}.plain{background:#f5f5ff;border-left:3px solid #5b5ff8;padding:8px 12px;margin:8px 0;font-size:14px;color:#3a3f55}.plainbox{background:#f5f5ff;border-left:3px solid #5b5ff8;padding:10px 14px;border-radius:0 8px 8px 0;margin:14px 0}.plainbox p{margin:4px 0;font-size:14px;color:#3a3f55}' router.get('/trends', async (req, res) => { const o = origin(req) @@ -76,6 +76,7 @@ router.get('/trends', async (req, res) => {

${esc(t.title)}

${t.summary ? `

${esc(String(t.summary).slice(0, 120))}

` : ''} + ${t.plainExplain ? `

人话:${esc(t.plainExplain)}${t.takeAway ? ` 对你:${esc(t.takeAway)}` : ''}

` : ''}
${esc(t.category || '资讯')}${esc(t.source || '')} · ${(t.newsDate || t.createdAt || '').toString().slice(0, 10)}
`).join('') res.type('html').send(` @@ -104,6 +105,7 @@ router.get('/trends/:id', async (req, res) => {

${esc(t.title)}

${esc(t.source || '')} · ${(t.newsDate || t.createdAt || '').toString().slice(0, 10)}
${t.summary ? `

${esc(t.summary)}

` : ''} +${t.plainExplain ? `

💡 一句话说清:${esc(t.plainExplain)}

${t.takeAway ? `

🎯 对你意味着:${esc(t.takeAway)}

` : ''}
` : ''} ${t.body ? `
${esc(t.body).split('\n').map((p) => `

${p}

`).join('')}
` : ''} ${t.sourceUrl ? `

阅读原文 →

` : ''}
← 更多 AI 趋势 · © 宇之然
diff --git a/server/src/scripts/update-trends.js b/server/src/scripts/update-trends.js index 3b49240..5ceecde 100644 --- a/server/src/scripts/update-trends.js +++ b/server/src/scripts/update-trends.js @@ -189,6 +189,33 @@ async function callAI(messages, maxTokens = 1024) { return ''; } +/** 人话解读:每条趋势生成"一句话说清 + 对普通人意味着什么"(治 AI 焦虑的核心价值层) */ +async function explainItems(items) { + const targets = items.filter(i => i.title && !i.plainExplain).slice(0, 14); + if (targets.length === 0) return items; + console.log(` → 生成人话解读 ${targets.length} 条...`); + for (const item of targets) { + const prompt = `你是"维度精灵",帮普通上班族看懂 AI 新闻。基于下面这条资讯,输出 JSON(只输出 JSON): +{"plainExplain":"用一句大白话说清这条资讯讲的是什么,30字内,不用术语","takeAway":"这对普通人意味着什么/该注意什么,一句行动向的话,30字内"} + +资讯标题:${item.title} +${item.summary ? '摘要:' + String(item.summary).slice(0, 200) : ''}`; + const out = await callAI([{ role: 'user', content: prompt }], 300); + const m = out.match(/\{[\s\S]*?\}/); + if (m) { + try { + const j = JSON.parse(m[0]); + if (j.plainExplain) item.plainExplain = String(j.plainExplain).slice(0, 60); + if (j.takeAway) item.takeAway = String(j.takeAway).slice(0, 60); + } catch (e) { /* 解析失败留空,前端回退只显示标题 */ } + } + await new Promise(r => setTimeout(r, 300)); + } + const ok = items.filter(i => i.plainExplain).length; + console.log(` ✓ 人话解读完成 ${ok}/${targets.length}`); + return items; +} + /** 翻译英文内容到中文 */ async function translateItems(items) { const needTranslate = items.filter(i => !hasChinese(i.title)); @@ -342,6 +369,9 @@ async function main() { console.log(` ✓ 中文保底补充: ${need} 条`); } + // AI 人话解读(失败不阻断采集,字段留空前端自动回退) + try { await explainItems(items); } catch (e) { console.log(' ⚠ 人话解读失败:', e.message); } + // 全量刷新:清空旧数据(避免残留英文 / 测试数据) await Trend.deleteMany({});