airpredict/空气质量预测/源码/docs/superpowers/plans/2026-07-07-health-science-a...

22 KiB
Raw Permalink Blame History

健康装修科普可点击全文 实现计划

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.ts4 篇原创全文 + getArticlelanding 轮播、列表页、详情页三处共用。新增两条 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/landingsrc/pages/forum 一致,内联样式即可,不新增全局 CSS
  • hash 路由:文章 id 必须为 URL 友好的 ASCII kebab-case slug。
  • 文章 4 篇slug 固定:gb-standards / formaldehyde-release / summer-exceed / predict-before-cma
  • 每篇正文结尾附免责声明段落:「本文为科普整理,预测结果仅供参考,以 CMA 检测为准。」
  • 提交在仓库根 C:/codegit add 用含中文的完整路径),每任务末尾提交一次。
  • 修改 .umirc.ts 后需重启前端 dev配置变更不热更新前端 dev 端口 8001。

文件结构

  • src/common/articles.ts新建,文章类型 + 4 篇内容 + getArticleTask 1
  • src/pages/article/index.tsx新建,详情页 /article/:idTask 2
  • src/pages/articles/index.tsx新建,列表页 /articlesTask 3
  • .umirc.ts — 新增 2 条路由Task 2 加 /article/:idTask 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: '新装住宅的甲醛,为什么能持续释放 315 年?',
        summary: '人造板里的脲醛树脂胶会缓慢分解释放甲醛,释放周期长、受温湿度影响大。短期通风只能降一时浓度,真正的关键在源头材料的选择。',
        body: [
            {
                heading: '甲醛藏在哪里',
                paragraphs: [
                    '室内甲醛的主要来源是人造板材——刨花板、密度板、胶合板等。它们用脲醛树脂胶把木屑、木片粘合成板,而脲醛树脂胶里既含有未完全反应的游离甲醛,也含有遇水遇热会缓慢分解的化学结构。'
                ]
            },
            {
                heading: '为什么能释放这么久',
                paragraphs: [
                    '脲醛树脂在温度和湿度作用下会发生缓慢水解,不断把结合态的甲醛释放为游离甲醛。这个过程非常缓慢却持续,板材类污染源的释放周期可长达数年,通常说的 315 年正来源于此。',
                    '相比之下,油漆、涂料等表面材料的挥发性污染物释放周期短得多(数周到数月),这也是为什么装修后长期超标的“主角”往往是板材而非油漆。'
                ]
            },
            {
                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: getArticleArticle from @/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.tsroutes 数组中,帖子详情/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: articles from @/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.tsroutes 数组中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: articles from @/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,取代原 newstag/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 3landing 接线(卡片可点 + 全部文章跳转 + news 派生自 articles→Task 4路由→Task 2/3。全覆盖。
  • 类型一致Article/ArticleSection 字段(id/tag/date/title/summary/body/sourcesheading/paragraphs)在 Task 1 定义Task 2/3/4 消费一致;getArticle(id)articles 命名一致landing 渲染由 n.desc 改为 n.summary 与新数据源匹配。
  • 占位符:无 TODO/TBD4 篇正文全文已写入 Task 1各步含具体命令与预期。
  • 风险landing 删除本地 news 数组时须整段删除、勿残留;.umirc.ts 改动后 Task 2/3 需重启前端Task 4 仅 tsx 热更新。