docs: 健康装修科普可点击全文 实现计划
4 任务:内容模块(含4篇全文)、详情页、列表页、landing接线。纯前端。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7EkGJYZMdQtMeDEt3z4vL
This commit is contained in:
parent
5e1f382e22
commit
909fd7762e
|
|
@ -0,0 +1,474 @@
|
||||||
|
# 健康装修科普可点击全文 实现计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 让 landing「健康装修科普」的 4 篇卡片可点击进入看原创全文,并提供 `/articles` 列表页与 `/article/:id` 详情页。
|
||||||
|
|
||||||
|
**Architecture:** 纯前端静态。新建单一内容模块 `src/common/articles.ts`(4 篇原创全文 + `getArticle`),landing 轮播、列表页、详情页三处共用。新增两条 hash 公开路由,无后端/数据库改动。
|
||||||
|
|
||||||
|
**Tech Stack:** UmiJS 4 + React 18 + Antd 5 + TypeScript。无单测框架 —— 用 `npx tsc --noEmit -p tsconfig.json` 做类型验证(既有仅 2 条 eslint 类型根告警属预存,新增文件须零错误),交互留浏览器实测。
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- 工作目录:`C:/code/空气质量预测/源码/用户端/iapip-web`。所有相对路径以此为根。
|
||||||
|
- 页面为公开页,路由 `layout: false`,风格与 `src/pages/landing`、`src/pages/forum` 一致,内联样式即可,**不新增全局 CSS**。
|
||||||
|
- hash 路由:文章 `id` 必须为 URL 友好的 ASCII kebab-case slug。
|
||||||
|
- 文章 4 篇,slug 固定:`gb-standards` / `formaldehyde-release` / `summer-exceed` / `predict-before-cma`。
|
||||||
|
- 每篇正文结尾附免责声明段落:「本文为科普整理,预测结果仅供参考,以 CMA 检测为准。」
|
||||||
|
- 提交在仓库根 `C:/code`(`git add` 用含中文的完整路径),每任务末尾提交一次。
|
||||||
|
- 修改 `.umirc.ts` 后需重启前端 dev(配置变更不热更新);前端 dev 端口 8001。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
- `src/common/articles.ts` — **新建**,文章类型 + 4 篇内容 + `getArticle`(Task 1)
|
||||||
|
- `src/pages/article/index.tsx` — **新建**,详情页 `/article/:id`(Task 2)
|
||||||
|
- `src/pages/articles/index.tsx` — **新建**,列表页 `/articles`(Task 3)
|
||||||
|
- `.umirc.ts` — 新增 2 条路由(Task 2 加 `/article/:id`,Task 3 加 `/articles`)
|
||||||
|
- `src/pages/landing/index.tsx` — news 派生自 articles、卡片与「全部文章」接线(Task 4)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: 文章内容模块 `src/common/articles.ts`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/common/articles.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `interface ArticleSection { heading?: string; paragraphs: string[] }`;`interface Article { id: string; tag: string; date: string; title: string; summary: string; body: ArticleSection[]; sources?: string[] }`;`const articles: Article[]`(4 篇);`function getArticle(id: string): Article | undefined`。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建内容模块(完整内容如下,勿留占位)**
|
||||||
|
|
||||||
|
创建 `src/common/articles.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface ArticleSection {
|
||||||
|
heading?: string
|
||||||
|
paragraphs: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Article {
|
||||||
|
id: string
|
||||||
|
tag: string
|
||||||
|
date: string
|
||||||
|
title: string
|
||||||
|
summary: string
|
||||||
|
body: ArticleSection[]
|
||||||
|
sources?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const DISCLAIMER = '本文为科普整理,预测结果仅供参考,以 CMA 检测为准。'
|
||||||
|
|
||||||
|
export const articles: Article[] = [
|
||||||
|
{
|
||||||
|
id: 'gb-standards',
|
||||||
|
tag: '政策解读',
|
||||||
|
date: '专栏 · 2026.05',
|
||||||
|
title: '两大国标怎么读?GB/T 18883 与 GB 50325 的差别',
|
||||||
|
summary: '一个是“住进去后”的室内空气质量标准,一个是“交工验收时”的工程控制标准——限值与采样条件并不相同,看懂它们才能判断房子到底达不达标。',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
heading: '两个标准,管的是两件不同的事',
|
||||||
|
paragraphs: [
|
||||||
|
'GB/T 18883《室内空气质量标准》管的是“人住进去之后”的日常居住环境,属于推荐性标准(编号带 GB/T),衡量的是房间在正常生活状态下的空气质量。',
|
||||||
|
'GB 50325《民用建筑工程室内环境污染控制标准》管的是“工程交付验收时”的室内环境,属于强制性工程标准,是新建、扩建民用建筑竣工验收必须满足的底线。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '采样条件不同,数字不能直接比',
|
||||||
|
paragraphs: [
|
||||||
|
'两个标准最容易被忽略的差别是采样前的“关闭门窗时间”。工程验收类标准通常要求关闭门窗较短时间后采样,模拟的是交工状态;居住类标准要求关闭门窗较长时间后采样,模拟的是日常关窗睡觉的真实场景。',
|
||||||
|
'关闭时间越长,累积的污染物浓度往往越高。因此同一套房子,用两套标准、两种采样条件测出来的数字并不能直接比较,更不能混用限值。具体限值与采样时长以两部标准原文为准。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '该看哪一个',
|
||||||
|
paragraphs: [
|
||||||
|
'装修完工、准备验收,看 GB 50325;已经入住、想给居住环境做“体检”,看 GB/T 18883。',
|
||||||
|
'本系统在预测时同时对照两部国标的限值,帮助你在选材和验收两个阶段都心里有数。',
|
||||||
|
DISCLAIMER
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sources: ['GB/T 18883 室内空气质量标准', 'GB 50325 民用建筑工程室内环境污染控制标准']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'formaldehyde-release',
|
||||||
|
tag: '科普',
|
||||||
|
date: '专栏 · 2026.04',
|
||||||
|
title: '新装住宅的甲醛,为什么能持续释放 3–15 年?',
|
||||||
|
summary: '人造板里的脲醛树脂胶会缓慢分解释放甲醛,释放周期长、受温湿度影响大。短期通风只能降一时浓度,真正的关键在源头材料的选择。',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
heading: '甲醛藏在哪里',
|
||||||
|
paragraphs: [
|
||||||
|
'室内甲醛的主要来源是人造板材——刨花板、密度板、胶合板等。它们用脲醛树脂胶把木屑、木片粘合成板,而脲醛树脂胶里既含有未完全反应的游离甲醛,也含有遇水遇热会缓慢分解的化学结构。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '为什么能释放这么久',
|
||||||
|
paragraphs: [
|
||||||
|
'脲醛树脂在温度和湿度作用下会发生缓慢水解,不断把结合态的甲醛释放为游离甲醛。这个过程非常缓慢却持续,板材类污染源的释放周期可长达数年,通常说的 3–15 年正来源于此。',
|
||||||
|
'相比之下,油漆、涂料等表面材料的挥发性污染物释放周期短得多(数周到数月),这也是为什么装修后长期超标的“主角”往往是板材而非油漆。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '通风为什么治标不治本',
|
||||||
|
paragraphs: [
|
||||||
|
'通风只是把已经释放到空气中的甲醛换走,降低当下的空气浓度,并不改变材料内部的释放源。一旦停止通风、关闭门窗,浓度又会回升,这就是常见的“反弹”。',
|
||||||
|
'真正有效的做法是从源头入手:优先选用低释放等级(E0/E1)的板材、控制板材总用量,再配合长期通风与温湿度管理。',
|
||||||
|
DISCLAIMER
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sources: ['人造板脲醛树脂释放机理(通用建材科学知识)']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'summer-exceed',
|
||||||
|
tag: '指南',
|
||||||
|
date: '专栏 · 2026.03',
|
||||||
|
title: '夏天为什么更容易超标?温度与释放速率',
|
||||||
|
summary: '温度升高,材料的甲醛释放速率会明显增大。这也是“冬天测达标、夏天又超标”的原因。预测时把环境温湿度纳入计算,结果才靠谱。',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
heading: '温度:释放速率的放大器',
|
||||||
|
paragraphs: [
|
||||||
|
'温度越高,材料内部甲醛分子的扩散和释放越活跃,单位时间释放到空气中的量随之增大。夏季室温明显高于冬季,因此同一套房子在夏天测得的浓度往往更高。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '湿度也在帮倒忙',
|
||||||
|
paragraphs: [
|
||||||
|
'较高的空气湿度会加速脲醛树脂的水解,进一步促进甲醛释放。高温加上高湿的梅雨、盛夏季节,是甲醛浓度最容易冲高的时段。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '“冬测达标”不等于安全',
|
||||||
|
paragraphs: [
|
||||||
|
'冬季低温下测得达标,并不代表夏季高温高湿时依然达标。判断房子是否安全,必须结合采样时的温湿度条件来看。',
|
||||||
|
'本系统在预测中引入了温湿度修正因子,把房间实际的温度、湿度纳入计算,让预测结果更贴近你真正居住的季节。',
|
||||||
|
DISCLAIMER
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sources: ['温湿度对材料释放速率的影响(通用建材科学知识)']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'predict-before-cma',
|
||||||
|
tag: '方法',
|
||||||
|
date: '专栏 · 2026.02',
|
||||||
|
title: '先预测,再决定要不要做 CMA 检测',
|
||||||
|
summary: '上门检测有成本。用本系统先做一次免费预测、定位高风险房间与主要污染材料,再有针对性地安排第三方 CMA 检测,省钱也更有的放矢。',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
heading: 'CMA 检测是什么',
|
||||||
|
paragraphs: [
|
||||||
|
'CMA(中国计量认证)检测是具有法律效力的第三方检测,结果权威、可作为依据。但它需要在装修完成后上门采样,存在时间与费用成本,点位越多越贵。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '先预测的价值',
|
||||||
|
paragraphs: [
|
||||||
|
'在选材和装修阶段,用本系统先做一次免费预测:输入房间、环境与所用材料,估算各房间的污染物浓度,定位出高风险房间和贡献最大的污染材料。',
|
||||||
|
'这样你就能把有限的检测预算花在刀刃上——重点检测预测中接近或超过限值的房间,而不是每个房间都盲目下点位。'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: '怎么配合使用',
|
||||||
|
paragraphs: [
|
||||||
|
'预测显示某房间接近或超过限值,就针对该区域安排 CMA 检测复核;预测整体余量较大,则可适当减少检测点位。',
|
||||||
|
'预测基于材料释放模型与国标限值,是决策辅助手段;房子最终是否达标,仍以 CMA 检测结果为准。',
|
||||||
|
DISCLAIMER
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sources: ['CMA 计量认证检测流程(通用知识)']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export function getArticle(id: string): Article | undefined {
|
||||||
|
return articles.find(a => a.id === id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 类型检查**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
cd "C:/code/空气质量预测/源码/用户端/iapip-web" && npx tsc --noEmit -p tsconfig.json 2>&1 | grep -E "error TS" | grep -v "eslint" | grep -iE "articles" || echo "articles.ts 无类型错误"
|
||||||
|
```
|
||||||
|
Expected: `articles.ts 无类型错误`
|
||||||
|
|
||||||
|
- [ ] **Step 3: 确认 4 篇 slug 齐全**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
grep -oE "id: '[a-z-]+'" "C:/code/空气质量预测/源码/用户端/iapip-web/src/common/articles.ts"
|
||||||
|
```
|
||||||
|
Expected: 4 行 —— `id: 'gb-standards'`、`id: 'formaldehyde-release'`、`id: 'summer-exceed'`、`id: 'predict-before-cma'`。
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "C:/code" && git add "空气质量预测/源码/用户端/iapip-web/src/common/articles.ts" && git commit -m "feat(web): add health-science articles content module"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: 文章详情页 `/article/:id`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/pages/article/index.tsx`
|
||||||
|
- Modify: `.umirc.ts`(新增 `/article/:id` 路由)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `getArticle`、`Article` from `@/common/articles`。
|
||||||
|
- Produces: 路由 `/article/:id`(组件 `./article`)。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建详情页组件**
|
||||||
|
|
||||||
|
创建 `src/pages/article/index.tsx`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { history, useParams } from '@umijs/max'
|
||||||
|
import { getArticle } from '@/common/articles'
|
||||||
|
|
||||||
|
export default function ArticleDetail() {
|
||||||
|
const { id } = useParams<{ id: string }>()
|
||||||
|
const article = id ? getArticle(id) : undefined
|
||||||
|
|
||||||
|
if (!article) {
|
||||||
|
return (
|
||||||
|
<div style={{ maxWidth: 760, margin: '0 auto', padding: '80px 16px', textAlign: 'center' }}>
|
||||||
|
<p style={{ color: '#666' }}>文章不存在或已下线。</p>
|
||||||
|
<a onClick={() => history.push('/landing')} style={{ color: '#B43533' }}>← 返回首页</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ maxWidth: 760, margin: '0 auto', padding: '32px 16px 64px' }}>
|
||||||
|
<a onClick={() => history.push('/landing')} style={{ color: '#888' }}>← 返回首页</a>
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<div style={{ color: '#B43533', fontSize: 13 }}>{article.tag} · {article.date}</div>
|
||||||
|
<h1 style={{ fontSize: 26, margin: '10px 0 4px', lineHeight: 1.35 }}>{article.title}</h1>
|
||||||
|
<p style={{ color: '#666', fontSize: 15, margin: '8px 0 24px' }}>{article.summary}</p>
|
||||||
|
</div>
|
||||||
|
{article.body.map((sec, i) => (
|
||||||
|
<section key={i} style={{ marginBottom: 20 }}>
|
||||||
|
{sec.heading && <h2 style={{ fontSize: 18, margin: '18px 0 8px' }}>{sec.heading}</h2>}
|
||||||
|
{sec.paragraphs.map((p, j) => (
|
||||||
|
<p key={j} style={{ lineHeight: 1.9, color: '#333', margin: '0 0 10px' }}>{p}</p>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
{article.sources && article.sources.length > 0 && (
|
||||||
|
<div style={{ marginTop: 28, paddingTop: 16, borderTop: '1px solid #eee', color: '#999', fontSize: 13 }}>
|
||||||
|
<div style={{ marginBottom: 6 }}>参考来源</div>
|
||||||
|
<ul style={{ margin: 0, paddingLeft: 18 }}>
|
||||||
|
{article.sources.map((s, i) => <li key={i}>{s}</li>)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 新增路由**
|
||||||
|
|
||||||
|
`.umirc.ts` 的 `routes` 数组中,`帖子详情`(`/forum/thread/:id`)路由项之后追加:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: '文章详情',
|
||||||
|
path: '/article/:id',
|
||||||
|
component: './article',
|
||||||
|
layout: false
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 类型检查**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
cd "C:/code/空气质量预测/源码/用户端/iapip-web" && npx tsc --noEmit -p tsconfig.json 2>&1 | grep -E "error TS" | grep -ivE "eslint" || echo "无新增类型错误"
|
||||||
|
```
|
||||||
|
Expected: `无新增类型错误`(仅可能残留既有的 2 条 eslint 告警,已被过滤)。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 重启前端并浏览器验证**
|
||||||
|
|
||||||
|
重启前端 dev(配置变更需重启):停止当前 dev → 释放 8001 → `cd 用户端/iapip-web && PORT=8001 pnpm dev`。
|
||||||
|
浏览器打开 `http://localhost:8001/iapip-web/#/article/gb-standards`。
|
||||||
|
Expected: 显示该篇标题、`标签 · 日期`、摘要、分节正文(含小标题)、底部“参考来源”。访问 `#/article/nope` 显示“文章不存在”与返回链接。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "C:/code" && git add "空气质量预测/源码/用户端/iapip-web/src/pages/article/index.tsx" "空气质量预测/源码/用户端/iapip-web/.umirc.ts" && git commit -m "feat(web): article detail page and route"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: 文章列表页 `/articles`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/pages/articles/index.tsx`
|
||||||
|
- Modify: `.umirc.ts`(新增 `/articles` 路由)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `articles` from `@/common/articles`。
|
||||||
|
- Produces: 路由 `/articles`(组件 `./articles`)。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 创建列表页组件**
|
||||||
|
|
||||||
|
创建 `src/pages/articles/index.tsx`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { history } from '@umijs/max'
|
||||||
|
import { articles } from '@/common/articles'
|
||||||
|
|
||||||
|
export default function ArticleList() {
|
||||||
|
return (
|
||||||
|
<div style={{ maxWidth: 820, margin: '0 auto', padding: '32px 16px 64px' }}>
|
||||||
|
<a onClick={() => history.push('/landing')} style={{ color: '#888' }}>← 返回首页</a>
|
||||||
|
<h1 style={{ fontSize: 24, margin: '16px 0 24px' }}>健康装修科普</h1>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
|
{articles.map(a => (
|
||||||
|
<article
|
||||||
|
key={a.id}
|
||||||
|
onClick={() => history.push('/article/' + a.id)}
|
||||||
|
style={{ cursor: 'pointer', padding: 20, border: '1px solid #eee', borderRadius: 8, transition: 'box-shadow .2s' }}
|
||||||
|
>
|
||||||
|
<div style={{ color: '#B43533', fontSize: 13 }}>{a.tag} · {a.date}</div>
|
||||||
|
<h2 style={{ fontSize: 18, margin: '8px 0' }}>{a.title}</h2>
|
||||||
|
<p style={{ color: '#666', fontSize: 14, lineHeight: 1.7, margin: 0 }}>{a.summary}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 新增路由**
|
||||||
|
|
||||||
|
`.umirc.ts` 的 `routes` 数组中,Task 2 新增的 `文章详情` 路由项**之前**追加(`/articles` 与 `/article/:id` 路径前缀不同,顺序不影响匹配,但列表在前更清晰):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: '科普文章',
|
||||||
|
path: '/articles',
|
||||||
|
component: './articles',
|
||||||
|
layout: false
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 类型检查**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
cd "C:/code/空气质量预测/源码/用户端/iapip-web" && npx tsc --noEmit -p tsconfig.json 2>&1 | grep -E "error TS" | grep -ivE "eslint" || echo "无新增类型错误"
|
||||||
|
```
|
||||||
|
Expected: `无新增类型错误`
|
||||||
|
|
||||||
|
- [ ] **Step 4: 重启前端并浏览器验证**
|
||||||
|
|
||||||
|
重启前端 dev。浏览器打开 `http://localhost:8001/iapip-web/#/articles`。
|
||||||
|
Expected: 列出 4 篇(标签/日期/标题/摘要),点击任一进入对应 `/article/:id` 详情。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "C:/code" && git add "空气质量预测/源码/用户端/iapip-web/src/pages/articles/index.tsx" "空气质量预测/源码/用户端/iapip-web/.umirc.ts" && git commit -m "feat(web): articles list page and route"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: landing 接线(卡片可点、全部文章跳列表)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/pages/landing/index.tsx`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `articles` from `@/common/articles`(复用其 `id/tag/date/title/summary`)。
|
||||||
|
|
||||||
|
- [ ] **Step 1: 用 articles 替换本地 news 常量**
|
||||||
|
|
||||||
|
在 `landing/index.tsx` 顶部 import 段追加:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { articles } from '@/common/articles'
|
||||||
|
```
|
||||||
|
|
||||||
|
删除文件中现有的本地 `const news = [ ... ]`(4 条写死资讯的整段数组定义),并在其原位置替换为:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const news = articles
|
||||||
|
```
|
||||||
|
|
||||||
|
> 说明:`articles` 每项含 `tag/date/title/summary`,取代原 `news` 的 `tag/date/title/desc`。渲染处会把 `desc` 改为 `summary`(下一步)。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 轮播卡片改用 summary 并加点击跳转**
|
||||||
|
|
||||||
|
在 `#news` 区块的轮播渲染中,找到 `news.map((n, i) => ( ... ))` 生成的 `<article className="cslide" key={i}>`。做两处改动:
|
||||||
|
|
||||||
|
其一,给整张卡加点击跳转——将 `<article className="cslide" key={i}>` 改为:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<article className="cslide" key={i} style={{ cursor: 'pointer' }} onClick={() => history.push('/article/' + n.id)}>
|
||||||
|
```
|
||||||
|
|
||||||
|
其二,把卡片正文里的摘要字段 `n.desc` 改为 `n.summary`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<p>{n.summary}</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
(`阅读全文` 的 `.cslide-more` 已在整卡点击范围内,无需单独绑定。)
|
||||||
|
|
||||||
|
- [ ] **Step 3: 「全部文章」按钮跳列表页**
|
||||||
|
|
||||||
|
在 `#news` 区块头部,将:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<a className="btn" onClick={openPredict}>全部文章</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
改为:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<a className="btn" onClick={() => history.push('/articles')}>全部文章</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 类型检查**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
cd "C:/code/空气质量预测/源码/用户端/iapip-web" && npx tsc --noEmit -p tsconfig.json 2>&1 | grep -E "error TS" | grep -ivE "eslint" || echo "无新增类型错误"
|
||||||
|
```
|
||||||
|
Expected: `无新增类型错误`
|
||||||
|
|
||||||
|
- [ ] **Step 5: 浏览器验证(landing 为 tsx,热更新即可,无需重启)**
|
||||||
|
|
||||||
|
浏览器打开 `http://localhost:8001/iapip-web/#/landing`。
|
||||||
|
Expected:
|
||||||
|
- 「健康装修科普」轮播卡片可点击 → 进入对应 `/article/:id` 全文。
|
||||||
|
- 顶部「全部文章」→ `/articles` 列表页。
|
||||||
|
- 轮播自动播放、左右切换、圆点交互不受影响;卡片摘要文字正常显示(非空白)。
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "C:/code" && git add "空气质量预测/源码/用户端/iapip-web/src/pages/landing/index.tsx" && git commit -m "feat(web): wire landing news cards to article pages"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review 记录
|
||||||
|
|
||||||
|
- **Spec 覆盖**:内容模块→Task 1;详情页 `/article/:id`→Task 2;列表页 `/articles`→Task 3;landing 接线(卡片可点 + 全部文章跳转 + news 派生自 articles)→Task 4;路由→Task 2/3。全覆盖。
|
||||||
|
- **类型一致**:`Article`/`ArticleSection` 字段(`id/tag/date/title/summary/body/sources`、`heading/paragraphs`)在 Task 1 定义,Task 2/3/4 消费一致;`getArticle(id)`、`articles` 命名一致;landing 渲染由 `n.desc` 改为 `n.summary` 与新数据源匹配。
|
||||||
|
- **占位符**:无 TODO/TBD;4 篇正文全文已写入 Task 1;各步含具体命令与预期。
|
||||||
|
- **风险**:landing 删除本地 `news` 数组时须整段删除、勿残留;`.umirc.ts` 改动后 Task 2/3 需重启前端,Task 4 仅 tsx 热更新。
|
||||||
Loading…
Reference in New Issue