diff --git a/docs/superpowers/specs/2026-04-24-h5-material-browser-design.md b/docs/superpowers/specs/2026-04-24-h5-material-browser-design.md new file mode 100644 index 0000000..8ebbd80 --- /dev/null +++ b/docs/superpowers/specs/2026-04-24-h5-material-browser-design.md @@ -0,0 +1,212 @@ +# H5 材料浏览端 设计文档 + +- 日期:2026-04-24 +- 作者:caoqianming +- 状态:草案 + +## 1 · 背景与目标 + +在现有 mat3 项目(PC 端材料管理 + Django 后端)基础上,新增面向手机浏览器的 **H5 材料查看端**。 +核心诉求:**只读浏览**——登录后按 "大类 → 种类 → 子类 → 材料 → 详情" 的层级快速找到材料并查看详细信息。 +不包含新增 / 编辑 / 导入 / 审批等写操作。 + +## 2 · 项目结构与技术栈 + +### 2.1 组织方式 +新建独立项目 `frontend-h5/`,与现有 `frontend/` 平级;独立 Vite、独立依赖、独立打包。 + +``` +frontend-h5/ +├── index.html +├── vite.config.js # base: '/m/',dev proxy → 后端 +├── package.json +├── tailwind.config.js +├── postcss.config.js +└── src/ + ├── main.js + ├── App.vue + ├── router/ # vue-router 4,history 模式 + ├── store/ # pinia + ├── api/ # 参照 frontend/src/api 复制同名文件,保持一致 + ├── composables/ # useAuth / useInfiniteScroll / useToast ... + ├── styles/ # tailwind.css、全局变量 + ├── components/ # MaterialCard / CategoryCard / StarLevel / Chip / NavBar / Toast ... + └── views/ + ├── Login.vue + ├── Home.vue + ├── CategoryDetail.vue + └── MaterialDetail.vue +``` + +### 2.2 技术栈 +- Vue 3 + vue-router 4 + Pinia +- Axios(同 PC 端一致的拦截器:附 token、统一错误处理、401 跳登录) +- **Tailwind CSS**(自定义设计令牌),不使用 Vant/NutUI +- 少量自研组件(Toast、NavBar、Tab、InfiniteList、StarLevel、Chip、Skeleton) + +### 2.3 部署 +- 构建产物部署到 Nginx 子路径 `/m/`(与 PC 端同域名) +- `vite.config.js` 设 `base: '/m/'` +- 开发期 `vite` devServer 走本地端口(如 5174),通过 `server.proxy` 把 `/api` 转发到后端 + +### 2.4 登录态 +- `localStorage` 存 token,key 为 `h5_token`(与 PC 端 `token` 隔离,避免互相污染) +- Pinia `authStore` 暴露 `token` / `user` / `login()` / `logout()` +- Axios request 拦截器:存在 `h5_token` 则附 `Authorization` +- Axios response 拦截器:401 清 token 并跳 `/login?redirect=` + +## 3 · 路由与页面流 + +| 路径 | 页面 | 说明 | +| --- | --- | --- | +| `/login` | Login | 账号密码登录 | +| `/` | Home | 大类卡片 + 选中后展开种类区 | +| `/category/:major/:category` | CategoryDetail | 子类 Tab + 材料列表 | +| `/material/:id` | MaterialDetail | 材料详情(三块) | + +**路由守卫**:非 `/login` 页进入前校验 token;无 token → 跳 `/login?redirect=<原路径>`;登录成功后回跳。 + +**过渡**:页面切换用右推左 slide + fade CSS 过渡。 + +**状态保留**: +- Pinia `uiStore` 记录 Home 选中的大类、CategoryDetail 选中的子类 Tab、列表滚动位置 +- 从详情返回时恢复选中态和滚动位置 +- 使用 `` 缓存 Home 与 CategoryDetail + +## 4 · 数据接口 + +### 4.1 复用现有接口 + +| 用途 | 接口 | +| --- | --- | +| 登录 | `POST /auth/login/` | +| 当前用户 | `GET /auth/user/` | +| 材料列表 | `GET /material/?major_category=&material_category=&material_subcategory=&search=&page=&page_size=` | +| 材料详情 | `GET /material/{id}/` | +| 选项字典 | `GET /material/choices/` | + +### 4.2 新增接口 + +由于 `MaterialCategory` 与 `Material.major_category` 没有 FK 关系,"大类下的种类""种类下的子类" 需要从 `Material` 表按大类 distinct 计算。两个新接口都作为 `MaterialViewSet` 的 `@action` 提供。 + +**接口 1**:`GET /material/categories-by-major/?major_category=architecture` + +- 从 `Material` 表取 `major_category=X` 的记录,按 `material_category` 分组 count +- 响应: + ```json + [ + { "value": "地砖", "count": 12 }, + { "value": "涂料", "count": 8 } + ] + ``` + +**接口 2**:`GET /material/subcategories-by-category/?major_category=&material_category=` + +- 同上,按 `material_subcategory` 分组 count,过滤空值 +- 响应: + ```json + [ + { "value": "釉面砖", "count": 5 }, + { "value": "通体砖", "count": 3 } + ] + ``` + +"全部"分类不从后端返回,由前端在 Tab 第一项注入,点击时不传 `material_subcategory` 参数。 + +### 4.3 列表卡片使用字段 + +- `name`(材料名称) +- `cost_compare`(成本对比,百分数,负数表示更便宜) +- `advantage_display`(竞争优势,取前 2 个 chip 显示) +- `importance_level`(重要等级 chip) +- `score_level`(综合评分,1-3 星) +- `factory_short_name`(供应商简称) + +### 4.4 分页与加载 + +- `page_size = 20` +- 列表支持下拉刷新 + 上拉无限加载(基于 IntersectionObserver 的 `useInfiniteScroll`) + +## 5 · 视觉与交互设计 + +### 5.1 设计语言 +**克制的卡片式 · 暖中性色** + **墨绿品牌色** + +- 背景:白 `#FFFFFF` + 极浅灰分层 `#FAFAFA` / `#F5F4F2` +- 主色:`#2F4F3F`(墨绿),用于按钮、重点数值、选中态 +- 辅助色:红 `#D2584A`(核心)、蓝 `#5A7FB8`(优先)、灰 `#8A8A8A`(一般) +- 卡片圆角 `16-20px`;阴影 `0 1px 2px rgba(0,0,0,.04)`;不用重描边 +- 字体:系统栈 (`-apple-system, "PingFang SC", ...`);数值字段启用 `font-feature-settings: "tnum"` 等宽 +- 点按反馈:`active:scale-[0.98]` + 背景变深 + +以上颜色 / 圆角 / 间距在 `tailwind.config.js` 定义为 theme tokens,后续组件直接用工具类。 + +### 5.2 关键页面 + +**Login** +- 上 60% 留白 + 品牌标识 +- 下 40% 表单:用户名、密码、提交按钮 +- 失败信息由 Toast 从顶部下滑显示,2s 自动消失 + +**Home** +- 顶部栏:欢迎语 + 用户名 + 退出图标 +- 主体:2×2 大类卡片网格(建筑/景观/设备/装修),每卡显示大类名 + 材料总数 +- 选中大类后在下方 slide-down + fade 出现种类卡片区(2 列网格,卡带材料数角标) +- 未选中时下方显示引导文案 +- 选中的大类 ID 写入 `uiStore`,从详情返回恢复展开状态 + +**CategoryDetail** +- 顶部 NavBar:返回按钮 + 种类名标题 +- 吸顶的子类 Tab 栏(横向滚动,"全部" 为第一项,选中下划线 + 文字加重) +- 材料卡列表:通栏,左信息区(名称 / 供应商 / 优势 chip)+ 右数值区(成本 / 评分 / 重要等级) +- 下拉刷新、上拉无限加载 +- 空态:图标 + "暂无材料" + +**MaterialDetail** +- 顶部 NavBar +- 可选顶部 banner:`brochure` 宣传图(16:9,懒加载,点击可全屏预览) +- 三个 section,每个 section 是一张圆角卡: + + 1. **材料信息**:基础 / 应用 / 优势 / 成本 / 评分,`grid-cols-[auto_1fr]` 标签值布局;竞争优势、应用场景、重要等级、替代类型用 chip;4 项等级用星级可视化。长文本(优势说明、成本说明、应用说明)独立成段。 + 2. **品牌与供应商**:品牌、供应商简称/全称、合作模式、省-市、对接人、对接人联系方式。电话字段带 `tel:` 链接可一键拨打。 + 3. **案例信息**:落地项目、案例(保留换行)、经办人、备注。 + +### 5.3 通用交互 +- Loading:顶部 NProgress 细线进度条;列表用骨架屏 +- 错误:非 401 错误 Toast 提示;页面级错误态带"重试"按钮 +- 401:清 token + 跳登录 + +## 6 · 后端改动清单 + +`apps/material/views.py` +- `MaterialViewSet` 新增两个 `@action(detail=False, methods=['get'])`: + - `categories_by_major` + - `subcategories_by_category` +- 两者权限与现有列表一致(登录用户可访问) +- 考虑给 `material_category` / `material_subcategory` 字段的"查询计数"加缓存(低优,后续再说) + +无 model migration。 + +## 7 · 验收标准 + +- [ ] 手机浏览器(iOS Safari / 微信内置 / Android Chrome)访问 `/m/`,可完成 登录 → 大类 → 种类 → 子类 → 材料详情 的完整浏览 +- [ ] 详情页三块布局、字段映射与本文档 §5.2 一致 +- [ ] 从详情返回上级页面,滚动位置与选中 Tab 保持 +- [ ] 401 时自动跳登录且登录后回跳原路径 +- [ ] 包体积:首屏 gzip 后 < 150 KB(不含首图) +- [ ] 单手可达性:关键按钮在屏幕下半区 +- [ ] PC 端 `/` 功能不受影响 + +## 8 · 非目标 + +- 新增、编辑、导入、导出、审批 +- 离线缓存 / PWA +- 多语言 +- 手机号验证码登录(后续视需要再开 Spec) + +## 9 · 风险与开放项 + +- **微信内置浏览器差异**:目前未要求适配微信分享/JSSDK,仅确保页面能正常打开 +- **H5 与 PC 登录态隔离**:使用不同 localStorage key,后端 token 为同一份 JWT,无服务端改动成本 +- **种类 distinct 性能**:当前数据量小可直接 SQL `GROUP BY`;后续若过万条可再加索引或 Redis 缓存 +- **视觉最终稿**:本文档只定方向,具体 mockup 由后续 `frontend-design` 技能产出