11 KiB
11 KiB
设计文档:导航改版 + 环保建材 + 健康装修大家谈论坛
- 日期:2026-07-07
- 目标项目:
C:\code\空气质量预测\源码(用户端 iapip-web / 服务端 iapip-svr) - 工作目录:前端
源码/用户端/iapip-web,后端源码/服务端/iapip-svr - 状态:已通过需求评审,待写实现计划
1. 背景与目标
用户端首页(landing,用户端/iapip-web/src/pages/landing/index.tsx)当前顶部导航为 5 项(资讯科普 / 治理案例 / 如何使用 / 污染源识别 / 专业看板),前三项为页面内锚点滚动、内容全为写死的静态数据。
本次改动:
- 顶部导航改为 4 项:健康装修科普、预测案例、环保建材、健康装修大家谈。
- 「环保建材」为新区块,从后端真实公共材料库拉取 E0 低释放材料展示。
- 「健康装修大家谈」为新的真功能论坛(进阶版:板块、发帖、楼层回帖、点赞、分页;公开浏览、手机登录后发帖)。
前两项(健康装修科普、预测案例)仅为现有区块的标签改名,内容不变。
2. 范围
2.1 纳入范围
- 导航 4 项改版与两处区块标签改名(纯前端)。
- 环保建材:新增 1 个免认证后端接口 + landing 新区块(真实数据)。
- 论坛:新增 3 张 Prisma 表 + 1 个后端 controller + 2 个前端页面 + 1 个前端 API 封装。
2.2 不纳入范围(YAGNI)
- 论坛板块的后台增删(板块用代码常量固定)。
- 帖子/回帖的富文本、@提及、通知、搜索。
- 管理端对论坛的审核后台(作者可自删自己的帖/回帖已满足基本治理;管理端审核为后续可选项)。
- 环保建材的收藏、跳转到材料详情等交互(仅展示)。
3. A. 导航栏与 landing 文案(前端)
文件:用户端/iapip-web/src/pages/landing/index.tsx
| 新导航项 | 行为 | 实现 |
|---|---|---|
| 健康装修科普 | 滚动到 #news |
现「资讯科普」区块改名;nav-links 文案改,#news 区块 sec-tag 由「资讯 · 科普」改为「健康装修科普」 |
| 预测案例 | 滚动到 #cases |
现「治理案例」区块改名;sec-tag 由「治理案例」改为「预测案例」,内容(before/after 卡片)不变 |
| 环保建材 | 滚动到新 #eco |
见第 4 节 |
| 健康装修大家谈 | history.push('/forum') |
见第 5–7 节 |
其他:
- 「免费试算」主按钮(
openPredict)保留不变。 - 原「如何使用」
#how区块保留在页面,仅从导航移除。 - 原「专业看板」入口从导航移除;CTA 区块的「查看专业看板」按钮(
goPro)与登录入口仍可到达工作台。
4. B. 环保建材(后端免认证接口 + 前端区块)
4.1 后端接口
- 路由:
GET /api/mtrl/eco,免认证(不挂authn),加在material.ts中。 - 逻辑:查询
PublicMaterial中deleted=0 且 display=1、eco_level='E0'的材料;若不足 8 条,补E1。按created_at倒序,最多返回 8 条。 - 返回字段(精简,不返回释放参数三件套等敏感/无关字段):
material_id, name, category, brand, factory, eco_level, methanal(甲醛标称值,若有)。 - 空结果返回
[],前端优雅处理。
4.2 前端区块
- landing 新增
#eco区块,位置置于#cases与#how之间(顺序:news → cases → eco → how)。 - 组件挂载时通过
api.material.getEcoMaterials()拉取,useState保存。 - 展示:卡片网格(复用现有
.cases-grid/.case或新增等价样式),每张卡显示材料名、品牌、类别、环保等级标签(E0/E1)。 - 加载中不阻塞页面其他区块;返回空数组时该区块整体隐藏(
data.length === 0则不渲染<section>)。 - 前端 API:
src/services/api/material.ts新增getEcoMaterials,走/api/mtrl/eco。
5. C. 论坛数据模型(Prisma)
文件:服务端/iapip-svr/prisma/schema.prisma。新增 3 张表。板块(board)不建表,用后端常量固定。
5.1 板块常量
在 服务端/iapip-svr/src/common/constants.ts 定义:
FORUM_BOARDS = ['甲醛治理', '异味TVOC', '环保材料', '装修避坑', '求助问答']
帖子的 board 字段值必须属于该列表(后端校验)。
5.2 表结构
model ForumThread {
id Int @id @default(autoincrement())
board String
title String
content String @db.Text
author_id String
author_name String
like_count Int @default(0)
reply_count Int @default(0)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted Int @default(0)
replies ForumReply[]
likes ForumThreadLike[]
@@map("forum_thread")
}
model ForumReply {
id Int @id @default(autoincrement())
thread_id Int
thread ForumThread @relation(fields: [thread_id], references: [id], onDelete: Cascade)
floor Int
content String @db.Text
author_id String
author_name String
created_at DateTime @default(now())
deleted Int @default(0)
@@map("forum_reply")
}
model ForumThreadLike {
thread_id Int
thread ForumThread @relation(fields: [thread_id], references: [id], onDelete: Cascade)
user_id String
created_at DateTime @default(now())
@@id([thread_id, user_id])
@@map("forum_thread_like")
}
author_name冗余存 username,列表/详情展示不连表。- 软删
deleted沿用项目惯例;ForumThreadLike无软删(toggle 直接删行)。 - 迁移:
npm run dbpush:dev(prisma db push)。
6. D. 论坛后端 API
新建 controller 服务端/iapip-svr/src/controllers/forum.ts,prefix('/api/forum'),在 src/index.ts 中 app.use(forumRoutes) 注册。登录鉴权复用 authn(CERT_TYPE.ACCOUNT),作者身份取 ctx.state.acct_cert(id、username)。
| 方法 路径 | 鉴权 | 功能 | 说明 |
|---|---|---|---|
GET /api/forum/boards |
公开 | 板块列表 + 各板块帖数 | 返回 [{board, count}],count 统计 deleted=0 |
GET /api/forum/threads |
公开 | 分页帖子列表 | query:board?(缺省全部)、page(默认1)、size(默认20)。按 created_at desc。返回列表项 + 总数 |
GET /api/forum/threads/:id |
公开 | 帖子详情 + 分页回帖 | 回帖按 floor asc,跳过 deleted=1 的回帖。best-effort:若请求带有效 token 则解析出用户并附 liked 布尔,否则省略该字段 |
POST /api/forum/threads |
登录 | 发帖 | body:{board,title,content}。board 必须在 FORUM_BOARDS,title/content 非空且限长 |
POST /api/forum/threads/:id/replies |
登录 | 回帖 | body:{content}。事务内 floor = 当前 reply_count + 1,同时 reply_count++ |
POST /api/forum/threads/:id/like |
登录 | 点赞/取消 toggle | 有 like 行则删并 like_count--;无则插并 like_count++。返回最新 {liked, like_count} |
DELETE /api/forum/threads/:id |
登录 | 软删自己的帖 | 仅 author_id === cert.id,否则 403 |
DELETE /api/forum/replies/:id |
登录 | 软删自己的回帖 | 同上;不回退 floor(其余楼层号保持不变)。详情接口跳过 deleted=1 的回帖,前端不展示已删楼层 |
- 校验:沿用项目 AJV
validate中间件风格(参照material.ts)。 - 错误码:非法 board / 空标题用
1000 INVALID_REQ_DATA;越权删除用3000 FORBIDDEN;帖子不存在用4000(沿用现有粗粒度约定)。 - 并发:回帖 floor 与 reply_count、点赞计数均在
db.$transaction内完成,避免竞态。
7. E. 论坛前端
前端 API 封装:新建 用户端/iapip-web/src/services/api/forum.ts,聚合进 src/services/api/index.ts(api.forum.*)。
新增两个公开路由(无需登录即可访问,路由风格参照 landing 在 .umirc.ts 中的注册方式):
7.1 /forum — 论坛首页
- 板块 Tab(含「全部」):点击切换
board过滤。 - 帖子列表:标题、作者名、时间、回帖数、点赞数;点击行 →
/forum/thread/:id。 - 分页控件(Antd
Pagination)。 - 右上「发帖」按钮:未登录 → 弹
PhoneAuthModal;已登录 → 打开发帖表单(Modal:板块选择 + 标题 + 正文)。发帖成功后刷新列表。
7.2 /forum/thread/:id — 帖子详情
- 帖子正文(作者、时间、板块)。
- 点赞按钮:显示
like_count与当前用户liked状态;未登录点则弹登录框,登录后 toggle。 - 楼层回帖列表(floor + 作者 + 时间 + 内容),分页。
- 底部回帖框:未登录点则弹登录框;已登录提交后刷新回帖列表并楼层递增。
- 若为作者本人,帖子/自己的回帖显示「删除」入口(软删)。
7.3 登录复用
- 统一复用现有
PhoneAuthModal(src/components/phone-auth-modal)。登录态判断沿用localStorage[LOC_STOR_KEY.AUTH_TOKEN]。 - 已登录请求自动带
Authorization(沿用services/api现有请求拦截)。
8. 测试
8.1 后端
- 发帖 → 该板块帖数 +1;非法 board 被拒(1000)。
- 连续回帖 → floor 依次 1、2、3,
reply_count同步;未登录回帖被拒(401/2000)。 - 点赞 toggle:首次 +1 且
liked=true,再次 -1 且liked=false;同一用户不重复计数。 - 越权删除他人帖 → 403;删除自己的 →
deleted=1,列表不再出现。 - 分页:
page/size边界正确,返回总数一致。
8.2 环保建材
- 有 E0 材料时返回 ≤8 条且字段精简;无任何公共材料时返回
[],前端#eco区块隐藏、不报错。
8.3 前端
- 未登录浏览
/forum与详情正常;触发发帖/回帖/点赞时弹登录框,登录后动作继续完成。 - 导航 4 项滚动/跳转正确;
#news/#cases改名后内容不变。
9. 涉及文件清单
后端 服务端/iapip-svr
prisma/schema.prisma(新增 3 表)src/common/constants.ts(新增FORUM_BOARDS)src/controllers/forum.ts(新建)src/controllers/material.ts(新增GET /eco)src/index.ts(注册forumRoutes)
前端 用户端/iapip-web
src/pages/landing/index.tsx(导航 4 项、区块改名、新增#eco区块及取数)src/pages/forum/index.tsx(新建,论坛首页)src/pages/forum/thread.tsx(新建,帖子详情)src/services/api/forum.ts(新建)src/services/api/material.ts(新增getEcoMaterials)src/services/api/index.ts(聚合api.forum).umirc.ts(新增/forum、/forum/thread/:id公开路由)
10. 风险与注意
- 数据库迁移:改 schema 需
npm run dbpush:dev;生产库需谨慎(本地开发用 Docker MySQL)。 - landing 首个真实接口:环保建材使 landing 依赖后端,需保证后端未起/空数据时页面不崩。
- 楼层稳定性:回帖软删后不回退 floor,避免楼层错位。
- 登录态一致性:论坛与现有测算入口共用手机登录 token,行为需一致。