22 KiB
健康装修科普可点击全文 实现计划
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:
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:
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:
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
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、Articlefrom@/common/articles。 -
Produces: 路由
/article/:id(组件./article)。 -
Step 1: 创建详情页组件
创建 src/pages/article/index.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)路由项之后追加:
{
name: '文章详情',
path: '/article/:id',
component: './article',
layout: false
},
- Step 3: 类型检查
Run:
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
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:
articlesfrom@/common/articles。 -
Produces: 路由
/articles(组件./articles)。 -
Step 1: 创建列表页组件
创建 src/pages/articles/index.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 路径前缀不同,顺序不影响匹配,但列表在前更清晰):
{
name: '科普文章',
path: '/articles',
component: './articles',
layout: false
},
- Step 3: 类型检查
Run:
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
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:
articlesfrom@/common/articles(复用其id/tag/date/title/summary)。 -
Step 1: 用 articles 替换本地 news 常量
在 landing/index.tsx 顶部 import 段追加:
import { articles } from '@/common/articles'
删除文件中现有的本地 const news = [ ... ](4 条写死资讯的整段数组定义),并在其原位置替换为:
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}> 改为:
<article className="cslide" key={i} style={{ cursor: 'pointer' }} onClick={() => history.push('/article/' + n.id)}>
其二,把卡片正文里的摘要字段 n.desc 改为 n.summary:
<p>{n.summary}</p>
(阅读全文 的 .cslide-more 已在整卡点击范围内,无需单独绑定。)
- Step 3: 「全部文章」按钮跳列表页
在 #news 区块头部,将:
<a className="btn" onClick={openPredict}>全部文章</a>
改为:
<a className="btn" onClick={() => history.push('/articles')}>全部文章</a>
- Step 4: 类型检查
Run:
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
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 热更新。