This commit is contained in:
2026-07-05 14:43:34 +08:00
commit 17a8027b20
277 changed files with 63794 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
# API 变更日志
所有非破坏性变更记录在此,**重大变更必须先经过设计评审**。
## [1.2.0] - 2026-06-14
### Added
- ✨ **Phase 3**:新增 `leads` 表(`phone + source` 唯一索引)
- ✨ **Phase 3**:`POST /api/v1/leads` 公开提交意向客户(自主问卷)
- ✨ **Phase 3**:`GET /api/v1/leads/stats` 鉴权统计
- ✨ **Phase 3**:`GET /api/v1/admin/leads` 鉴权列表(phone/source/keyword/时间过滤 + 分页)
- ✨ **Phase 3**:`GET /api/v1/admin/leads/:id` 详情
- ✨ **Phase 3**:`PUT /api/v1/admin/leads/:id/remark` 改备注
- ✨ **Phase 3**:`DELETE /api/v1/admin/leads/:id` 删除
## [1.1.0] - 2026-06-14
### Added
- ✨ **Phase 1**:后端按业务域重构为 `modules/{route,service,dto,README}` 四件套
- ✨ **Phase 1**:所有接口统一加 `/api/v1` 前缀,URL 路径升级
- ✨ **Phase 2**:Swagger UI 文档(`/api/docs`)和 OpenAPI 3.0 JSON 端点
- ✨ **Phase 2**:Appointment 模块新增 `GET /api/v1/appointments/stats` 鉴权接口
### Changed
- 修改:所有接口路径前缀从 `/api/...` 改为 `/api/v1/...`
- 修改:业务错误响应中,**资源不存在** 仍使用 HTTP 404,但响应体保持 `{code, message, data}` 结构
- 修改:管理后台自动给 `/api/` 路径加 `/v1` 前缀,调用方无需修改
### Removed
- 删除:`/api/hospitals` `/api/departments` 等无版本前缀的接口
## [1.0.0] - 2026-06-13
- 初始版本
- 7 个业务域:hospital / department / doctor / appointment / expert / banner / config
- 1 个鉴权域:adminAuth
- 同进程静态托管 `/admin` 管理后台
+68
View File
@@ -0,0 +1,68 @@
# API 接口文档
> **在线浏览**:http://124.221.67.199:3000/api/docs/
> **OpenAPI JSON**:http://124.221.67.199:3000/api/docs.json
## 基础约定
| 项 | 约定 |
|---|---|
| **基地址** | `http://<host>:3000/api/v1` |
| **版本** | `v1`(Phase 1 起所有接口) |
| **请求方法** | GET / POST / PUT / DELETE |
| **请求格式** | `Content-Type: application/json` |
| **字符集** | UTF-8 |
## 统一响应结构
```jsonc
{
"code": 0, // 0 = 成功,其他 = 业务错误
"message": "ok", // 业务消息
"data": { ... } // 业务数据
}
```
## 状态码
| 状态码 | 含义 | 出现场景 |
|---|---|---|
| 200 | 成功 | 接口正常返回 |
| 400 | 参数错误 | 缺字段、格式不对、校验失败 |
| 401 | 鉴权失败 | token 缺失/失效/密码错误 |
| 404 | 资源不存在 | 找不到 ID 对应的资源 |
| 500 | 服务器内部错误 | 异常抛出 |
> 业务错误统一用 HTTP 200 + `code !== 0` 表示,**仅在资源未找到时使用 404**。
## 鉴权
管理后台接口需要 JWT Bearer Token:
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx
```
通过 `POST /api/v1/admin/auth/login` 获得 token。
## 业务模块
| 模块 | 路径前缀 | 文档 |
|---|---|---|
| Hospital | `/hospitals` | [hospital.md](v1/hospital.md) |
| Department | `/departments` | [department.md](v1/department.md) |
| Doctor | `/doctors` | [doctor.md](v1/doctor.md) |
| Appointment | `/appointments` | [appointment.md](v1/appointment.md) |
| Expert | `/experts` | [expert.md](v1/expert.md) |
| Banner | `/banners` | [banner.md](v1/banner.md) |
| Config | `/config` | [config.md](v1/config.md) |
| AdminAuth | `/admin/auth` | [admin-auth.md](v1/admin-auth.md) |
| Lead | `/leads` · `/admin/leads` | [lead.md](v1/lead.md) |
## 变更日志
详见 [CHANGELOG.md](CHANGELOG.md)
## 同步机制
后端改接口 → 见 [SYNC.md](SYNC.md) 工作流(Phase 5 实施)
+48
View File
@@ -0,0 +1,48 @@
# AdminAuth · 管理员鉴权
JWT (jsonwebtoken)。请求时 Header `Authorization: Bearer <token>`。
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| POST | `/api/v1/admin/auth/login` | - | 登录 |
| GET | `/api/v1/admin/auth/me` | ✅ | 当前账号 |
| POST | `/api/v1/admin/auth/change-password` | ✅ | 修改密码 |
## POST /api/v1/admin/auth/login
**请求体**
```json
{ "username": "admin", "password": "Yygh@2026#Admin" }
```
**响应**
```json
{
"code": 0,
"message": "登录成功",
"data": {
"token": "eyJhbGc...",
"admin": { "id": "u-001", "username": "admin", "nickname": "系统管理员", "role": "super" }
}
}
```
**错误码**
- 400 用户名 / 密码必填
- 401 用户名或密码错误
- 500 服务器内部错误
## POST /api/v1/admin/auth/change-password
**请求体**
```json
{ "oldPassword": "...", "newPassword": "新密码至少 8 位" }
```
**错误码**
- 400 原密码 / 新密码必填
- 400 新密码至少 8 位
- 400 原密码错误
- 404 账号不存在
+63
View File
@@ -0,0 +1,63 @@
# Appointment · 预约挂号
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/v1/appointments` | - | 预约列表 |
| GET | `/api/v1/appointments/stats` | ✅ | 统计 |
| GET | `/api/v1/appointments/:id` | - | 详情 |
| POST | `/api/v1/appointments` | - | 创建 |
| PUT | `/api/v1/appointments/:id/cancel` | - | 取消 |
| PUT | `/api/v1/appointments/:id/confirm` | ✅ | 确认(管理后台) |
| PUT | `/api/v1/appointments/:id/complete` | ✅ | 完成(管理后台) |
| PUT | `/api/v1/appointments/:id` | ✅ | 更新备注 |
| DELETE | `/api/v1/appointments/admin/:id` | ✅ | 删除(管理后台) |
## GET /api/v1/appointments
**Query**
- `phone` - 按手机号过滤
- `status` - 'pending' / 'confirmed' / 'completed' / 'cancelled'
## POST /api/v1/appointments
**请求体**
```json
{
"patientName": "张三",
"patientAge": 30,
"patientPhone": "13800138000",
"date": "2026-06-14",
"departmentName": "肛肠",
"doctorId": "doc-001",
"doctorName": "李医生",
"timeSlot": "上午",
"symptom": "便血"
}
```
**校验规则**
- `patientName` 必填,长度 1-20
- `patientAge` 必填,1-149
- `patientPhone` 必填,匹配 `^1[3-9]\d{9}$`
- `date` 必填,ISO 日期 `YYYY-MM-DD`
- `departmentName` 必填,仅支持 `肛肠` / `胃肠`
- `doctorId` / `doctorName` / `timeSlot` / `symptom` 可选
**错误码**
- 400 缺少必填参数
- 400 手机号格式不正确
- 400 年龄参数错误
- 400 科室参数仅支持:肛肠 / 胃肠
- 500 服务器内部错误
## 状态机
```
pending ──confirm──► confirmed ──complete──► completed
│ │
└─cancel──► cancelled └─cancel──► cancelled
```
completed 状态不可取消;cancelled 状态不可确认/完成。
+14
View File
@@ -0,0 +1,14 @@
# Banner · 轮播图
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/v1/banners` | - | 列表 |
| POST | `/api/v1/banners` | ✅ | 新增 |
| PUT | `/api/v1/banners/:id` | ✅ | 更新 |
| DELETE | `/api/v1/banners/:id` | ✅ | 删除 |
## 字段
`id` / `title` / `subtitle` / `image` / `link` / `sort` / `enabled` / `createdAt` / `updatedAt`
+39
View File
@@ -0,0 +1,39 @@
# Config · 首页整体配置
存储在 `home_config.data` JSON 列。**单行单列,固定 `id=1`**。
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/v1/config/home` | - | 读取首页配置 |
| PUT | `/api/v1/config/home` | ✅ | 整体更新 |
| PUT | `/api/v1/config/home/hero` | ✅ | 更新 Hero 区 |
| PUT | `/api/v1/config/home/features` | ✅ | 整体替换功能卡 |
| PUT | `/api/v1/config/home/expert-team` | ✅ | 整体替换专家团队 |
| PUT | `/api/v1/config/home/info-banner` | ✅ | 更新信息条 |
| PUT | `/api/v1/config/home/hero-experts` | ✅ | 整体替换 Hero 专家 |
## GET /api/v1/config/home
**响应** `data` 形态:
```json
{
"hero": {
"title": "...",
"subtitle": "...",
"bgImage": "...",
"primaryCta": { "text": "...", "link": "/pages/appointment/index" }
},
"features": [
{ "icon": "🩺", "title": "..." , "link": "..." }
],
"expertTeam": [
{ "id": "he-001", "name": "...", "title": "...", "avatar": "..." }
],
"infoBanner": { "enabled": true, "texts": ["..."] },
"heroExperts": [
{ "id": "he-001", "name": "...", "title": "...", "avatar": "..." }
]
}
```
+34
View File
@@ -0,0 +1,34 @@
# Department · 科室
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/v1/departments` | - | 科室列表 |
## GET /api/v1/departments
**Query 参数**
- `hospitalId` (string, 可选) - 按医院过滤
- `enabled` ('true' | 'false', 可选) - 按启用状态过滤
**响应示例**
```json
{
"code": 0,
"message": "ok",
"data": [
{
"id": "d-001",
"name": "肛肠中心",
"icon": "🩺",
"description": "肛肠疾病专科",
"doctorCount": 5,
"sort": 1,
"enabled": true,
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
+39
View File
@@ -0,0 +1,39 @@
# Doctor · 医生
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/v1/doctors` | - | 医生列表 |
| GET | `/api/v1/doctors/:id` | - | 医生详情 |
| POST | `/api/v1/doctors/admin` | ✅ | 新增 |
| PUT | `/api/v1/doctors/admin/:id` | ✅ | 更新 |
| DELETE | `/api/v1/doctors/admin/:id` | ✅ | 删除 |
## GET /api/v1/doctors
**Query 参数**
- `departmentId` (string) - 按科室 ID
- `departmentName` (string) - 按科室名
- `enabled` (string) - 'true' / 'false'
**响应** - `data` 是 `Doctor[]`
## Doctor 字段
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 医生 ID |
| name | string | 姓名 |
| title | string | 职称 |
| departmentId | string \| null | 所属科室 ID |
| departmentName | string \| null | 所属科室名 |
| avatar | string | 头像 URL |
| specialty | string | 专长 |
| intro | string | 简介 |
| fee | number | 挂号费 |
| rating | number | 评分 |
| availableDates | string[] | 可预约日期(ISO) |
| enabled | boolean | 启用状态 |
| createdAt | string | ISO datetime |
| updatedAt | string | ISO datetime |
+16
View File
@@ -0,0 +1,16 @@
# Expert · 专家资源
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/v1/experts` | - | 列表(?enabled=true) |
| GET | `/api/v1/experts/:id` | - | 详情 |
| POST | `/api/v1/experts` | ✅ | 新增 |
| PUT | `/api/v1/experts/:id` | ✅ | 更新 |
| DELETE | `/api/v1/experts/:id` | ✅ | 删除 |
| POST | `/api/v1/experts/upload` | ✅ | 上传(url / base64) |
## 字段
`id` / `name` / `title` / `avatar` / `department` / `intro` / `sort` / `enabled` / `createdAt` / `updatedAt`
+46
View File
@@ -0,0 +1,46 @@
# Hospital · 医院信息
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/v1/hospitals` | - | 医院列表 |
| GET | `/api/v1/hospitals/:id` | - | 医院详情 |
## GET /api/v1/hospitals
**响应示例**
```json
{
"code": 0,
"message": "ok",
"data": [
{
"id": "h-001",
"name": "苏州肛泰中医院",
"level": "三甲",
"address": "苏州市姑苏区吴中西路 888 号",
"phone": "0512-6586-3999",
"intro": "胃肠肛肠专科医院 · 三甲名医亲诊",
"description": "...",
"banner": "https://...",
"logo": "https://...",
"established": "1998",
"departments": 12,
"doctors": 35,
"beds": 200,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
]
}
```
## GET /api/v1/hospitals/:id
**路径参数**
- `id` (string, 必填) - 医院 ID
**错误响应**
- 404 医院不存在
- 500 服务器内部错误
+22
View File
@@ -0,0 +1,22 @@
# HospitalImage · 医院图片
维护医院"简介"栏目展示的图片 (轮播/单图)。小程序 `hospital` 页"医院简介" tab 直接拉取启用项展示。
## 接口
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /api/v1/hospital-images | 否 | 列表 (支持 `?enabled=true` 过滤) |
| GET | /api/v1/hospital-images/:id | 否 | 详情 |
| POST | /api/v1/hospital-images | 是 | 新增 |
| PUT | /api/v1/hospital-images/:id | 是 | 更新 |
| DELETE | /api/v1/hospital-images/:id | 是 | 删除 |
| POST | /api/v1/uploads/image | 是 | 通用图片上传 (base64 → dataURL) |
## DTO 字段
- `hospitalId` 关联医院 id (默认 h-001)
- `image` 图片 URL 或 dataURL
- `caption` 图片说明
- `sort` 排序 (升序)
- `enabled` 是否启用
+113
View File
@@ -0,0 +1,113 @@
# Lead · 意向客户(Phase 3)
> 自主问卷用户信息采集的统一入口,落到 `leads` 表,供管理后台跟进。
## 接口表
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| POST | `/api/v1/leads` | - | 提交(小程序自测问卷、首页落地等) |
| GET | `/api/v1/leads/stats` | ✅ | 统计:总数 / 24h / 按 source 分组 |
| GET | `/api/v1/admin/leads` | ✅ | 列表,支持 phone / source / keyword / 时间过滤 |
| GET | `/api/v1/admin/leads/:id` | ✅ | 详情 |
| PUT | `/api/v1/admin/leads/:id/remark` | ✅ | 修改备注 |
| DELETE | `/api/v1/admin/leads/:id` | ✅ | 删除 |
## POST /api/v1/leads
**请求体**
```json
{
"name": "张三",
"phone": "13800138000",
"symptom": "便血近 1 周,伴有肛门坠胀",
"questionnaire": {
"total": 5,
"answers": { "0": "sometimes", "1": "no" },
"createdAt": "2026-06-14T08:30:00.000Z"
},
"source": "self-test",
"score": 5
}
```
**校验规则**
- `name` 必填,1-20 字符
- `phone` 必填,匹配 `^1[3-9]\d{9}$`
- `symptom` 可选,≤ 500 字
- `questionnaire` 必填,任意 JSON 对象
- `source` 可选,枚举 `self-test` / `home` / `appointment` / `other`,默认 `self-test`
- `score` 可选,0-100
**响应**
```json
{ "code": 0, "message": "提交成功", "data": { "id": "lead-1781398800000", "createdAt": "2026-06-14 16:30:00" } }
```
**错误码**
| HTTP | code | message |
|---|---|---|
| 400 | 1 | 姓名必填 / 姓名长度需在 1-20 字符 |
| 400 | 1 | 手机号必填 / 手机号格式不正确 |
| 400 | 1 | 症状描述不超过 500 字 |
| 400 | 1 | 问卷答案 questionnaire 必填且为对象 |
| 400 | 1 | source 仅支持:self-test / home / appointment / other |
| 400 | 1 | 该手机号在同渠道已提交过,请勿重复 |
| 500 | 500 | 服务器内部错误 |
## GET /api/v1/admin/leads
**Query**
- `phone` 手机号精确匹配
- `source` 渠道
- `keyword` 姓名模糊匹配
- `startDate` / `endDate` 时间范围(`YYYY-MM-DD`)
- `limit` 默认 50,最大 200
- `offset` 默认 0
**响应**
```json
{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"id": "lead-1781398800000",
"name": "张三",
"phone": "13800138000",
"symptom": "便血近 1 周",
"questionnaire": { "total": 5, "answers": {} },
"source": "self-test",
"score": 5,
"remark": "",
"createdAt": "2026-06-14 16:30:00",
"updatedAt": "2026-06-14 16:30:00"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
}
```
## 数据表
```sql
CREATE TABLE leads (
id VARCHAR(32) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
phone VARCHAR(20) NOT NULL,
symptom TEXT,
questionnaire JSON NOT NULL,
source VARCHAR(20) NOT NULL DEFAULT 'self-test',
score INT DEFAULT 0,
remark VARCHAR(500),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_phone_source (phone, source)
);
```
唯一约束 `(phone, source)`:同手机号在同一渠道只保留一条。
@@ -0,0 +1,165 @@
# 苏州肛泰中医院 · v2 平台化升级实施计划
> **For agentic workers:** 按 Phase 顺序执行,每个 Phase 都是可独立交付的 milestone,用户可在任意 Phase 完成后暂停/调整方向。
> **执行策略**:Phase 内逐步交付,每个 bite-sized 任务完成后等待用户确认再继续。
**Goal**:把当前"医院小程序 + 后端 + 管理后台"从"能跑"升级到"企业级可维护",覆盖 4 大需求:API 标准化、视觉重塑、问卷增强、模块化重构。
**Architecture**:
- 后端:Express + MySQL,新增 Swagger/OpenAPI 自动文档 + Markdown 接口规范 + `/api/leads` 新表 + 业务域 service 拆分
- 前端:Taro 4 + React 18 + TS 5,新增 `modules/lead` 业务域模块 + 全局 design tokens 重塑(低饱和度专业色系)
- 管理后台:原生 SPA 风格不变,新增 leads 客户列表 + 接口版本号展示
**Tech Stack**:
- 后端:Express 4 / mysql2 / dayjs / **新增** swagger-ui-express + swagger-jsdoc + apidoc
- 前端:Taro 4.1 / React 18 / TypeScript 5 / Sass / Zustand / **新增** modules/lead 域
- 数据库:MariaDB 10.x,新增 `leads` 表
---
## Phase 总览
| Phase | 主题 | 涉及文件数 | 估时 | 依赖 |
|---|---|---|---|---|
| **P1** | 业务域 service 拆分(模块化基础) | 14 改 + 6 新增 | 1.5h | 无 |
| **P2** | Swagger + Markdown 接口文档 | 4 改 + 3 新增 | 1h | P1 |
| **P3** | 问卷 / 自主测试 + 用户信息采集 + leads 表 | 1 改 + 2 新增 | 1.5h | P1, P2 |
| **P4** | 全局设计 tokens 重塑 + 主流程 6 页视觉升级 | 1 改 + 6 改 | 1.5h | P1 |
| **P5** | 接口版本控制 / 同步机制 | 2 改 + 1 新增 | 0.5h | P2 |
**总估时**:~6h(4 个 milestone 验收点,可分多次完成)
---
## Phase 1:业务域 service 拆分(高内聚低耦合)
**目标**:把 `server/src/routes/*` 按业务域重组,添加 README,建立模块化骨架。
### 任务清单
- [ ] **Task 1.1** 新建 `server/src/modules/hospital/` 目录,迁移 `routes/hospital.js` → `modules/hospital/route.js` + 新增 `modules/hospital/service.js`(查询逻辑)+ `modules/hospital/README.md`
- [ ] **Task 1.2** 同样模式处理 `department` / `doctor` / `appointment` / `expert` / `banner` / `config` 6 个域
- [ ] **Task 1.3** 新建 `server/src/modules/index.js` 统一注册 router
- [ ] **Task 1.4** 修改 `server/src/index.js`:把 `app.use('/api/...', router)` 改为 `app.use('/api/v1/...', router)`(**v1 是版本号起点**)
- [ ] **Task 1.5** 写 `server/src/modules/README.md`:描述每个模块的 `route.js`(HTTP 层)/`service.js`(业务逻辑)/`dto.js`(数据转换)`README.md`(接口说明)规范
- [ ] **Task 1.6** 验证:PM2 重启后 `curl /api/v1/hospitals` 仍返回 `code:0, data:[...]`
**验收标准**:
- 每个业务域目录包含 route/service/dto/README 四件套
- 服务启动日志接口列表全部以 `/api/v1` 开头
- 前端 services/*.ts 调用的 URL 由 `/api/...` 改为 `/api/v1/...`(同时**前端 BASE_URL 配置新增版本号**)
- 数据库交互全部经过 service.js,route.js 不再直接写 SQL
---
## Phase 2:Swagger UI + Markdown 接口文档
**目标**:自动生成 OpenAPI 3.0 规范 + Markdown 业务说明,浏览器可访问 `/api/docs`。
### 任务清单
- [ ] **Task 2.1** 安装依赖:`cd server && npm i swagger-ui-express swagger-jsdoc`
- [ ] **Task 2.2** 新建 `server/src/swagger/spec.js`:配置 swagger-jsdoc,扫描 `modules/*/route.js` 的 JSDoc 注释,生成 OpenAPI 3.0 spec
- [ ] **Task 2.3** 在 `server/src/index.js` 注册 `app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(spec))` + `app.get('/api/docs.json', ...)` 输出原始 spec
- [ ] **Task 2.4** 给 7 个 module 的 route.js 关键接口加 JSDoc:`@swagger` 路径,包含 path/method/parameters/responses
- [ ] **Task 2.5** 新建 `docs/api/README.md`:API 总览(基地址、版本、认证、错误码约定)
- [ ] **Task 2.6** 新建 `docs/api/v1/hospital.md`、`department.md`、`doctor.md`、`appointment.md`、`expert.md`、`banner.md`、`config.md`、`admin-auth.md`、`lead.md`(lead 是 P3 的)
- [ ] **Task 2.7** 新建 `docs/api/CHANGELOG.md`:版本变更记录
**验收标准**:
- 浏览器访问 `http://124.221.67.199:3000/api/docs/` 显示完整 API 文档
- 8 个 markdown 文档,每个包含:接口路径 / 方法 / 请求参数表 / 响应示例 / 错误码 / 业务说明
- CHANGELOG 记录 v1.0.0 → v1.1.0 的接口变更
---
## Phase 3:自主问卷用户信息采集 + leads 表
**目标**:问卷底部新增姓名/手机号/症状采集区,前后端校验,提交到 `/api/v1/leads`。
### 任务清单
- [ ] **Task 3.1** 数据库:在 `server/src/schema.sql` 末尾追加 `CREATE TABLE leads (...)`:id / name / phone / symptom / questionnaire JSON / source / created_at,加唯一索引 `(phone, source)`
- [ ] **Task 3.2** 后端:新建 `server/src/modules/lead/route.js`(POST 提交 / GET 列表 / GET 详情)
- [ ] **Task 3.3** 后端:新建 `server/src/modules/lead/service.js`:手机号正则、问卷 JSON 解析、source 枚举校验
- [ ] **Task 3.4** 后端:新建 `server/src/modules/lead/dto.js` + `README.md`
- [ ] **Task 3.5** 前端:新建 `src/modules/lead/types.ts`(Lead / LeadForm TS 类型)
- [ ] **Task 3.6** 前端:新建 `src/modules/lead/api.ts`(submitLead / fetchLeads)
- [ ] **Task 3.7** 前端:修改 `src/pages/self-test/index.tsx`:底部加 `<View className={styles.userInfoSection}>`,包含姓名 Input / 手机 Input / 症状 Textarea,"提交问卷结果"按钮
- [ ] **Task 3.8** 前端:表单校验(姓名 1-20 字符 / 手机 `^1[3-9]\d{9}$` / 症状 ≤500 字 / 必填),错误内联展示
- [ ] **Task 3.9** 前端:成功后调 `submitLead`,后端返回 leadId,跳 `/pages/success/index?type=lead&id=...`
- [ ] **Task 3.10** 管理后台:在 `admin/index.html` 加 `/admin/leads` 标签页,列表展示姓名/手机/来源/时间/问卷结果 JSON 折叠
- [ ] **Task 3.11** 后端:新增 `GET /api/v1/admin/leads`(authRequired)给管理后台用
**验收标准**:
- `curl -X POST /api/v1/leads -d '{"name":"...","phone":"...","questionnaire":{...}}'` 返回 200 + leadId
- 手机号不合法 → 后端 400 `{code:400, message:"手机号格式不正确"}`
- 数据库 `SELECT * FROM leads` 看到刚提交的行
- 小程序端提交 → 管理后台 `/admin/leads` 立即看到
---
## Phase 4:设计 tokens 重塑 + 主流程 6 页视觉升级
**目标**:低饱和度专业色系(去掉原 #1f7ae0 高饱蓝),升级 6 个主流程页视觉。
### 任务清单
- [ ] **Task 4.1** 重构 `src/styles/theme.scss`:主色改为 `#0f766e`(低饱和度医绿)或 `#475569`(深石板蓝),辅色 `border-radius` 提升到 12-16rpx,阴影由硬阴影改为柔阴影 `0 2px 8px rgba(15,23,42,.04) → 0 1px 3px rgba(15,23,42,.06), 0 4px 16px rgba(15,23,42,.04)`
- [ ] **Task 4.2** 重构 `src/styles/variables.scss`:间距 8pt 网格(`$spacing-1: 4rpx; $spacing-2: 8rpx; ...`),字号阶梯
- [ ] **Task 4.3** 新建 `src/styles/mixins.scss`:`@mixin card` / `@mixin btn-primary` / `@mixin section-title`,统一卡片/按钮/章节头样式
- [ ] **Task 4.4** 首页:使用 mixins 重写 `home/index.module.scss`,增大 Hero 留白,专家头像由 64rpx → 80rpx
- [ ] **Task 4.5** 医院页:科室网格由 4 列 → 3 列,卡片高度 220rpx → 240rpx,加 hover 状态
- [ ] **Task 4.6** 预约页:表单分组由密集 → 留白充足,提交按钮加 24rpx 圆角
- [ ] **Task 4.7** 问卷页:题目卡片加大内边距,选项 hover 加 border 高亮
- [ ] **Task 4.8** 成功页:成功图标由 emoji → SVG,结果列表用左侧色条
- [ ] **Task 4.9** 我的页:菜单项由 2 列 → 1 列 + 右侧箭头,更接近企业级
- [ ] **Task 4.10** 全站 6 个页面 `pages.config.json` 标题统一"苏州肛泰中医院 - X"
**验收标准**:
- 全站色系用 `<kbd>ColorPick</kbd>` 取色,所有页面主色都从 tokens 派生(无硬编码颜色)
- 阴影统一两层:近阴影(按钮/输入框) + 远阴影(卡片/弹层)
- 6 个页面对比之前截图,整体更"沉稳专业"(非"活泼可爱")
---
## Phase 5:接口版本控制与同步机制
**目标**:建立 `/api/v1` 之后到 `/api/v2` 的兼容/弃用/移除规范。
### 任务清单
- [ ] **Task 5.1** 后端:新增 `server/src/middleware/apiVersion.js`:响应头加 `X-API-Version: v1` 和 `Sunset` 头(如果 deprecated)
- [ ] **Task 5.2** 后端:新增 `server/src/middleware/changelogBroadcast.js`:每次启动时把当前版本号 + CHANGELOG.md 摘要写入 `server_runtime.log`
- [ ] **Task 5.3** 前端:新增 `src/services/apiVersion.ts`:请求时带 `X-Client-Version` 头,响应时检查 `Sunset` 头并 toast 提示
- [ ] **Task 5.4** 新建 `docs/api/SYNC.md`:描述后端接口变更 → 前端同步流程
1. 后端开发改 route.js + swagger JSDoc
2. `npm run docs:gen` 自动更新 markdown
3. 在 `docs/api/CHANGELOG.md` 加一行
4. 前端 `pnpm sync-api` 拉取 `/api/docs.json` 生成 `types/api/*.d.ts`
5. 前端 IDE 报类型错误 → 定位 + 修改
- [ ] **Task 5.5** 新建 `docs/superpowers/plans/2026-06-13-api-sync-workflow.md`:详细描述开发者日常流程
**验收标准**:
- `curl -I /api/v1/hospitals` 响应头包含 `X-API-Version: v1`
- 前端 `apiVersion.ts` 在收到 Sunset 头时 `Taro.showToast({ title: '接口即将停用,请更新 APP', icon: 'none' })`
- docs/api/SYNC.md 描述的工作流可被新开发者 5 分钟内理解
---
## 风险与回滚
| 风险 | 缓解 |
|---|---|
| Phase 4 视觉改坏既有页面 | 保留 theme 旧版 v1 注释,可在 1 行切换 |
| Phase 1 改 URL 前缀 `/api/v1` 影响前端联调 | 同步修改 `src/services/request.ts` 的 BASE_URL 拼接逻辑 |
| Phase 3 leads 表字段缺索引 | `(phone, source)` 加唯一索引 + `created_at` 加普通索引 |
| Phase 5 Sunset 头跨域被浏览器剥 | Nginx/proxy 不应剥自定义响应头,标注到部署文档 |
## 验收里程碑(用户暂停点)
- **M1**:Phase 1 完成后,前端 `pnpm dev` + 后端 `npm start` 全栈联通,业务不变
- **M2**:Phase 2 完成后,`/api/docs` 可访问,8 个 markdown 文档齐全
- **M3**:Phase 3 完成后,问卷可提交 + 管理后台可见
- **M4**:Phase 4 完成后,6 页视觉一致、设计 tokens 单点控制
- **M5**:Phase 5 完成后,接口版本与同步流程文档化
@@ -0,0 +1,154 @@
# Phase 4 · 设计 tokens 重塑 + 主流程 6 页视觉升级
## 范围
- 6 个核心页面: home / hospital / appointment / phone-consult / self-test / success
- 目标: 整洁美观、提升品质感
- 手段: 设计 token 优化 + 6 页视觉重构
## 设计 Thesis(一句话)
> 用 **医疗级克制美学** + **人文摄影** + **精准排版层级**,告别卡片堆叠,改为分栏 + divider + 全宽背景区,让用户在一屏内完成 "我该做什么" 的判断。
## Visual Thesis
- **氛围**: 医疗专业 × 苏州江南温润(降低饱和、加微暖)
- **材质**: 大面积白 + 极浅米灰背景(#fafbfc),用品牌蓝绿做局部强调
- **能量**: 安静、稳定、可信赖——> 留白 + 排版权重做主节奏
## Content Plan(6 页)
| # | 页面 | 主任务 | 主导视觉 |
|---|---|---|---|
| 1 | home 首页 | 引导用户进入预约/咨询/自测 | 真实医院外景 hero + 3 个分栏入口 + 科室速选 |
| 2 | hospital 医院详情 | 了解医院+选科室 | 医院环境图 + 实力数据 + 科室列表(无卡片) |
| 3 | appointment 预约 | 提交预约信息 | 单一表单焦点 + 步骤条 |
| 4 | phone-consult 电话咨询 | 选专家并拨号 | 专家横向滚动(行) + 拨号区 |
| 5 | self-test 自测 | 答题+留资 | 进度条主导 + 题卡 + 底部信息采集 |
| 6 | success 成功 | 确认+下一步 | 大号 ✓ 符号 + 信息行(无卡) |
## Interaction Thesis(2-3 个核心动效)
1. **入场 fadeInUp**: 首屏内容从下方 16rpx 渐入,延迟 0/80/160ms 形成节奏
2. **按下反馈**: 按钮 active 状态 `scale(0.97)` + 透明度 0.85(已有 mixin)
3. **scroll reveal**: ScrollView 内区块在视口出现时 fadeIn(用 IntersectionObserver,小程序内用 onScroll)
## Token 优化清单
### A. 颜色: 收敛与精致
- **主品牌色**: `#1f7ae0` → `#0a6dff`(更精炼的"医蓝"),加 50/100/200/300/500/600/700 完整色阶
- **辅色**: 青绿 `#15b9a3` 保留,但使用率≤15%
- **新增"米杏"色系**: `#fafbfc` 背景 / `#f4f1ec` 卡片底 / `#f0e6d6` 强调区(降低冷感、提升温度)
- **价格色**: `#ff5e3a` → `#e85a3c`(更稳重)
- 删:`$color-text-disabled` 浅灰改为 `$color-text-tertiary` 的 60% opacity
### B. 排版: 增强层级
- 引入"两套字重"原则: 标题用 $font-weight-bold,正文用 regular,**不混用 medium 太多**
- 新增 `$letter-spacing-tight: -0.01em`(标题字距)
- 新增 `$letter-spacing-loose: 0.04em`(小标签字距)
### C. 阴影: 收为 3 级
- 现有 5 级 → 3 级: `$shadow-1` (xs) / `$shadow-2` (md) / `$shadow-3` (lg)
- 删除 `$shadow-xs`、合并 `$shadow-sm` → 1
### D. 圆角: 收敛
- 主要用 8/12/16,删除 20/24(过圆损失专业感)
- 按钮仍用全圆角(`$radius-button: 999rpx`)
### E. 间距: 加 `$page-gutter`
- 现有 `$page-spacing: 32rpx` → 改为 `$page-gutter: 24rpx`(更紧凑、更易扫读)
### F. 新增 Z-index 标准化
- 现有 ok,不动
## 6 页视觉重做
### home 首页
- **删除**: 顶部 banner 条、三个并列卡片式入口、医生推荐卡
- **改为**:
- 全宽 hero: 医院外景图 + 品牌名 + 一句承诺
- 3 个入口: divider 分隔的 3 行(不用卡片),左 icon + 标题 + 副文 + 箭头
- 科室速选: 横向滚动 chip(去卡片)
- 专家推荐: 1 个真实人物照 + 文案段(无卡)
- 底部信息采集卡(自测入口): 全宽背景图 + 居中标题 + 1 按钮
### hospital 医院详情
- **删除**: 顶部图、卡式信息块
- **改为**:
- 医院环境照(全宽,16:9)
- 医院简介: 段落式,首字下沉
- 实力数据: 4 列数字 + 标签(无卡,divider 隔开)
- 科室列表: 折叠面板(可展开),不用卡片
- 底部固定: 立即预约 CTA
### appointment 预约
- **删除**: 多步向导里的"步骤卡"
- **改为**:
- 步骤指示器: 4 个圆点 + 连线
- 单一表单焦点: 当前步骤内容居中,信息密度大
- 字段: 大输入框(高 96rpx),label 在上方
- 提交按钮: 底部固定主色渐变按钮(高 96rpx)
### phone-consult 电话咨询
- **删除**: 9 宫格、卡片式专家列表
- **改为**:
- 顶部 hero: "24h 健康热线" + 大号电话号(可点击拨号)
- 专家列表: 横向滚动,每个专家是一行(头像 + 姓名 + 简介 + 拨号按钮)
- 底部说明: 2-3 段文字,无卡
### self-test 自测
- 已 Phase 3 改造完成,本阶段只做视觉微调
- 优化: 进度条改用更细的 4rpx,圆角降低
- 题卡从阴影改为分隔线
- 用户信息区改为无阴影背景
### success 成功
- **删除**: 卡片包裹
- **改为**:
- 顶部 1/3 高度: 大号 ✓ 居中(无背景)
- 中部信息行: divider 分隔的列表(医院/患者/电话/科室/时间)
- 底部双按钮: 圆角胶囊,主次分明
## 实施步骤
1. **Task 4.1**: 重写 [src/styles/theme.scss](file:///Users/skn/Desktop/yuyueguahao/src/styles/theme.scss),新增色阶/排版变量,精简阴影圆角
2. **Task 4.2**: 同步更新 [src/styles/variables.scss](file:///Users/skn/Desktop/yuyueguahao/src/styles/variables.scss) 转发关系(避免变量重复定义)
3. **Task 4.3**: home 页重做
4. **Task 4.4**: hospital 页重做
5. **Task 4.5**: appointment 页重做
6. **Task 4.6**: phone-consult 页重做
7. **Task 4.7**: self-test 页视觉微调
8. **Task 4.8**: success 页重做
9. **Task 4.9**: 部署 + 视觉验证(上传 taro dist 或仅改 SCSS)
## 设计原则硬约束
- ✅ 严格 1 个主品牌色 + 1 个辅色
- ✅ 卡片只在 card-as-interaction 时使用(如自测题卡、日期选择)
- ✅ 所有 hero 区是全宽,不留左右页边距
- ✅ 标题用 bold,正文用 regular,不混用 medium 太多
- ✅ 留白 ≥ 32rpx(区块之间)
- ✅ 字距: 标题 -0.01em,小标签 0.04em
## 暂缓(避免范围蔓延)
- 不做 Framer Motion 等大型动效库(小程序不友好)
- 不换字体(用系统字体)
- 不做国际化
## 风险
- 小程序 SCSS 变量是编译期,改 theme 后需要 `npm run build:weapp`
- 后端与本次重做无关,只改前端 + theme
## 验收
- 6 页均可 `npm run dev:weapp` 编译通过
- 在微信开发者工具中,6 页布局正常,无样式丢失
- 后端 API 调用与 Phase 3 保持一致(无破坏)
File diff suppressed because it is too large Load Diff