@ -0,0 +1,310 @@ |
|||||||
|
# IGG4 小程序埋点实施计划 |
||||||
|
|
||||||
|
## 概要 |
||||||
|
|
||||||
|
基于 `doc/track.md` 文档和信达项目的成熟埋点模式,为 IGG4 项目实施页面级 PV 埋点和点击级事件埋点。采用信达项目已验证的 `PG_` / `BTN_` 双层命名体系,复用现有 `app.mpBehavior()` 基础设施。 |
||||||
|
|
||||||
|
## 现状分析 |
||||||
|
|
||||||
|
| 维度 | 现状 | |
||||||
|
|------|------| |
||||||
|
| 埋点函数 | `app.mpBehavior({ PageName, doctor? })` — 仅 1 处调用 (`MINI_PROGRAM_VISIT`) | |
||||||
|
| API 接口 | 患者端 `?r=igg4/mp-behavior/add`,医生端 `?r=igg4/doctor/mp-behavior/add` | |
||||||
|
| 类型签名 | `{ PageName: string; doctor?: boolean }` — 需扩展以支持 `detailId` 等可选参数 | |
||||||
|
| 页面级埋点 | 完全未实现(track.md 中 25+ 页面均未添加调用) | |
||||||
|
| 点击级埋点 | 完全未实现 | |
||||||
|
| 命名规范 | 无(信达项目使用 `PG_`/`BTN_` 前缀命名体系) | |
||||||
|
|
||||||
|
## 命名规范(参照信达项目) |
||||||
|
|
||||||
|
| 类别 | 前缀 | 格式 | 示例 | |
||||||
|
|------|------|------|------| |
||||||
|
| 页面浏览(患者端) | `PG_` | `PG_Patient{功能名}` | `PG_PatientHome`、`PG_PatientRepository` | |
||||||
|
| 页面浏览(医生端) | `PG_` | `PG_Doctor{功能名}` | `PG_DoctorHome`、`PG_DoctorPatient` | |
||||||
|
| 点击事件(患者端) | `BTN_` | `BTN_Patient{功能名}{操作}` | `BTN_PatientHomeDoctorCard`、`BTN_PatientMyVip` | |
||||||
|
| 点击事件(医生端) | `BTN_` | `BTN_Doctor{功能名}{操作}` | `BTN_DoctorHomeInvite`、`BTN_DoctorPatientDetail` | |
||||||
|
|
||||||
|
### PageName 与 track.md 页面对照表 |
||||||
|
|
||||||
|
**患者端页面级埋点(PG_):** |
||||||
|
|
||||||
|
| track.md 页面路径 | PageName | |
||||||
|
|-------------------|----------| |
||||||
|
| pages/index/index | `PG_PatientHome` | |
||||||
|
| gift/pages/recordList/index | `PG_PatientRecordList` | |
||||||
|
| gift/pages/record/index | `PG_PatientRecordAdd` | |
||||||
|
| gift/pages/recordDetail/index | `PG_PatientRecordDetail` | |
||||||
|
| pages/interactivePatient/index | `PG_PatientInteractive` | |
||||||
|
| pages/doctorDetail/index | `PG_PatientDoctorDetail` | |
||||||
|
| pages/repository/index | `PG_PatientRepository` | |
||||||
|
| pages/repositoryDetail/index | `PG_PatientRepositoryDetail` | |
||||||
|
| pages/my/index | `PG_PatientMy` | |
||||||
|
| pages/qaFormDetail/index | `PG_PatientQaFormDetail` | |
||||||
|
| pages/qaForm/index | `PG_PatientQaForm` | |
||||||
|
| pages/caseReport/index | `PG_PatientCaseReport` | |
||||||
|
| pages/personalInformation/index | `PG_PatientPersonalInfo` | |
||||||
|
| pages/cancellation/index | `PG_PatientCancellation` | |
||||||
|
| pages/mySave/index | `PG_PatientMySave` | |
||||||
|
| pages/feedback/index | `PG_PatientFeedback` | |
||||||
|
|
||||||
|
**医生端页面级埋点(PG_):** |
||||||
|
|
||||||
|
| track.md 页面路径 | PageName | |
||||||
|
|-------------------|----------| |
||||||
|
| doctor/pages/d_home/index | `PG_DoctorHome` | |
||||||
|
| doctor/pages/d_invite/index | `PG_DoctorInvite` | |
||||||
|
| doctor/pages/d_patient/index | `PG_DoctorPatient` | |
||||||
|
| doctor/pages/d_patientDetail/index | `PG_DoctorPatientDetail` | |
||||||
|
| doctor/pages/d_taskList/index | `PG_DoctorTaskList` | |
||||||
|
| doctor/pages/d_taskDetail/index | `PG_DoctorTaskDetail` | |
||||||
|
| doctor/pages/d_createTask/index | `PG_DoctorCreateTask` | |
||||||
|
| doctor/pages/d_interactive/index | `PG_DoctorInteractive` | |
||||||
|
| doctor/pages/d_interactiveDoctor/index | `PG_DoctorInteractiveDoctor` | |
||||||
|
| doctor/pages/d_my/index | `PG_DoctorMy` | |
||||||
|
| doctor/pages/d_userInfo/index | `PG_DoctorUserInfo` | |
||||||
|
| doctor/pages/d_changeDoctor/index | `PG_DoctorChangeDoctor` | |
||||||
|
|
||||||
|
## 实施步骤 |
||||||
|
|
||||||
|
### 第 1 步:扩展 mpBehavior 类型签名 |
||||||
|
|
||||||
|
**文件**: `src/app.ts` + `typings/index.d.ts` |
||||||
|
|
||||||
|
扩展 `mpBehavior` 方法签名,支持可选的 `detailId` 参数(用于详情页等场景): |
||||||
|
|
||||||
|
```ts |
||||||
|
// typings/index.d.ts |
||||||
|
mpBehavior: (data: { PageName: string; doctor?: boolean; detailId?: string }) => void |
||||||
|
|
||||||
|
// src/app.ts — 实现签名同步更新 |
||||||
|
mpBehavior(data: { PageName: string; doctor?: boolean; detailId?: string }) { |
||||||
|
// ... 现有逻辑不变,detailId 会随 data 对象一起 POST 到后端 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
> 说明:与信达项目一致,`detailId` 等额外字段作为 POST data 的一部分直接发送给后端,无需在 `mpBehavior` 函数内做特殊处理。 |
||||||
|
|
||||||
|
### 第 2 步:实施患者端页面级埋点(16 个页面) |
||||||
|
|
||||||
|
在每个页面的 `onShow` 中(有 `waitLogin` 的在 `waitLogin().then()` 内),调用 `app.mpBehavior()`。 |
||||||
|
|
||||||
|
**通用模式:** |
||||||
|
|
||||||
|
```ts |
||||||
|
// 患者端页面(无 waitLogin) |
||||||
|
onShow() { |
||||||
|
app.mpBehavior({ PageName: 'PG_PatientXxx' }) |
||||||
|
} |
||||||
|
|
||||||
|
// 患者端页面(有 waitLogin) |
||||||
|
onShow() { |
||||||
|
app.waitLogin().then(() => { |
||||||
|
app.mpBehavior({ PageName: 'PG_PatientXxx' }) |
||||||
|
// ... 原有业务逻辑 |
||||||
|
}) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**修改文件清单:** |
||||||
|
|
||||||
|
| # | 文件路径 | PageName | 嵌入位置 | |
||||||
|
|---|---------|----------|---------| |
||||||
|
| 1 | `src/pages/index/index.ts` | `PG_PatientHome` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 2 | `src/gift/pages/recordList/index.ts` | `PG_PatientRecordList` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 3 | `src/gift/pages/record/index.ts` | `PG_PatientRecordAdd` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 4 | `src/gift/pages/recordDetail/index.ts` | `PG_PatientRecordDetail` | `onShow` → `waitLogin().then()` 内首行,带 `detailId` | |
||||||
|
| 5 | `src/pages/interactivePatient/index.ts` | `PG_PatientInteractive` | `onLoad` → `waitLogin().then()` 内首行 | |
||||||
|
| 6 | `src/pages/doctorDetail/index.ts` | `PG_PatientDoctorDetail` | `onShow` → `waitLogin().then()` 内首行,带 `detailId` | |
||||||
|
| 7 | `src/pages/repository/index.ts` | `PG_PatientRepository` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 8 | `src/pages/repositoryDetail/index.ts` | `PG_PatientRepositoryDetail` | `onShow` → `waitLogin().then()` 内首行,带 `detailId` | |
||||||
|
| 9 | `src/pages/my/index.ts` | `PG_PatientMy` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 10 | `src/pages/qaFormDetail/index.ts` | `PG_PatientQaFormDetail` | `onShow` → `waitLogin().then()` 内首行,带 `detailId` | |
||||||
|
| 11 | `src/pages/qaForm/index.ts` | `PG_PatientQaForm` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 12 | `src/pages/caseReport/index.ts` | `PG_PatientCaseReport` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 13 | `src/pages/personalInformation/index.ts` | `PG_PatientPersonalInfo` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 14 | `src/pages/cancellation/index.ts` | `PG_PatientCancellation` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 15 | `src/pages/mySave/index.ts` | `PG_PatientMySave` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 16 | `src/pages/feedback/index.ts` | `PG_PatientFeedback` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
|
||||||
|
### 第 3 步:实施医生端页面级埋点(12 个页面) |
||||||
|
|
||||||
|
医生端埋点需传入 `doctor: true`,API 路径切换为 `?r=igg4/doctor/mp-behavior/add`。 |
||||||
|
|
||||||
|
**通用模式:** |
||||||
|
|
||||||
|
```ts |
||||||
|
// 医生端页面 |
||||||
|
onShow() { |
||||||
|
app.waitLogin().then(() => { |
||||||
|
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorXxx' }) |
||||||
|
// ... 原有业务逻辑 |
||||||
|
}) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**修改文件清单:** |
||||||
|
|
||||||
|
| # | 文件路径 | PageName | 嵌入位置 | |
||||||
|
|---|---------|----------|---------| |
||||||
|
| 1 | `src/doctor/pages/d_home/index.ts` | `PG_DoctorHome` | `onLoad` → `waitLogin().then()` 内首行 | |
||||||
|
| 2 | `src/doctor/pages/d_invite/index.ts` | `PG_DoctorInvite` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 3 | `src/doctor/pages/d_patient/index.ts` | `PG_DoctorPatient` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 4 | `src/doctor/pages/d_patientDetail/index.ts` | `PG_DoctorPatientDetail` | `onShow` → `waitLogin().then()` 内首行,带 `detailId` | |
||||||
|
| 5 | `src/doctor/pages/d_taskList/index.ts` | `PG_DoctorTaskList` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 6 | `src/doctor/pages/d_taskDetail/index.ts` | `PG_DoctorTaskDetail` | `onShow` → `waitLogin().then()` 内首行,带 `detailId` | |
||||||
|
| 7 | `src/doctor/pages/d_createTask/index.ts` | `PG_DoctorCreateTask` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 8 | `src/doctor/pages/d_interactive/index.ts` | `PG_DoctorInteractive` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 9 | `src/doctor/pages/d_interactiveDoctor/index.ts` | `PG_DoctorInteractiveDoctor` | `onShow` → `waitLogin().then()` 内首行,带 `detailId` | |
||||||
|
| 10 | `src/doctor/pages/d_my/index.ts` | `PG_DoctorMy` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 11 | `src/doctor/pages/d_userInfo/index.ts` | `PG_DoctorUserInfo` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
| 12 | `src/doctor/pages/d_changeDoctor/index.ts` | `PG_DoctorChangeDoctor` | `onShow` → `waitLogin().then()` 内首行 | |
||||||
|
|
||||||
|
### 第 4 步:实施点击级埋点(BTN_) |
||||||
|
|
||||||
|
在关键交互的事件处理函数首行,插入 `app.mpBehavior()` 调用。 |
||||||
|
|
||||||
|
#### 患者端点击埋点 |
||||||
|
|
||||||
|
**首页 — `src/pages/index/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `routerTo(e)` 中 doctor 类卡片 | `BTN_PatientHomeDoctorCard` | 迷你医生卡片点击 | |
||||||
|
| `routerTo(e)` 中功能入口 | `BTN_PatientHomeFunctionCard` | 功能卡片跳转 | |
||||||
|
| `handleNextVisit()` | `BTN_PatientHomeNextVisit` | 下次复诊入口 | |
||||||
|
| `handleArticleView(e)` | `BTN_PatientHomeArticle` | 文章点击 | |
||||||
|
| `handleArticleSave(e)` | `BTN_PatientHomeArticleSave` | 文章收藏 | |
||||||
|
| `handleToastOk()`(绑定医生确认) | `BTN_PatientHomeBindDoctor` | 绑定医生弹窗确认 | |
||||||
|
| `handlePublic()` | `BTN_PatientHomeFollowPublic` | 关注公众号 | |
||||||
|
|
||||||
|
**我的页面 — `src/pages/my/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `routerVipTo()` | `BTN_PatientMyVip` | VIP 认证入口 | |
||||||
|
| `handleMyHealthRecord(e)` | `BTN_PatientMyHealthRecord` | 健康档案 | |
||||||
|
| `handleQaForm()` | `BTN_PatientMyQaForm` | 健康问卷 | |
||||||
|
| `routerTo(e)` 中 mySave | `BTN_PatientMySave` | 我的收藏 | |
||||||
|
| `routerTo(e)` 中 personalInfo | `BTN_PatientMyPersonalInfo` | 个人信息 | |
||||||
|
| `routerTo(e)` 中 feedback | `BTN_PatientMyFeedback` | 问题反馈 | |
||||||
|
| `handleMiniDoctor()` | `BTN_PatientMyMiniDoctor` | 迷你医生 | |
||||||
|
|
||||||
|
**医患互动 — `src/pages/interactivePatient/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleHot(e)` | `BTN_PatientInteractiveHotQuestion` | 热门问题点击 | |
||||||
|
| `handleQuestion(e)` | `BTN_PatientInteractiveQuestion` | 快捷问题点击 | |
||||||
|
| `handleConfirm()` | `BTN_PatientInteractiveSend` | 发送消息 | |
||||||
|
| `handleAdl()` | `BTN_PatientInteractiveAdl` | ADL 评估入口 | |
||||||
|
| `handleQol()` | `BTN_PatientInteractiveQol` | QoL 入口 | |
||||||
|
| `handleDoctorDetail()` | `BTN_PatientInteractiveDoctorDetail` | 查看医生详情 | |
||||||
|
|
||||||
|
**知识库 — `src/pages/repository/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `tabChange(e)` | `BTN_PatientRepositoryTab` | Tab 切换 | |
||||||
|
| `handleSearch()` | `BTN_PatientRepositorySearch` | 搜索 | |
||||||
|
| `handleCate(e)` | `BTN_PatientRepositoryCategory` | 分类筛选 | |
||||||
|
| `handleDetail(e)` | `BTN_PatientRepositoryArticle` | 文章详情 | |
||||||
|
| `handleStar(e)` | `BTN_PatientRepositoryStar` | 点赞 | |
||||||
|
| `handleToggleSave(e)` | `BTN_PatientRepositorySave` | 收藏 | |
||||||
|
|
||||||
|
**就诊记录列表 — `src/gift/pages/recordList/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 新增就诊记录入口 | `BTN_PatientRecordListAdd` | 新增记录按钮 | |
||||||
|
| 点击记录项 | `BTN_PatientRecordListItem` | 记录列表项点击 | |
||||||
|
|
||||||
|
**就诊记录新增 — `src/gift/pages/record/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 提交/保存按钮 | `BTN_PatientRecordAddSubmit` | 保存记录 | |
||||||
|
|
||||||
|
**医生详情 — `src/pages/doctorDetail/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 查看互动 | `BTN_PatientDoctorDetailInteractive` | 进入互动页 | |
||||||
|
|
||||||
|
#### 医生端点击埋点 |
||||||
|
|
||||||
|
**医生首页 — `src/doctor/pages/d_home/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handlePatient(e)` | `BTN_DoctorHomePatient` | 患者列表入口 | |
||||||
|
| `handleInvite()` | `BTN_DoctorHomeInvite` | 邀约按钮 | |
||||||
|
| `handleSelectYear1()` | `BTN_DoctorHomeSelectYear` | 年份筛选 | |
||||||
|
|
||||||
|
**邀约患者 — `src/doctor/pages/d_invite/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 发送邀约按钮 | `BTN_DoctorInviteSend` | 发送邀约 | |
||||||
|
|
||||||
|
**患者列表 — `src/doctor/pages/d_patient/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 搜索 | `BTN_DoctorPatientSearch` | 搜索患者 | |
||||||
|
| 点击患者项 | `BTN_DoctorPatientItem` | 患者列表项 | |
||||||
|
|
||||||
|
**互动列表 — `src/doctor/pages/d_interactive/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 搜索 | `BTN_DoctorInteractiveSearch` | 搜索互动 | |
||||||
|
| 点击互动项 | `BTN_DoctorInteractiveItem` | 互动列表项 | |
||||||
|
|
||||||
|
**互动页 — `src/doctor/pages/d_interactiveDoctor/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 发送消息 | `BTN_DoctorInteractiveSend` | 发送消息 | |
||||||
|
|
||||||
|
**群发列表 — `src/doctor/pages/d_taskList/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 新增群发 | `BTN_DoctorTaskListAdd` | 新增群发入口 | |
||||||
|
| 点击群发项 | `BTN_DoctorTaskListItem` | 群发列表项 | |
||||||
|
|
||||||
|
**群发新增 — `src/doctor/pages/d_createTask/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 提交群发 | `BTN_DoctorCreateTaskSubmit` | 创建群发 | |
||||||
|
|
||||||
|
**切换医生 — `src/doctor/pages/d_changeDoctor/index.ts`** |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| 选择医生 | `BTN_DoctorChangeDoctorSelect` | 切换医生选择 | |
||||||
|
|
||||||
|
> **注意**:以上点击埋点 PageName 是基于代码探索的预定义清单。实施时需逐页确认实际的方法名和交互行为,如有差异需现场调整。 |
||||||
|
|
||||||
|
### 第 5 步:更新 doc/track.md 文档 |
||||||
|
|
||||||
|
将完整的 PageName 对照表(页面级 + 点击级)更新到 `doc/track.md`,作为埋点实施的参考文档。 |
||||||
|
|
||||||
|
## 假设与决策 |
||||||
|
|
||||||
|
1. **命名规范**:采用信达项目的 `PG_/BTN_` 前缀体系,患者端用 `Patient` 前缀,医生端用 `Doctor` 前缀。这样命名清晰且便于后端区分数据来源。 |
||||||
|
2. **detailId 参数**:扩展 `mpBehavior` 类型签名以支持可选 `detailId`,用于详情页场景。实际在 JS 层该字段会随 data 对象一起 POST,无需函数内额外处理。 |
||||||
|
3. **页面级埋点位置**:有 `waitLogin` 的页面在 `.then()` 内首行调用;无 `waitLogin` 的在 `onShow` 首行调用。确保用户登录后才上报,避免无效数据。 |
||||||
|
4. **点击级埋点位置**:在事件处理函数首行调用 `mpBehavior`,先上报再执行业务逻辑(与信达项目一致)。 |
||||||
|
5. **医生端 `doctor: true`**:所有医生端埋点均传入 `doctor: true`,确保 API 路径正确切换。 |
||||||
|
6. **MINI_PROGRAM_VISIT 保留**:`app.ts` 中现有的 `MINI_PROGRAM_VISIT` 埋点保留不变,它记录小程序整体打开事件,与页面级 PV 埋点互不冲突。 |
||||||
|
7. **无集中常量文件**:PageName 采用硬编码字符串(与信达项目一致),不创建额外的常量/枚举文件,避免增加维护成本。 |
||||||
|
|
||||||
|
## 验证步骤 |
||||||
|
|
||||||
|
1. 编译检查:确保 `pnpm run lint:fix` 无新增错误 |
||||||
|
2. 逐页验证:在微信开发者工具中打开每个页面,检查 console 中 mpBehavior POST 请求是否正常发出 |
||||||
|
3. 参数验证:确认患者端请求路径为 `?r=igg4/mp-behavior/add`,医生端为 `?r=igg4/doctor/mp-behavior/add` |
||||||
|
4. doctor 参数验证:确认 `doctor` 字段在发送前被正确删除(不出现在 POST body 中) |
||||||
|
5. detailId 验证:确认详情页的 `detailId` 正确传入 POST body |
||||||
@ -0,0 +1,572 @@ |
|||||||
|
# 医生消息任务接口文档 |
||||||
|
|
||||||
|
## 通用说明 |
||||||
|
|
||||||
|
### 响应格式 |
||||||
|
|
||||||
|
所有接口返回 JSON,严格遵循以下格式: |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"message": "success", |
||||||
|
"data": {} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
- `code`:0 表示成功,非 0 表示失败 |
||||||
|
- `message`:成功时为 "success",失败时为错误描述 |
||||||
|
- `data`:业务数据,失败时为 null |
||||||
|
|
||||||
|
### 数据命名规则 |
||||||
|
|
||||||
|
`data` 中所有字段使用**小驼峰**命名法(如 `patientNum`、`sendTime`)。 |
||||||
|
|
||||||
|
### 登录校验 |
||||||
|
|
||||||
|
所有接口需通过请求头 `loginState` 或请求参数 `loginState` 传递登录态,未登录返回 code=403。本模块所有接口需医生身份登录。 |
||||||
|
|
||||||
|
### 错误码 |
||||||
|
|
||||||
|
| code | 说明 | |
||||||
|
|------|------| |
||||||
|
| 0 | 成功 | |
||||||
|
| 1 | 参数缺失或业务异常 | |
||||||
|
| 403 | 需要登录 | |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 一、筛选患者人数 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/filter-patient-num` |
||||||
|
- **请求方式**:GET |
||||||
|
- **说明**:根据筛选条件获取符合条件的患者人数 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||||
|
|------|------|------|------| |
||||||
|
| SelectAll | int | 否 | 全选:0-否,1-是;为1时跳过其余筛选条件 | |
||||||
|
| BindStartDate | string | 否 | 绑定日期开始,格式 YYYY-MM-DD | |
||||||
|
| BindEndDate | string | 否 | 绑定日期结束,格式 YYYY-MM-DD | |
||||||
|
| ConfirmedIgg4 | int | 否 | 确诊IGG4-RD:0-全部,1-是,2-否 | |
||||||
|
| MedicationType | string | 否 | 正在使用的药物,逗号分隔选项值(如"2,3"),空或0表示全部。可选值见下面枚举表 | |
||||||
|
|
||||||
|
#### ConfirmedIgg4 枚举 |
||||||
|
|
||||||
|
| 值 | 说明 | |
||||||
|
|----|------| |
||||||
|
| 0 | 全部 | |
||||||
|
| 1 | 是(确诊患者) | |
||||||
|
| 2 | 否(非确诊患者,含未填写和疑似) | |
||||||
|
|
||||||
|
#### MedicationType 枚举 |
||||||
|
|
||||||
|
| 值 | 说明 | |
||||||
|
|----|------| |
||||||
|
| 0 | 全部 | |
||||||
|
| 2 | 激素 | |
||||||
|
| 3 | 免疫抑制剂 | |
||||||
|
| 4 | 靶向CD19生物制剂(伊奈利珠单抗) | |
||||||
|
| 5 | 靶向CD20生物制剂(利妥昔单抗等) | |
||||||
|
|
||||||
|
### 请求示例 |
||||||
|
|
||||||
|
``` |
||||||
|
?r=igg4/doctor/task/filter-patient-num&SelectAll=0&BindStartDate=2026-01-01&BindEndDate=2026-06-30&ConfirmedIgg4=1&MedicationType=2,3 |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": { |
||||||
|
"PatientNum": "1" |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 基础条件:患者状态有效、属于当前医生、有绑定用户 |
||||||
|
2. SelectAll=1 时跳过所有筛选直接返回 |
||||||
|
3. 绑定时间范围:按 `InviteTime` 字段筛选,使用 BETWEEN 逻辑 |
||||||
|
4. 确诊IGG4-RD:选"是"时匹配 `IsSuspectedPatient=2`,选"否"时排除 `IsSuspectedPatient=2`(保留未填写和疑似患者) |
||||||
|
5. 药物筛选:关联 `t_patient_health_questionnaire_latest` 表(QuestionNo=4),使用 `FIND_IN_SET` 匹配多选答案,多个选项值之间为 OR 关系 |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 二、获取任务模板 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/get-template` |
||||||
|
- **请求方式**:GET |
||||||
|
- **说明**:获取所有消息推送模板,按模板类型分组 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
无 |
||||||
|
|
||||||
|
### 响应示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": [ |
||||||
|
{ |
||||||
|
"templateType": 1, |
||||||
|
"templateTypeText": "复诊提醒", |
||||||
|
"templateList": [ |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"templateType": 1, |
||||||
|
"interactMsgContent": "互动消息内容", |
||||||
|
"templateIndex": "模板index", |
||||||
|
"templateContent": "模板内容JSON" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### TemplateType 枚举 |
||||||
|
|
||||||
|
| 值 | 说明 | |
||||||
|
|----|------| |
||||||
|
| 1 | 复诊提醒 | |
||||||
|
| 2 | 激素风险提醒 | |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 查询所有模板记录,按 `TemplateType` 升序、`Id` 升序排列 |
||||||
|
2. 按 `TemplateType` 分组返回,每组包含类型编号、类型文本和模板列表 |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 三、创建任务 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/create` |
||||||
|
- **请求方式**:POST |
||||||
|
- **说明**:创建消息推送任务,使用数据库事务 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||||
|
|------|------|------|------| |
||||||
|
| FilterCondition | object | 否 | 筛选条件JSON对象,结构同"筛选患者人数"接口参数 | |
||||||
|
| PushTemplateId | int | 是 | 推送模板ID | |
||||||
|
| IsSendNow | int | 是 | 是否立即发送:0-否,1-是 | |
||||||
|
| SendTime | string | 条件必填 | 发送时间(IsSendNow=0时必填),格式 YYYY-MM-DD HH:mm:ss | |
||||||
|
| PatientNum | int | 是 | 患者人数 | |
||||||
|
|
||||||
|
#### FilterCondition 结构 |
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 | |
||||||
|
|------|------|------| |
||||||
|
| SelectAll | int | 全选:0-否,1-是 | |
||||||
|
| BindStartDate | string | 绑定日期开始 | |
||||||
|
| BindEndDate | string | 绑定日期结束 | |
||||||
|
| ConfirmedIgg4 | int | 确诊IGG4-RD:0-全部,1-是,2-否 | |
||||||
|
| MedicationType | string | 正在使用的药物,逗号分隔 | |
||||||
|
|
||||||
|
### 请求示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"FilterCondition": { |
||||||
|
"SelectAll": 0, |
||||||
|
"BindStartDate": "2026-01-01", |
||||||
|
"BindEndDate": "2026-06-30", |
||||||
|
"ConfirmedIgg4": 1, |
||||||
|
"MedicationType": "2" |
||||||
|
}, |
||||||
|
"PushTemplateId": 2, |
||||||
|
"IsSendNow": 1, |
||||||
|
"SendTime": "", |
||||||
|
"PatientNum": 25 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(成功) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": "创建成功" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(失败) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 1, |
||||||
|
"data": "每个医生每天只能创建一次任务" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 校验医生存在(逻辑删除标记 `DeleteTime` 为空) |
||||||
|
2. 校验模板存在 |
||||||
|
3. 每个医生每天只能创建一次任务(当天已有状态为待发送/发送中/暂停/已发送/已停止的任务则不可再创建) |
||||||
|
4. IsSendNow=1 时,SendTime 自动设为当前时间 |
||||||
|
5. 自动生成 IdNumber(`YmdHis` + 3位随机数) |
||||||
|
6. IsDoctorCreate=1(医生端创建),CreateWorkerId 为空 |
||||||
|
7. 生成模板消息内容(调用 `transformTemplateContent` 转化模板变量) |
||||||
|
8. 保存任务后,自动调用 `generateUserRecord` 生成用户发送记录 |
||||||
|
9. 更新任务的 ActualPatientNum 和 IsInsertUser 字段 |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 四、获取任务列表 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/list` |
||||||
|
- **请求方式**:GET |
||||||
|
- **说明**:获取当前医生的任务列表,支持分页,按创建时间降序排列 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||||
|
|------|------|------|------| |
||||||
|
| count | int | 否 | 每页条数,默认10 | |
||||||
|
| page | int | 否 | 页码,从1开始 | |
||||||
|
|
||||||
|
### 请求示例 |
||||||
|
|
||||||
|
``` |
||||||
|
?r=igg4/doctor/task/list&count=10&page=1 |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": { |
||||||
|
"list": [ |
||||||
|
{ |
||||||
|
"Id": 1, |
||||||
|
"IdNumber": "20260702143025123", |
||||||
|
"DoctorId": 5, |
||||||
|
"FilterCondition": { |
||||||
|
"SelectAll": 0, |
||||||
|
"BindStartDate": "2026-01-01", |
||||||
|
"BindEndDate": "2026-06-30", |
||||||
|
"ConfirmedIgg4": 1, |
||||||
|
"MedicationType": "2" |
||||||
|
}, |
||||||
|
"TemplateType": 1, |
||||||
|
"CreateTime": "2026-07-02 14:30:25", |
||||||
|
"IsSendNow": 1, |
||||||
|
"SendTime": "2026-07-02 14:30:25", |
||||||
|
"Status": 4, |
||||||
|
"PatientNum": 25, |
||||||
|
"ActualPatientNum": 23 |
||||||
|
} |
||||||
|
], |
||||||
|
"pages": 3, |
||||||
|
"count": 28, |
||||||
|
"page": 1 |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应字段说明 |
||||||
|
| 字段 | 类型 | 说明 | |
||||||
|
|------|------|------| |
||||||
|
| Id | int | 任务ID | |
||||||
|
| IdNumber | string | 任务编号 | |
||||||
|
| DoctorId | int | 医生ID | |
||||||
|
| FilterCondition | object | 筛选条件JSON对象 | |
||||||
|
| TemplateType | int | 模板类型 | |
||||||
|
| CreateTime | string | 创建时间 | |
||||||
|
| IsSendNow | int | 是否立即发送:0-否,1-是 | |
||||||
|
| SendTime | string | 发送时间 | |
||||||
|
| Status | int | 任务状态 | |
||||||
|
| PatientNum | int | 患者人数 | |
||||||
|
| ActualPatientNum | int | 实际发送患者人数 | |
||||||
|
|
||||||
|
#### FilterCondition 结构说明 |
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 | |
||||||
|
|------|------|------| |
||||||
|
| SelectAll | int | 全选:0-否,1-是 | |
||||||
|
| BindStartDate | string | 绑定日期开始 | |
||||||
|
| BindEndDate | string | 绑定日期结束 | |
||||||
|
| ConfirmedIgg4 | int | 确诊IGG4-RD:0-全部,1-是,2-否 | |
||||||
|
| MedicationType | string | 正在使用的药物,逗号分隔 | |
||||||
|
|
||||||
|
#### TemplateType 枚举 |
||||||
|
|
||||||
|
| 值 | 说明 | |
||||||
|
|----|------| |
||||||
|
| 1 | 复诊提醒 | |
||||||
|
| 2 | 激素风险提醒 | |
||||||
|
|
||||||
|
#### Status 枚举 |
||||||
|
|
||||||
|
| 值 | 说明 | |
||||||
|
|----|------| |
||||||
|
| 1 | 待发送 | |
||||||
|
| 2 | 发送中 | |
||||||
|
| 3 | 暂停 | |
||||||
|
| 4 | 已发送 | |
||||||
|
| 5 | 已停止 | |
||||||
|
| 6 | 已取消 | |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 仅查询 `DeleteTime` 为空的任务 |
||||||
|
2. FilterCondition 字段从 JSON 字符串解码为数组返回 |
||||||
|
3. 返回字段包含:Id, IdNumber, DoctorId, FilterCondition, TemplateType, CreateTime, IsSendNow, SendTime, Status, PatientNum, ActualPatientNum |
||||||
|
4. 分页参数:pages 为总页数,count 为总记录数,page 为当前页码(从1开始) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 五、获取任务详情 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/detail` |
||||||
|
- **请求方式**:GET |
||||||
|
- **说明**:获取单个任务的详细信息,包含发送统计 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||||
|
|------|------|------|------| |
||||||
|
| TaskId | int | 是 | 任务ID | |
||||||
|
|
||||||
|
### 请求示例 |
||||||
|
|
||||||
|
``` |
||||||
|
?r=igg4/doctor/task/detail&TaskId=1 |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": { |
||||||
|
"Id": 1, |
||||||
|
"IdNumber": "20260702143025123", |
||||||
|
"DoctorId": 5, |
||||||
|
"FilterCondition": { |
||||||
|
"SelectAll": 0, |
||||||
|
"BindStartDate": "2026-01-01", |
||||||
|
"BindEndDate": "2026-06-30", |
||||||
|
"ConfirmedIgg4": 1, |
||||||
|
"MedicationType": "2" |
||||||
|
}, |
||||||
|
"TemplateType": 1, |
||||||
|
"PushTemplateId": 2, |
||||||
|
"TemplateMsgContent": "{...}", |
||||||
|
"InteractMsgContent": "互动消息内容", |
||||||
|
"IsSendNow": 1, |
||||||
|
"SendTime": "2026-07-02 14:30:25", |
||||||
|
"PatientNum": 25, |
||||||
|
"ActualPatientNum": 23, |
||||||
|
"IsInsertUser": 1, |
||||||
|
"Status": 4, |
||||||
|
"IsDoctorCreate": 1, |
||||||
|
"WorkerId": "", |
||||||
|
"DeleteTime": null, |
||||||
|
"CreateTime": "2026-07-02 14:30:25", |
||||||
|
"UpdateTime": "2026-07-02 14:30:30", |
||||||
|
"SendDetail": { |
||||||
|
"TaskId": 1, |
||||||
|
"SumSendCount": 23, |
||||||
|
"SendCount": 20, |
||||||
|
"UnSendCount": 3, |
||||||
|
"InteractSendSuccessCount": 18, |
||||||
|
"InteractSendFailCount": 2, |
||||||
|
"PushSendSuccessCount": 16, |
||||||
|
"PushSendFailCount": 4, |
||||||
|
"PushClickCount": 10, |
||||||
|
"InteractReadCount": 15, |
||||||
|
"InteractUnReadCount": 5, |
||||||
|
"PushUnClickCount": 10 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### SendDetail 字段说明 |
||||||
|
|
||||||
|
| 字段 | 说明 | |
||||||
|
|------|------| |
||||||
|
| sumSendCount | 总人数(去重) | |
||||||
|
| sendCount | 已发送人数 | |
||||||
|
| unSendCount | 未发送人数 | |
||||||
|
| InteractSendSuccessCount | 互动消息发送成功人数 | |
||||||
|
| InteractSendFailCount | 互动消息发送失败人数(派生值:SendCount - InteractSendSuccessCount) | |
||||||
|
| PushSendSuccessCount | Push消息发送成功人数 | |
||||||
|
| PushSendFailCount | Push消息发送失败人数 | |
||||||
|
| PushClickCount | Push消息点击人数 | |
||||||
|
| InteractReadCount | 互动消息已读人数 | |
||||||
|
| InteractUnReadCount | 互动消息未读人数(派生值:sendCount - InteractReadCount) | |
||||||
|
| PushUnClickCount | Push消息未点击人数(派生值:sendCount - PushClickCount) | |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 校验任务存在且 `DeleteTime` 为空,否则抛出"任务不存在" |
||||||
|
2. FilterCondition 从 JSON 字符串解码为数组 |
||||||
|
3. 根据 PushTemplateId 查询模板,补充 TemplateType 和 InteractMsgContent 字段 |
||||||
|
4. 通过 `TZdDoctorMsgTaskUserMessage::taskDetail` 获取发送统计数据,包含派生的失败/未读/未点击人数 |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 六、删除任务 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/delete` |
||||||
|
- **请求方式**:POST |
||||||
|
- **说明**:逻辑删除已取消的任务,使用数据库事务 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||||
|
|------|------|------|------| |
||||||
|
| TaskId | int | 是 | 任务ID | |
||||||
|
|
||||||
|
### 请求示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"TaskId": 1 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(成功) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": "删除成功" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(失败) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 1, |
||||||
|
"msg": "当前任务状态不能删除", |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 校验任务存在、属于当前医生且 `DeleteTime` 为空,否则抛出"任务不存在" |
||||||
|
2. 只有状态为"已取消"(Status=6)的任务才能删除 |
||||||
|
3. 设置 `DeleteTime` 为当前时间(软删除) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 七、停止任务 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/stop` |
||||||
|
- **请求方式**:POST |
||||||
|
- **说明**:停止正在发送中或暂停的任务,使用数据库事务 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||||
|
|------|------|------|------| |
||||||
|
| TaskId | int | 是 | 任务ID | |
||||||
|
|
||||||
|
### 请求示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"TaskId": 1 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(成功) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": "停止成功" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(失败) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 1, |
||||||
|
"msg": "当前任务状态不能停止", |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 校验任务存在、属于当前医生且 `DeleteTime` 为空,否则抛出"任务不存在" |
||||||
|
2. 只有状态为"发送中"(Status=2)或"暂停"(Status=3)的任务才能停止 |
||||||
|
3. 将状态更新为"已停止"(Status=5) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 八、取消任务 |
||||||
|
|
||||||
|
- **路由**:`?r=igg4/doctor/task/cancel` |
||||||
|
- **请求方式**:POST |
||||||
|
- **说明**:取消待发送的任务,使用数据库事务 |
||||||
|
|
||||||
|
### 请求参数 |
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||||
|
|------|------|------|------| |
||||||
|
| TaskId | int | 是 | 任务ID | |
||||||
|
|
||||||
|
### 请求示例 |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"TaskId": 1 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(成功) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 0, |
||||||
|
"data": "取消成功" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应示例(失败) |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"code": 1, |
||||||
|
"msg": "当前任务状态不能取消", |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 业务逻辑说明 |
||||||
|
|
||||||
|
1. 校验任务存在、属于当前医生且 `DeleteTime` 为空,否则抛出"任务不存在" |
||||||
|
2. 只有状态为"待发送"(Status=1)的任务才能取消 |
||||||
|
3. 将状态更新为"已取消"(Status=6) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 附录 |
||||||
|
|
||||||
|
### 附录A:任务状态流转 |
||||||
|
|
||||||
|
``` |
||||||
|
待发送(1) ──发送调度──> 发送中(2) ──发送完成──> 已发送(4) |
||||||
|
│ │ ↑ |
||||||
|
│取消 │ │暂停 |
||||||
|
↓ ↓ │ |
||||||
|
已取消(6) <────────── 暂停(3)─┘ |
||||||
|
│ │ |
||||||
|
│删除(软删) │停止 |
||||||
|
↓ ↓ |
||||||
|
(DeleteTime非空) 已停止(5) |
||||||
|
``` |
||||||
@ -0,0 +1,216 @@ |
|||||||
|
# 埋点文档 |
||||||
|
|
||||||
|
## 命名规范 |
||||||
|
|
||||||
|
| 类别 | 前缀 | 格式 | 示例 | |
||||||
|
|------|------|------|------| |
||||||
|
| 页面浏览(患者端) | `PG_` | `PG_Patient{功能名}` | `PG_PatientHome` | |
||||||
|
| 页面浏览(医生端) | `PG_` | `PG_Doctor{功能名}` | `PG_DoctorHome` | |
||||||
|
| 点击事件(患者端) | `BTN_` | `BTN_Patient{功能名}{操作}` | `BTN_PatientHomeNextVisit` | |
||||||
|
| 点击事件(医生端) | `BTN_` | `BTN_Doctor{功能名}{操作}` | `BTN_DoctorHomeInvite` | |
||||||
|
|
||||||
|
## 埋点示例 |
||||||
|
|
||||||
|
### 患者端页面访问埋点 |
||||||
|
|
||||||
|
```ts |
||||||
|
// 页面级 PV |
||||||
|
app.waitLogin().then(() => { |
||||||
|
app.mpBehavior({ PageName: 'PG_PatientHome' }) |
||||||
|
}) |
||||||
|
|
||||||
|
// 详情页带 detailId |
||||||
|
app.waitLogin().then(() => { |
||||||
|
app.mpBehavior({ PageName: 'PG_PatientDoctorDetail', detailId: options.id }) |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
### 医生端页面访问埋点 |
||||||
|
|
||||||
|
```ts |
||||||
|
app.waitLogin().then(() => { |
||||||
|
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorHome' }) |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
### 患者端点击埋点 |
||||||
|
|
||||||
|
```ts |
||||||
|
handleNextVisit() { |
||||||
|
app.mpBehavior({ PageName: 'BTN_PatientHomeNextVisit' }) |
||||||
|
// ... 业务逻辑 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 医生端点击埋点 |
||||||
|
|
||||||
|
```ts |
||||||
|
handleInvite() { |
||||||
|
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorHomeInvite' }) |
||||||
|
// ... 业务逻辑 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 全局访问埋点 |
||||||
|
|
||||||
|
```ts |
||||||
|
// app.ts onShow 中,记录小程序整体打开事件 |
||||||
|
app.mpBehavior({ PageName: 'MINI_PROGRAM_VISIT' }) |
||||||
|
``` |
||||||
|
|
||||||
|
## 页面级埋点(PG_) |
||||||
|
|
||||||
|
### 患者端 |
||||||
|
|
||||||
|
| 页面路径 | 页面名称 | PageName | detailId | |
||||||
|
|---------|---------|----------|----------| |
||||||
|
| pages/index/index | 首页 | `PG_PatientHome` | - | |
||||||
|
| gift/pages/recordList/index | 就诊记录列表页 | `PG_PatientRecordList` | - | |
||||||
|
| gift/pages/record/index | 就诊记录新增页 | `PG_PatientRecordAdd` | - | |
||||||
|
| gift/pages/recordDetail/index | 就诊记录详情页 | `PG_PatientRecordDetail` | `options.id` | |
||||||
|
| pages/interactivePatient/index | 医患互动页 | `PG_PatientInteractive` | - | |
||||||
|
| pages/doctorDetail/index | 医生详情页 | `PG_PatientDoctorDetail` | `options.id` | |
||||||
|
| pages/repository/index | 知识库 | `PG_PatientRepository` | - | |
||||||
|
| pages/repositoryDetail/index | 知识库详情页 | `PG_PatientRepositoryDetail` | `id` | |
||||||
|
| pages/my/index | 我的 | `PG_PatientMy` | - | |
||||||
|
| pages/qaFormDetail/index | 健康档案页 | `PG_PatientQaFormDetail` | - | |
||||||
|
| pages/qaForm/index | 健康档案编辑页 | `PG_PatientQaForm` | - | |
||||||
|
| pages/caseReport/index | 病例报告页 | `PG_PatientCaseReport` | - | |
||||||
|
| pages/personalInformation/index | 个人信息页 | `PG_PatientPersonalInfo` | - | |
||||||
|
| pages/cancellation/index | 注销账号页 | `PG_PatientCancellation` | - | |
||||||
|
| pages/mySave/index | 我的收藏页 | `PG_PatientMySave` | - | |
||||||
|
| pages/feedback/index | 问题反馈页 | `PG_PatientFeedback` | - | |
||||||
|
|
||||||
|
### 医生端 |
||||||
|
|
||||||
|
| 页面路径 | 页面名称 | PageName | detailId | |
||||||
|
|---------|---------|----------|----------| |
||||||
|
| doctor/pages/d_home/index | 首页 | `PG_DoctorHome` | - | |
||||||
|
| doctor/pages/d_invite/index | 邀约患者页 | `PG_DoctorInvite` | - | |
||||||
|
| doctor/pages/d_patient/index | 患者列表页 | `PG_DoctorPatient` | - | |
||||||
|
| doctor/pages/d_patientDetail/index | 患者详情页 | `PG_DoctorPatientDetail` | `this.data.id` | |
||||||
|
| doctor/pages/d_taskList/index | 群发列表页 | `PG_DoctorTaskList` | - | |
||||||
|
| doctor/pages/d_taskDetail/index | 群发详情页 | `PG_DoctorTaskDetail` | `this.data.id` | |
||||||
|
| doctor/pages/d_createTask/index | 群发新增页 | `PG_DoctorCreateTask` | - | |
||||||
|
| doctor/pages/d_interactive/index | 互动列表页 | `PG_DoctorInteractive` | - | |
||||||
|
| doctor/pages/d_interactiveDoctor/index | 互动页 | `PG_DoctorInteractiveDoctor` | `this.data.patientId` | |
||||||
|
| doctor/pages/d_my/index | 我的 | `PG_DoctorMy` | - | |
||||||
|
| doctor/pages/d_userInfo/index | 个人信息页 | `PG_DoctorUserInfo` | - | |
||||||
|
| doctor/pages/d_changeDoctor/index | 切换医生页 | `PG_DoctorChangeDoctor` | - | |
||||||
|
|
||||||
|
## 点击级埋点(BTN_) |
||||||
|
|
||||||
|
### 患者端 |
||||||
|
|
||||||
|
#### 首页(pages/index/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `routerTo(e)` | `BTN_PatientHomeFunctionCard` | 功能卡片跳转 | |
||||||
|
| `handleNextVisit()` | `BTN_PatientHomeNextVisit` | 下次复诊入口 | |
||||||
|
| `handleArticleView(e)` | `BTN_PatientHomeArticle` | 文章点击 | |
||||||
|
| `handleArticleSave(e)` | `BTN_PatientHomeArticleSave` | 文章收藏 | |
||||||
|
| `handleToastOk()` | `BTN_PatientHomeBindDoctor` | 绑定医生弹窗确认 | |
||||||
|
| `handlePublic()` | `BTN_PatientHomeFollowPublic` | 关注公众号 | |
||||||
|
|
||||||
|
#### 我的(pages/my/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `routerVipTo()` | `BTN_PatientMyVip` | VIP 认证入口 | |
||||||
|
| `handleMyHealthRecord(e)` | `BTN_PatientMyHealthRecord` | 健康档案 | |
||||||
|
| `handleQaForm()` | `BTN_PatientMyQaForm` | 健康问卷 | |
||||||
|
| `routerTo(e)` → mySave | `BTN_PatientMySave` | 我的收藏 | |
||||||
|
| `routerTo(e)` → personalInfo | `BTN_PatientMyPersonalInfo` | 个人信息 | |
||||||
|
| `handleMiniDoctor()` | `BTN_PatientMyMiniDoctor` | 迷你医生 | |
||||||
|
| `handleFeedback()` | `BTN_PatientMyFeedback` | 问题反馈 | |
||||||
|
|
||||||
|
#### 医患互动(pages/interactivePatient/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleHot(e)` | `BTN_PatientInteractiveHotQuestion` | 热门问题点击 | |
||||||
|
| `handleQuestion(e)` | `BTN_PatientInteractiveQuestion` | 快捷问题点击 | |
||||||
|
| `handleConfirm()` | `BTN_PatientInteractiveSend` | 发送消息 | |
||||||
|
| `handleAdl()` | `BTN_PatientInteractiveAdl` | ADL 评估入口 | |
||||||
|
| `handleQol()` | `BTN_PatientInteractiveQol` | QoL 入口 | |
||||||
|
| `handleDoctorDetail()` | `BTN_PatientInteractiveDoctorDetail` | 查看医生详情 | |
||||||
|
|
||||||
|
#### 知识库(pages/repository/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `tabChange(e)` | `BTN_PatientRepositoryTab` | Tab 切换 | |
||||||
|
| `handleSearch()` | `BTN_PatientRepositorySearch` | 搜索 | |
||||||
|
| `handleCate(e)` | `BTN_PatientRepositoryCategory` | 分类筛选 | |
||||||
|
| `handleDetail(e)` | `BTN_PatientRepositoryArticle` | 文章详情 | |
||||||
|
| `handleStar(e)` | `BTN_PatientRepositoryStar` | 点赞 | |
||||||
|
| `handleToggleSave(e)` | `BTN_PatientRepositorySave` | 收藏 | |
||||||
|
|
||||||
|
#### 就诊记录列表(gift/pages/recordList/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleAdd()` | `BTN_PatientRecordListAdd` | 新增记录 | |
||||||
|
| `handleDetail(e)` | `BTN_PatientRecordListItem` | 记录列表项 | |
||||||
|
|
||||||
|
#### 就诊记录新增(gift/pages/record/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleSubmit()` | `BTN_PatientRecordAddSubmit` | 保存记录 | |
||||||
|
|
||||||
|
### 医生端 |
||||||
|
|
||||||
|
#### 医生首页(doctor/pages/d_home/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handlePatient(e)` | `BTN_DoctorHomePatient` | 患者列表入口 | |
||||||
|
| `handleInvite()` | `BTN_DoctorHomeInvite` | 邀约按钮 | |
||||||
|
| `handleSelectYear1()` | `BTN_DoctorHomeSelectYear` | 年份筛选 | |
||||||
|
|
||||||
|
#### 邀约患者(doctor/pages/d_invite/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleDownload()` | `BTN_DoctorInviteSend` | 发送邀约(海报保存/分享) | |
||||||
|
|
||||||
|
#### 患者列表(doctor/pages/d_patient/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleSearch()` | `BTN_DoctorPatientSearch` | 搜索患者 | |
||||||
|
| `handleDetail(e)` | `BTN_DoctorPatientItem` | 患者列表项 | |
||||||
|
|
||||||
|
#### 互动列表(doctor/pages/d_interactive/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleSearch()` | `BTN_DoctorInteractiveSearch` | 搜索互动 | |
||||||
|
| `handleDetail(e)` | `BTN_DoctorInteractiveItem` | 互动列表项 | |
||||||
|
|
||||||
|
#### 互动页(doctor/pages/d_interactiveDoctor/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleConfirm()` | `BTN_DoctorInteractiveSend` | 发送消息 | |
||||||
|
|
||||||
|
#### 群发列表(doctor/pages/d_taskList/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleCreate()` | `BTN_DoctorTaskListAdd` | 新增群发 | |
||||||
|
| `handleDetail(e)` | `BTN_DoctorTaskListItem` | 群发列表项 | |
||||||
|
|
||||||
|
#### 群发新增(doctor/pages/d_createTask/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleSubmit()` | `BTN_DoctorCreateTaskSubmit` | 创建群发 | |
||||||
|
|
||||||
|
#### 切换医生(doctor/pages/d_changeDoctor/index) |
||||||
|
|
||||||
|
| 方法 | PageName | 说明 | |
||||||
|
|------|----------|------| |
||||||
|
| `handleSubmit()` | `BTN_DoctorChangeDoctorSelect` | 切换医生选择 | |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "病例报告", |
||||||
|
"usingComponents": { |
||||||
|
"pagination": "/components/pagination/index", |
||||||
|
"van-divider": "@vant/weapp/divider/index", |
||||||
|
"van-loading": "@vant/weapp/loading/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(246, 246, 246, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.page { |
||||||
|
padding: 32rpx 32rpx 300rpx; |
||||||
|
.list { |
||||||
|
.card { |
||||||
|
display: flex; |
||||||
|
gap: 20rpx; |
||||||
|
.aside { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
flex-shrink: 0; |
||||||
|
.line-top, |
||||||
|
.line-bottom { |
||||||
|
border-right: 1px dashed rgba(34, 34, 34, 0.2); |
||||||
|
} |
||||||
|
.line-top { |
||||||
|
flex-shrink: 0; |
||||||
|
} |
||||||
|
.line-bottom { |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
.circle { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 20rpx; |
||||||
|
height: 20rpx; |
||||||
|
border-radius: 50%; |
||||||
|
background-color: rgba(22, 121, 203, 1); |
||||||
|
background-clip: content-box; |
||||||
|
border: 6rpx solid rgba(22, 121, 203, 0.10); |
||||||
|
} |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
flex: 1; |
||||||
|
min-width: 0; |
||||||
|
padding-bottom: 48rpx; |
||||||
|
.date { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
line-height: 1; |
||||||
|
} |
||||||
|
.container { |
||||||
|
margin-top: 20rpx; |
||||||
|
padding: 32rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 12rpx; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(2, 1fr); |
||||||
|
gap: 24rpx; |
||||||
|
.photo { |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 154rpx; |
||||||
|
border-radius: 12rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.empty { |
||||||
|
padding: 200rpx 0; |
||||||
|
text-align: center; |
||||||
|
.text { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(34, 34, 34, 0.4); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,58 @@ |
|||||||
|
Page({ |
||||||
|
data: { |
||||||
|
imageField: '', |
||||||
|
patientId: '', |
||||||
|
list: [] as { visitDate: string; images: string[] }[], |
||||||
|
pagination: { count: 0, page: 1, pages: 1 }, |
||||||
|
}, |
||||||
|
|
||||||
|
onLoad(options: any) { |
||||||
|
if (options.name) { |
||||||
|
wx.setNavigationBarTitle({ title: options.name }) |
||||||
|
} |
||||||
|
if (options.imageField) { |
||||||
|
this.setData({ imageField: options.imageField, patientId: options.patientId || '' }) |
||||||
|
this.getImageHistory(true) |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
onReachBottom() { |
||||||
|
const { page, pages } = this.data.pagination |
||||||
|
if (page < pages) { |
||||||
|
this.setData({ 'pagination.page': page + 1 }) |
||||||
|
this.getImageHistory() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
getImageHistory(reset = false) { |
||||||
|
const page = reset ? 1 : this.data.pagination.page |
||||||
|
const data: Record<string, any> = { imageField: this.data.imageField, page, pageSize: 10 } |
||||||
|
data.patientId = this.data.patientId |
||||||
|
wx.ajax({ |
||||||
|
method: 'GET', |
||||||
|
url: '?r=igg4/doctor/medical-visit/image-history', |
||||||
|
data, |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res || !res.list) return |
||||||
|
const newList = res.list |
||||||
|
this.setData({ |
||||||
|
list: reset ? newList : [...this.data.list, ...newList], |
||||||
|
pagination: { |
||||||
|
count: res.totalCount || 0, |
||||||
|
page: res.page || 1, |
||||||
|
pages: res.pages || 1, |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
handlePreviewImage(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const { url, urls } = e.currentTarget.dataset |
||||||
|
wx.previewImage({ |
||||||
|
current: url, |
||||||
|
urls, |
||||||
|
}) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
<view class="page" id="{{theme === 'DRUG' && 'drug-page'}}"> |
||||||
|
<view class="list"> |
||||||
|
<view class="card" wx:for="{{list}}" wx:key="visitDate"> |
||||||
|
<view class="aside"> |
||||||
|
<view class="line-top"></view> |
||||||
|
<view class="circle"></view> |
||||||
|
<view class="line-bottom"></view> |
||||||
|
</view> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="date">{{item.visitDate}}</view> |
||||||
|
<view class="container"> |
||||||
|
<image |
||||||
|
class="photo" |
||||||
|
wx:for="{{item.imageUrls}}" |
||||||
|
wx:key="*this" |
||||||
|
wx:for-item="imgUrl" |
||||||
|
src="{{imgUrl}}" |
||||||
|
mode="aspectFill" |
||||||
|
bind:tap="handlePreviewImage" |
||||||
|
data-url="{{imgUrl}}" |
||||||
|
data-urls="{{item.imageUrls}}" |
||||||
|
data-date="{{item.visitDate}}" |
||||||
|
></image> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<pagination pagination="{{pagination}}" customEmpty emptyTitle="暂无记录"> |
||||||
|
<view class="empty-text" slot>暂无记录</view> |
||||||
|
</pagination> |
||||||
|
</view> |
||||||
@ -1,6 +1,9 @@ |
|||||||
{ |
{ |
||||||
"navigationStyle": "custom", |
"navigationBarTitleText": "任务列表", |
||||||
"usingComponents": { |
"usingComponents": { |
||||||
|
"pagination": "/components/pagination/index", |
||||||
|
"van-divider": "@vant/weapp/divider/index", |
||||||
|
"van-loading": "@vant/weapp/loading/index", |
||||||
"navBar": "/components/navBar/navBar" |
"navBar": "/components/navBar/navBar" |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "就诊记录", |
||||||
|
"navigationBarBackgroundColor": "#F6F6F6", |
||||||
|
"usingComponents": { |
||||||
|
"uploadFile": "/components/uploadFile/index", |
||||||
|
"van-icon": "@vant/weapp/icon/index", |
||||||
|
"toast": "/components/toast/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,607 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(246, 246, 246, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.page { |
||||||
|
.page-header { |
||||||
|
padding: 50rpx 64rpx 0; |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
.item { |
||||||
|
flex-shrink: 0; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
.order { |
||||||
|
width: 52rpx; |
||||||
|
height: 52rpx; |
||||||
|
text-align: center; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 0.3); |
||||||
|
line-height: 52rpx; |
||||||
|
border-radius: 50%; |
||||||
|
background-color: rgba(236, 236, 236, 1); |
||||||
|
border: 8rpx solid rgba(255, 255, 255, 0); |
||||||
|
background-clip: content-box; |
||||||
|
.icon { |
||||||
|
width: 28rpx; |
||||||
|
height: 28rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.order-end { |
||||||
|
width: 52rpx; |
||||||
|
height: 52rpx; |
||||||
|
} |
||||||
|
.name { |
||||||
|
margin-top: 4rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(34, 34, 34, 0.4); |
||||||
|
&.current { |
||||||
|
color: rgba(14, 182, 109, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
&.active { |
||||||
|
.order { |
||||||
|
background-color: rgba(14, 182, 109, 1); |
||||||
|
border: 8rpx solid #e5f2ec; |
||||||
|
color: #fff; |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.line { |
||||||
|
margin: 30rpx -10rpx 0; |
||||||
|
flex: 1; |
||||||
|
border-top: 1px dashed rgba(34, 34, 34, 0.1); |
||||||
|
&.active { |
||||||
|
border-color: rgba(14, 182, 109, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.step1 { |
||||||
|
margin: 32rpx; |
||||||
|
background-color: rgba(255, 255, 255, 0.9); |
||||||
|
border-radius: 24rpx; |
||||||
|
.s-header { |
||||||
|
padding: 20rpx 0; |
||||||
|
display: flex; |
||||||
|
align-items: start; |
||||||
|
justify-content: center; |
||||||
|
.title { |
||||||
|
margin-top: 48rpx; |
||||||
|
width: 410rpx; |
||||||
|
height: 122rpx; |
||||||
|
} |
||||||
|
.badge { |
||||||
|
margin-left: -24rpx; |
||||||
|
width: 224rpx; |
||||||
|
height: 188rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.s-form { |
||||||
|
padding: 24rpx 40rpx 40rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 16rpx; |
||||||
|
box-shadow: 0rpx 4rpx 40rpx 0rpx rgba(0, 0, 0, 0.08); |
||||||
|
.form-item { |
||||||
|
margin-top: 32rpx; |
||||||
|
padding-bottom: 32rpx; |
||||||
|
border-bottom: 1px dashed rgba(34, 34, 34, 0.1); |
||||||
|
&:last-of-type { |
||||||
|
border: none; |
||||||
|
} |
||||||
|
.label { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 12rpx; |
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
background: #0eb66d; |
||||||
|
border-radius: 44rpx 44rpx 44rpx 44rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.date { |
||||||
|
margin-top: 24rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
padding: 0 32rpx; |
||||||
|
height: 96rpx; |
||||||
|
background: #f6f6f6; |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
gap: 24rpx; |
||||||
|
.icon1 { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 44rpx; |
||||||
|
height: 44rpx; |
||||||
|
} |
||||||
|
.icon2 { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 40rpx; |
||||||
|
height: 40rpx; |
||||||
|
} |
||||||
|
.content { |
||||||
|
flex: 1; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 0.4); |
||||||
|
|
||||||
|
&.active { |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.step1-btn { |
||||||
|
margin: 82rpx 40rpx 0; |
||||||
|
height: 96rpx; |
||||||
|
font-size: 40rpx; |
||||||
|
color: rgba(255, 255, 255, 1); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
background: linear-gradient(86deg, #0eb66d 0%, #00d277 100%); |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
} |
||||||
|
|
||||||
|
.step2 { |
||||||
|
.tip-card { |
||||||
|
margin: 32rpx; |
||||||
|
padding: 16rpx 32rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(248, 167, 28, 1); |
||||||
|
line-height: 48rpx; |
||||||
|
background-color: rgba(255, 248, 236, 1); |
||||||
|
border-radius: 24rpx; |
||||||
|
border: 1px solid #fff; |
||||||
|
} |
||||||
|
.form-card { |
||||||
|
margin: 32rpx; |
||||||
|
padding: 32rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
.f-header { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.label { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
line-height: 48rpx; |
||||||
|
&::before { |
||||||
|
margin-right: 24rpx; |
||||||
|
margin-left: -32rpx; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
background: #0eb66d; |
||||||
|
content: ''; |
||||||
|
border-radius: 0 8rpx 8rpx 0; |
||||||
|
} |
||||||
|
&.required::after { |
||||||
|
display: inline; |
||||||
|
content: '*'; |
||||||
|
color: rgba(211, 62, 48, 1); |
||||||
|
bottom: none; |
||||||
|
padding: 0; |
||||||
|
background: transparent; |
||||||
|
} |
||||||
|
&::after { |
||||||
|
padding: 0 16rpx; |
||||||
|
line-height: 40rpx; |
||||||
|
content: '选填'; |
||||||
|
background-color: rgba(34, 34, 34, 0.06); |
||||||
|
border-radius: 8rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: normal; |
||||||
|
} |
||||||
|
} |
||||||
|
.example { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(14, 182, 109, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 16rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(34, 34, 34, 0.5); |
||||||
|
} |
||||||
|
.upload-wrap { |
||||||
|
margin-top: 32rpx; |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
gap: 20rpx; |
||||||
|
} |
||||||
|
.upload-container { |
||||||
|
flex-shrink: 0; |
||||||
|
padding: 0; |
||||||
|
width: 300rpx; |
||||||
|
height: 168rpx; |
||||||
|
box-sizing: border-box; |
||||||
|
background: #f3fffa; |
||||||
|
border-radius: 16rpx; |
||||||
|
border: 2rpx dashed #0eb66d; |
||||||
|
overflow: hidden; |
||||||
|
.icon { |
||||||
|
display: block; |
||||||
|
margin: 34rpx auto 0; |
||||||
|
width: 48rpx; |
||||||
|
height: 48rpx; |
||||||
|
} |
||||||
|
.name { |
||||||
|
margin-top: 18rpx; |
||||||
|
text-align: center; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(14, 182, 109, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.upload-preview { |
||||||
|
position: relative; |
||||||
|
flex-shrink: 0; |
||||||
|
width: 300rpx; |
||||||
|
height: 168rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
overflow: hidden; |
||||||
|
.upload-img { |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
border-radius: 16rpx; |
||||||
|
} |
||||||
|
.del { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
right: 0; |
||||||
|
color: #fff; |
||||||
|
font-size: 24rpx; |
||||||
|
padding: 4rpx 12rpx; |
||||||
|
background-color: rgba(0, 0, 0, 0.8); |
||||||
|
border-radius: 0 16rpx 0 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.step2-footer { |
||||||
|
position: sticky; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
padding: 32rpx 32rpx calc(env(safe-area-inset-bottom)); |
||||||
|
background-color: #fff; |
||||||
|
box-sizing: border-box; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 26rpx; |
||||||
|
.prev { |
||||||
|
flex: 1; |
||||||
|
height: 96rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(14, 182, 109, 1); |
||||||
|
background: #ffffff; |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
border: 2rpx solid #0eb66d; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
.next { |
||||||
|
flex: 1; |
||||||
|
height: 96rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
font-size: 36rpx; |
||||||
|
color: #fff; |
||||||
|
background: linear-gradient(86deg, #0eb66d 0%, #00d277 100%); |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.step3 { |
||||||
|
.tip-card { |
||||||
|
margin: 32rpx; |
||||||
|
padding: 16rpx 32rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(248, 167, 28, 1); |
||||||
|
line-height: 48rpx; |
||||||
|
background-color: rgba(255, 248, 236, 1); |
||||||
|
border-radius: 24rpx; |
||||||
|
border: 1px solid #fff; |
||||||
|
} |
||||||
|
.form-card { |
||||||
|
margin: 32rpx; |
||||||
|
padding: 32rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
.f-header { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.label { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
line-height: 48rpx; |
||||||
|
display: flex; |
||||||
|
&::before { |
||||||
|
display: inline-block; |
||||||
|
margin-right: 24rpx; |
||||||
|
margin-left: -32rpx; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
background: #0eb66d; |
||||||
|
content: ''; |
||||||
|
border-radius: 0 8rpx 8rpx 0; |
||||||
|
} |
||||||
|
&.doboule::before { |
||||||
|
height: 84rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.num { |
||||||
|
margin-top: 24rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 20rpx; |
||||||
|
.wrap { |
||||||
|
position: relative; |
||||||
|
padding: 0 32rpx; |
||||||
|
background: #f6f6f6; |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
.input { |
||||||
|
flex: 1; |
||||||
|
height: 96rpx; |
||||||
|
line-height: 96rpx; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(0, 0, 0, 1); |
||||||
|
} |
||||||
|
.place-input { |
||||||
|
color: rgba(0, 0, 0, 0.4); |
||||||
|
} |
||||||
|
.icon { |
||||||
|
position: absolute; |
||||||
|
right: 32rpx; |
||||||
|
width: 44rpx; |
||||||
|
height: 44rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.sub { |
||||||
|
flex-shrink: 0; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 0.3); |
||||||
|
} |
||||||
|
} |
||||||
|
.check-group { |
||||||
|
margin-top: 32rpx; |
||||||
|
display: grid; |
||||||
|
gap: 24rpx; |
||||||
|
&.col1 { |
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300rpx, 1fr)); |
||||||
|
} |
||||||
|
&.col2 { |
||||||
|
grid-template-columns: repeat(2, 1fr); |
||||||
|
} |
||||||
|
.check-item { |
||||||
|
display: flex; |
||||||
|
gap: 24rpx; |
||||||
|
padding: 32rpx 32rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background-color: rgba(248, 248, 248, 1); |
||||||
|
border: 1px solid rgba(248, 248, 248, 1); |
||||||
|
box-sizing: border-box; |
||||||
|
.check { |
||||||
|
flex-shrink: 0; |
||||||
|
margin-top: 6rpx; |
||||||
|
width: 40rpx; |
||||||
|
height: 40rpx; |
||||||
|
border-radius: 8rpx; |
||||||
|
border: 1px solid rgba(34, 34, 34, 0.2); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
.icon { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 0.7); |
||||||
|
} |
||||||
|
&.active { |
||||||
|
border-color: rgba(14, 182, 109, 1); |
||||||
|
background-color: rgba(14, 182, 109, 0.08); |
||||||
|
.check { |
||||||
|
border-color: rgba(14, 182, 109, 1); |
||||||
|
background-color: rgba(14, 182, 109, 1); |
||||||
|
.icon { |
||||||
|
color: #fff; |
||||||
|
display: block; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.num-select { |
||||||
|
margin-top: 32rpx; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(3, 1fr); |
||||||
|
gap: 24rpx 22rpx; |
||||||
|
.n-item { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
gap: 24rpx; |
||||||
|
height: 96rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background-color: rgba(248, 248, 248, 1); |
||||||
|
border: 1px solid rgba(248, 248, 248, 1); |
||||||
|
box-sizing: border-box; |
||||||
|
.content { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 0.7); |
||||||
|
} |
||||||
|
&.active { |
||||||
|
border-color: rgba(14, 182, 109, 1); |
||||||
|
background-color: rgba(14, 182, 109, 0.08); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.custom-num { |
||||||
|
margin-top: 24rpx; |
||||||
|
padding: 0 32rpx; |
||||||
|
background: #f6f6f6; |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
.input { |
||||||
|
flex: 1; |
||||||
|
height: 96rpx; |
||||||
|
line-height: 96rpx; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(0, 0, 0, 1); |
||||||
|
} |
||||||
|
.place-input { |
||||||
|
color: rgba(0, 0, 0, 0.4); |
||||||
|
} |
||||||
|
.sub { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(0, 0, 0, 0.8); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.step3-footer { |
||||||
|
position: sticky; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
padding: 32rpx 32rpx calc(env(safe-area-inset-bottom)); |
||||||
|
background-color: #fff; |
||||||
|
box-sizing: border-box; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 26rpx; |
||||||
|
.prev { |
||||||
|
flex: 1; |
||||||
|
height: 96rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(14, 182, 109, 1); |
||||||
|
background: #ffffff; |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
border: 2rpx solid #0eb66d; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
.submit { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 406rpx; |
||||||
|
height: 96rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
font-size: 36rpx; |
||||||
|
color: #fff; |
||||||
|
background: linear-gradient(86deg, #0eb66d 0%, #00d277 100%); |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#drug-page { |
||||||
|
.page-header { |
||||||
|
.name.current { |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
.item.active { |
||||||
|
.order { |
||||||
|
background-image: linear-gradient(274deg, #5956e9 0%, #b384f4 100%); |
||||||
|
border-color: rgba(89, 86, 233, 0.08); |
||||||
|
} |
||||||
|
} |
||||||
|
.line.active { |
||||||
|
border-color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.step1 { |
||||||
|
.label::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.step1-btn { |
||||||
|
background: linear-gradient(274deg, #5956e9 0%, #b384f4 100%); |
||||||
|
} |
||||||
|
.step2 { |
||||||
|
.form-card { |
||||||
|
.label::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
.example { |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
.upload-container { |
||||||
|
border-color: rgba(89, 86, 233, 1); |
||||||
|
background-color: rgba(249, 249, 255, 1); |
||||||
|
.name { |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.step2-footer { |
||||||
|
.prev { |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
border-color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
.next { |
||||||
|
background: linear-gradient(274deg, #5956e9 0%, #b384f4 100%); |
||||||
|
} |
||||||
|
} |
||||||
|
.step3 { |
||||||
|
.f-header { |
||||||
|
.label::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.check-group { |
||||||
|
.check-item.active { |
||||||
|
border-color: rgba(89, 86, 233, 1); |
||||||
|
background-color: rgba(89, 86, 233, 0.08); |
||||||
|
.check { |
||||||
|
border-color: rgba(89, 86, 233, 1); |
||||||
|
background-color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.num-select { |
||||||
|
.n-item.active { |
||||||
|
border-color: rgba(89, 86, 233, 1); |
||||||
|
background-color: rgba(89, 86, 233, 0.08); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.step3-footer { |
||||||
|
.prev { |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
border-color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
.submit { |
||||||
|
background: linear-gradient(274deg, #5956e9 0%, #b384f4 100%); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,466 @@ |
|||||||
|
import { debounce } from 'miniprogram-licia' |
||||||
|
const _app = getApp<IAppOption>() |
||||||
|
|
||||||
|
const MEDICATIONS_OPTIONS = [ |
||||||
|
{ id: 1, label: '没有使用激素', active: false }, |
||||||
|
{ id: 2, label: '激素', active: false }, |
||||||
|
{ id: 3, label: '免疫抑制剂', active: false }, |
||||||
|
{ id: 4, label: '靶向CD19生物制剂(伊奈利珠单抗)', active: false }, |
||||||
|
{ id: 5, label: '靶向CD20生物制剂(利妥昔单抗等)', active: false }, |
||||||
|
{ id: 99, label: '其他', active: false }, |
||||||
|
] |
||||||
|
|
||||||
|
const IMMUNOSUPPRESSANT_OPTIONS = [ |
||||||
|
{ id: 1, label: '吗替麦考酚酯(骁悉、麦考芬)', active: false }, |
||||||
|
{ id: 2, label: '替唑嘌呤', active: false }, |
||||||
|
{ id: 3, label: '环磷酰胺', active: false }, |
||||||
|
{ id: 4, label: '来氟米特', active: false }, |
||||||
|
{ id: 5, label: '甲氨蝶呤', active: false }, |
||||||
|
{ id: 6, label: '环孢素 A', active: false }, |
||||||
|
{ id: 7, label: '他克莫司', active: false }, |
||||||
|
{ id: 8, label: '艾拉莫德', active: false }, |
||||||
|
{ id: 99, label: '其他', active: false }, |
||||||
|
] |
||||||
|
|
||||||
|
const HORMONE_DOSAGE_OPTIONS = [1, 2, 4, 6, 8, 0.5] |
||||||
|
|
||||||
|
const IMAGE_TYPE_MAP: Record<string, string> = { |
||||||
|
outpatientRecord: 'outpatientRecord', |
||||||
|
labReport: 'labReport', |
||||||
|
imagingExam: 'imagingExam', |
||||||
|
pathology: 'pathology', |
||||||
|
immuneFunction: 'immuneFunction', |
||||||
|
} |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
step: 1, |
||||||
|
draftId: 0, |
||||||
|
recordStatus: 0, |
||||||
|
|
||||||
|
// step1
|
||||||
|
visitDate: '', |
||||||
|
nextVisitDate: '', |
||||||
|
|
||||||
|
// step2 - 每种图片类型的文件列表
|
||||||
|
outpatientRecordFiles: [] as any[], |
||||||
|
labReportFiles: [] as any[], |
||||||
|
imagingExamFiles: [] as any[], |
||||||
|
pathologyFiles: [] as any[], |
||||||
|
immuneFunctionFiles: [] as any[], |
||||||
|
|
||||||
|
// step3
|
||||||
|
igG4Value: '', |
||||||
|
medicationsUsed: [] as number[], |
||||||
|
immunosuppressantName: [] as number[], |
||||||
|
oralHormoneDosage: 0, |
||||||
|
customDosage: '', |
||||||
|
medicationsOptions: MEDICATIONS_OPTIONS, |
||||||
|
immunosuppressantOptions: IMMUNOSUPPRESSANT_OPTIONS, |
||||||
|
hormoneDosageOptions: HORMONE_DOSAGE_OPTIONS, |
||||||
|
|
||||||
|
// 其他输入
|
||||||
|
medicationsUsedOtherText: '', |
||||||
|
immunosuppressantNameOtherText: '', |
||||||
|
medicationsOtherShow: false, |
||||||
|
immunosuppressantOtherShow: false, |
||||||
|
|
||||||
|
// UI
|
||||||
|
toastShow: false, |
||||||
|
toastType: '', |
||||||
|
toastParams: {} as any, |
||||||
|
saving: false, |
||||||
|
submitted: false, |
||||||
|
}, |
||||||
|
|
||||||
|
_saveDraftDebounced: null as any, |
||||||
|
|
||||||
|
onLoad(options: any) { |
||||||
|
_app.waitLogin().then(() => { |
||||||
|
_app.mpBehavior({ PageName: 'PG_PatientRecordAdd' }) |
||||||
|
}) |
||||||
|
if (options.id) { |
||||||
|
this.setData({ draftId: Number(options.id) }) |
||||||
|
_app.waitLogin().then(() => { |
||||||
|
this.getDetail() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
this._saveDraftDebounced = debounce(() => { |
||||||
|
this.saveDraft() |
||||||
|
}, 800) |
||||||
|
}, |
||||||
|
|
||||||
|
onUnload() { |
||||||
|
wx.disableAlertBeforeUnload() |
||||||
|
}, |
||||||
|
|
||||||
|
_updateAlertBeforeUnload() { |
||||||
|
if (this.data.submitted || !this._hasFilledData()) { |
||||||
|
wx.disableAlertBeforeUnload() |
||||||
|
} else { |
||||||
|
wx.enableAlertBeforeUnload({ message: '您有填写信息还未提交' }) |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
_hasFilledData() { |
||||||
|
if (this.data.visitDate || this.data.nextVisitDate) return true |
||||||
|
const hasImage = Object.keys(IMAGE_TYPE_MAP).some((key) => (this.data[`${key}Files` as any] as any[]).length > 0) |
||||||
|
if (hasImage) return true |
||||||
|
if (this.data.igG4Value || this.data.oralHormoneDosage || this.data.customDosage) return true |
||||||
|
if (this.data.medicationsUsed.length || this.data.immunosuppressantName.length) return true |
||||||
|
return false |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== API ==========
|
||||||
|
|
||||||
|
getDetail() { |
||||||
|
wx.ajax({ |
||||||
|
method: 'GET', |
||||||
|
url: '?r=igg4/medical-visit/detail', |
||||||
|
data: { id: this.data.draftId }, |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) return |
||||||
|
const images = res.images || {} |
||||||
|
const setData: Record<string, any> = { |
||||||
|
visitDate: res.visitDate || '', |
||||||
|
nextVisitDate: res.nextVisitDate || '', |
||||||
|
igG4Value: res.manualIgG4Value != null ? String(res.manualIgG4Value) : '', |
||||||
|
medicationsUsed: res.medicationsUsed || [], |
||||||
|
immunosuppressantName: res.immunosuppressantName || [], |
||||||
|
medicationsOptions: MEDICATIONS_OPTIONS.map((item) => ({ |
||||||
|
...item, |
||||||
|
active: (res.medicationsUsed || []).includes(item.id), |
||||||
|
})), |
||||||
|
immunosuppressantOptions: IMMUNOSUPPRESSANT_OPTIONS.map((item) => ({ |
||||||
|
...item, |
||||||
|
active: (res.immunosuppressantName || []).includes(item.id), |
||||||
|
})), |
||||||
|
oralHormoneDosage: res.oralHormoneDosage || 0, |
||||||
|
recordStatus: res.recordStatus, |
||||||
|
customDosage: |
||||||
|
res.oralHormoneDosage && !HORMONE_DOSAGE_OPTIONS.includes(res.oralHormoneDosage) |
||||||
|
? String(res.oralHormoneDosage) |
||||||
|
: '', |
||||||
|
medicationsUsedOtherText: res.medicationsUsedOtherText || '', |
||||||
|
immunosuppressantNameOtherText: res.immunosuppressantNameOtherText || '', |
||||||
|
medicationsOtherShow: (res.medicationsUsed || []).includes(99), |
||||||
|
immunosuppressantOtherShow: (res.immunosuppressantName || []).includes(99), |
||||||
|
} |
||||||
|
|
||||||
|
// 将按类型分组的图片转为 fileList
|
||||||
|
for (const typeKey of Object.keys(IMAGE_TYPE_MAP)) { |
||||||
|
const typeImages = images[typeKey] || [] |
||||||
|
setData[`${typeKey}Files`] = typeImages.map((img: any) => ({ |
||||||
|
imgUrl: img.maskedImageUrl || img.imageUrl, |
||||||
|
fileUrl: img.imageUrl, |
||||||
|
type: 'image', |
||||||
|
imageId: img.id, |
||||||
|
imageType: typeKey, |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
this.setData(setData) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
saveDraft(): Promise<any> { |
||||||
|
if (this.data.saving) return Promise.resolve() |
||||||
|
if (!this.data.visitDate) return Promise.resolve() |
||||||
|
|
||||||
|
this.setData({ saving: true }) |
||||||
|
|
||||||
|
// 汇总所有图片
|
||||||
|
const images: any[] = [] |
||||||
|
const typeKeys = Object.keys(IMAGE_TYPE_MAP) |
||||||
|
for (const typeKey of typeKeys) { |
||||||
|
const files = this.data[`${typeKey}Files` as any] as any[] |
||||||
|
if (files && files.length) { |
||||||
|
for (const file of files) { |
||||||
|
images.push({ |
||||||
|
imageUrl: file.fileUrl || file.imgUrl, |
||||||
|
imageType: typeKey, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const data: Record<string, any> = { |
||||||
|
visitDate: this.data.visitDate, |
||||||
|
nextVisitDate: this.data.nextVisitDate || undefined, |
||||||
|
manualIgG4Value: this.data.igG4Value ? Number(this.data.igG4Value) : undefined, |
||||||
|
medicationsUsed: this.data.medicationsUsed.length ? this.data.medicationsUsed : undefined, |
||||||
|
medicationsUsedOtherText: this.data.medicationsUsedOtherText || undefined, |
||||||
|
immunosuppressantName: this.data.immunosuppressantName.length ? this.data.immunosuppressantName : undefined, |
||||||
|
immunosuppressantNameOtherText: this.data.immunosuppressantNameOtherText || undefined, |
||||||
|
oralHormoneDosage: |
||||||
|
this.data.oralHormoneDosage || this.data.customDosage |
||||||
|
? Number(this.data.oralHormoneDosage || this.data.customDosage) |
||||||
|
: undefined, |
||||||
|
images, |
||||||
|
} |
||||||
|
|
||||||
|
if (this.data.draftId) { |
||||||
|
data.id = this.data.draftId |
||||||
|
} |
||||||
|
|
||||||
|
return wx |
||||||
|
.ajax({ |
||||||
|
method: 'POST', |
||||||
|
url: '?r=igg4/medical-visit/save-draft', |
||||||
|
data, |
||||||
|
}) |
||||||
|
.then((res: any) => { |
||||||
|
if (res && res.id && !this.data.draftId) { |
||||||
|
this.setData({ draftId: res.id }) |
||||||
|
} |
||||||
|
if (res && res.recordStatus) { |
||||||
|
this.setData({ recordStatus: res.recordStatus }) |
||||||
|
} |
||||||
|
}) |
||||||
|
.catch(() => {}) |
||||||
|
.finally(() => { |
||||||
|
this.setData({ saving: false }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
autoSave() { |
||||||
|
this._updateAlertBeforeUnload() |
||||||
|
if (this._saveDraftDebounced) { |
||||||
|
this._saveDraftDebounced() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
submit() { |
||||||
|
if (!this.data.draftId) { |
||||||
|
wx.showToast({ title: '请先保存草稿', icon: 'none' }) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
wx.ajax({ |
||||||
|
method: 'POST', |
||||||
|
url: '?r=igg4/medical-visit/submit', |
||||||
|
data: { id: this.data.draftId }, |
||||||
|
loading: true, |
||||||
|
}).then(() => { |
||||||
|
this.setData({ submitted: true }) |
||||||
|
wx.disableAlertBeforeUnload() |
||||||
|
wx.showToast({ title: '提交成功', icon: 'success' }) |
||||||
|
setTimeout(() => { |
||||||
|
wx.navigateBack() |
||||||
|
}, 1500) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== Step1 事件 ==========
|
||||||
|
|
||||||
|
handleVisitDateChange(e: WechatMiniprogram.PickerChange) { |
||||||
|
this.setData({ visitDate: e.detail.value as string }) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleNextVisitDateChange(e: WechatMiniprogram.PickerChange) { |
||||||
|
this.setData({ nextVisitDate: e.detail.value as string }) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleStep1Next() { |
||||||
|
if (!this.data.visitDate && !this.data.nextVisitDate) { |
||||||
|
this.setData({ toastShow: true, toastType: 'oneWrite', toastParams: {} }) |
||||||
|
return |
||||||
|
} |
||||||
|
this.saveDraft().then(() => { |
||||||
|
this.setData({ step: 2 }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== Step2 事件 ==========
|
||||||
|
|
||||||
|
handleSetData(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const imageType = e.currentTarget.dataset.type as string |
||||||
|
const file = e.detail[0] |
||||||
|
const filesKey = `${imageType}Files` |
||||||
|
const currentFiles = (this.data[filesKey as any] as any[]) || [] |
||||||
|
const newFile = { |
||||||
|
imgUrl: file.imgUrl, |
||||||
|
fileUrl: file.imgUrl, |
||||||
|
type: 'image', |
||||||
|
imageType, |
||||||
|
} |
||||||
|
this.setData({ |
||||||
|
[filesKey]: [...currentFiles, newFile], |
||||||
|
}) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleDeleteFile(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const imageType = e.currentTarget.dataset.type as string |
||||||
|
const index = e.detail.index |
||||||
|
this._removeFile(imageType, index) |
||||||
|
}, |
||||||
|
|
||||||
|
handleDeleteImage(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const imageType = e.currentTarget.dataset.type as string |
||||||
|
const index = e.currentTarget.dataset.index as number |
||||||
|
this._removeFile(imageType, index) |
||||||
|
}, |
||||||
|
|
||||||
|
_removeFile(imageType: string, index: number) { |
||||||
|
const filesKey = `${imageType}Files` |
||||||
|
const currentFiles = this.data[filesKey as any] as any[] |
||||||
|
currentFiles.splice(index, 1) |
||||||
|
this.setData({ |
||||||
|
[filesKey]: [...currentFiles], |
||||||
|
}) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handlePreviewImage(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const imageType = e.currentTarget.dataset.type as string |
||||||
|
const filesKey = `${imageType}Files` |
||||||
|
const files = this.data[filesKey as any] as any[] |
||||||
|
const urls = files.map((f) => f.fileUrl || f.imgUrl) |
||||||
|
wx.previewImage({ urls }) |
||||||
|
}, |
||||||
|
|
||||||
|
handleExample(e: WechatMiniprogram.CustomEvent) { |
||||||
|
wx.previewImage({ |
||||||
|
urls: [e.currentTarget.dataset.url], |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
handleStep2Prev() { |
||||||
|
this.saveDraft().then(() => { |
||||||
|
this.setData({ step: 1 }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
handleStep2Next() { |
||||||
|
const hasImage = Object.keys(IMAGE_TYPE_MAP).some((key) => (this.data[`${key}Files` as any] as any[]).length > 0) |
||||||
|
if (!hasImage) { |
||||||
|
this.setData({ toastShow: true, toastType: 'oneWrite', toastParams: {} }) |
||||||
|
return |
||||||
|
} |
||||||
|
this.saveDraft().then(() => { |
||||||
|
this.setData({ step: 3 }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== Step3 事件 ==========
|
||||||
|
|
||||||
|
handleIgG4Input(e: WechatMiniprogram.Input) { |
||||||
|
this.setData({ igG4Value: e.detail.value }) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleMedicationToggle(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const id = e.currentTarget.dataset.id as number |
||||||
|
const options = this.data.medicationsOptions.map((item) => { |
||||||
|
if (item.id === id) return { ...item, active: !item.active } |
||||||
|
return item |
||||||
|
}) |
||||||
|
const usedIds = options.filter((item) => item.active).map((item) => item.id) |
||||||
|
const isOtherActive = usedIds.includes(99) |
||||||
|
this.setData({ |
||||||
|
medicationsOptions: options, |
||||||
|
medicationsUsed: usedIds, |
||||||
|
medicationsOtherShow: isOtherActive, |
||||||
|
medicationsUsedOtherText: isOtherActive ? this.data.medicationsUsedOtherText : '', |
||||||
|
}) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleMedicationsUsedOtherInput(e: WechatMiniprogram.Input) { |
||||||
|
this.setData({ medicationsUsedOtherText: e.detail.value }) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleImmunosuppressantToggle(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const id = e.currentTarget.dataset.id as number |
||||||
|
const options = this.data.immunosuppressantOptions.map((item) => { |
||||||
|
if (item.id === id) return { ...item, active: !item.active } |
||||||
|
return item |
||||||
|
}) |
||||||
|
const usedIds = options.filter((item) => item.active).map((item) => item.id) |
||||||
|
const isOtherActive = usedIds.includes(99) |
||||||
|
this.setData({ |
||||||
|
immunosuppressantOptions: options, |
||||||
|
immunosuppressantName: usedIds, |
||||||
|
immunosuppressantOtherShow: isOtherActive, |
||||||
|
immunosuppressantNameOtherText: isOtherActive ? this.data.immunosuppressantNameOtherText : '', |
||||||
|
}) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleImmunosuppressantNameOtherInput(e: WechatMiniprogram.Input) { |
||||||
|
this.setData({ immunosuppressantNameOtherText: e.detail.value }) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleDosageSelect(e: WechatMiniprogram.CustomEvent) { |
||||||
|
const val = e.currentTarget.dataset.val as number |
||||||
|
this.setData({ |
||||||
|
oralHormoneDosage: this.data.oralHormoneDosage === val ? 0 : val, |
||||||
|
customDosage: '', |
||||||
|
}) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleCustomDosageInput(e: WechatMiniprogram.Input) { |
||||||
|
this.setData({ |
||||||
|
customDosage: e.detail.value, |
||||||
|
oralHormoneDosage: 0, |
||||||
|
}) |
||||||
|
this.autoSave() |
||||||
|
}, |
||||||
|
|
||||||
|
handleStep3Prev() { |
||||||
|
this.saveDraft().then(() => { |
||||||
|
this.setData({ step: 2 }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
handleSubmit() { |
||||||
|
_app.mpBehavior({ PageName: 'BTN_PatientRecordAddSubmit' }) |
||||||
|
const hasData = |
||||||
|
this.data.igG4Value || |
||||||
|
this.data.medicationsUsed.length || |
||||||
|
this.data.immunosuppressantName.length || |
||||||
|
this.data.oralHormoneDosage || |
||||||
|
this.data.customDosage |
||||||
|
if (!hasData) { |
||||||
|
this.setData({ toastShow: true, toastType: 'oneWrite', toastParams: {} }) |
||||||
|
return |
||||||
|
} |
||||||
|
this.saveDraft().then(() => { |
||||||
|
// 编辑已提交的记录,无需再调用提交接口
|
||||||
|
if (this.data.recordStatus === 2) { |
||||||
|
this.setData({ submitted: true }) |
||||||
|
wx.disableAlertBeforeUnload() |
||||||
|
wx.navigateBack() |
||||||
|
return |
||||||
|
} |
||||||
|
this.submit() |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== Toast 事件 ==========
|
||||||
|
|
||||||
|
handleToastOk() { |
||||||
|
this.setData({ |
||||||
|
toastShow: false, |
||||||
|
toastType: '', |
||||||
|
toastParams: {}, |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
handleToastCancel() { |
||||||
|
this.setData({ |
||||||
|
toastShow: false, |
||||||
|
toastType: '', |
||||||
|
toastParams: {}, |
||||||
|
}) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,418 @@ |
|||||||
|
<view class="page" id="{{theme === 'DRUG' && 'drug-page'}}"> |
||||||
|
<view class="page-header"> |
||||||
|
<view class="item active"> |
||||||
|
<view class="order"> |
||||||
|
<block wx:if="{{step<=1}}">1</block> |
||||||
|
<image wx:else class="icon" src="{{imageUrl}}icon112.png?t={{Timestamp}}"></image> |
||||||
|
</view> |
||||||
|
<view class="name {{step==1 && 'current'}}">时间记录</view> |
||||||
|
</view> |
||||||
|
<view class="line {{step>1 &&'active'}}"></view> |
||||||
|
<view class="item {{step>1 &&'active'}}"> |
||||||
|
<view class="order"> |
||||||
|
<block wx:if="{{step<=2}}">2</block> |
||||||
|
<image wx:else class="icon" src="{{imageUrl}}icon112.png?t={{Timestamp}}"></image> |
||||||
|
</view> |
||||||
|
<view class="name {{step==2 && 'current'}}">上传资料</view> |
||||||
|
</view> |
||||||
|
<view class="line {{step>2 &&'active'}}"></view> |
||||||
|
<view class="item {{step>2 &&'active'}}"> |
||||||
|
<view class="order">3</view> |
||||||
|
<view class="name {{step==3 && 'current'}}">核对指标</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- Step1: 时间记录 --> |
||||||
|
<block wx:if="{{step==1}}"> |
||||||
|
<view class="step1"> |
||||||
|
<view class="s-header"> |
||||||
|
<image class="title" src="{{imageUrl}}{{theme === 'DRUG' ? 'title5' : 'title4'}}.png?t={{Timestamp}}"></image> |
||||||
|
<image class="badge" src="{{imageUrl}}{{theme === 'DRUG' ? 'icon133' : 'icon113'}}.png?t={{Timestamp}}"></image> |
||||||
|
</view> |
||||||
|
<view class="s-form"> |
||||||
|
<view class="form-item"> |
||||||
|
<view class="label">我的本次就诊时间</view> |
||||||
|
<picker mode="date" value="{{visitDate}}" end="{{today}}" bind:change="handleVisitDateChange"> |
||||||
|
<view class="date"> |
||||||
|
<image |
||||||
|
class="icon1" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon134' : 'icon114'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
<view class="content {{visitDate && 'active'}}">{{visitDate || '选择就诊时间'}}</view> |
||||||
|
<image class="icon2" src="{{imageUrl}}icon115.png?t={{Timestamp}}"></image> |
||||||
|
</view> |
||||||
|
</picker> |
||||||
|
</view> |
||||||
|
<view class="form-item"> |
||||||
|
<view class="label">我的下次就诊时间</view> |
||||||
|
<picker mode="date" value="{{nextVisitDate}}" start="{{visitDate}}" bind:change="handleNextVisitDateChange"> |
||||||
|
<view class="date"> |
||||||
|
<image |
||||||
|
class="icon1" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon134' : 'icon114'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
<view class="content {{nextVisitDate && 'active'}}">{{nextVisitDate || '选择复诊时间'}}</view> |
||||||
|
<image class="icon2" src="{{imageUrl}}icon115.png?t={{Timestamp}}"></image> |
||||||
|
</view> |
||||||
|
</picker> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="step1-btn" bind:tap="handleStep1Next">下一步</view> |
||||||
|
</block> |
||||||
|
|
||||||
|
<!-- Step2: 上传资料 --> |
||||||
|
<view class="step2" wx:if="{{step==2}}"> |
||||||
|
<view class="tip-card">门诊材料会分类存储到 "我的-健康档案",方便您查看对比。</view> |
||||||
|
|
||||||
|
<!-- 门诊病历及处方 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label required">门诊病历及处方</view> |
||||||
|
<view |
||||||
|
class="example" |
||||||
|
bind:tap="handleExample" |
||||||
|
data-url="{{imageUrl}}{{theme === 'DRUG' ? 'bg42' : 'bg40'}}.png?t={{Timestamp}}" |
||||||
|
> |
||||||
|
示意 |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="content">医生手写或电脑打印的病历、处方</view> |
||||||
|
<view class="upload-wrap"> |
||||||
|
<view |
||||||
|
class="upload-preview" |
||||||
|
wx:for="{{outpatientRecordFiles}}" |
||||||
|
wx:key="index" |
||||||
|
data-type="outpatientRecord" |
||||||
|
data-index="{{index}}" |
||||||
|
bind:tap="handlePreviewImage" |
||||||
|
> |
||||||
|
<image class="upload-img" mode="aspectFill" src="{{item.imgUrl}}"></image> |
||||||
|
<view class="del" catch:tap="handleDeleteImage" data-type="outpatientRecord" data-index="{{index}}"> |
||||||
|
<van-icon name="cross" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<uploadFile |
||||||
|
fileTypes="{{['image']}}" |
||||||
|
bind:setData="handleSetData" |
||||||
|
data-type="outpatientRecord" |
||||||
|
maxNum="{{10}}" |
||||||
|
isSlot="{{true}}" |
||||||
|
> |
||||||
|
<view class="upload-container"> |
||||||
|
<image |
||||||
|
class="icon" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon135' : 'icon48'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
<view class="name">添加文件照片</view> |
||||||
|
</view> |
||||||
|
</uploadFile> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 检验报告 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label required">检验报告(IgG4化验单等)</view> |
||||||
|
<view |
||||||
|
class="example" |
||||||
|
bind:tap="handleExample" |
||||||
|
data-url="{{imageUrl}}{{theme === 'DRUG' ? 'bg41' : 'bg39'}}.png?t={{Timestamp}}" |
||||||
|
> |
||||||
|
示意 |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="content">抽血检测的IgG4、总免疫球蛋白、ESR等</view> |
||||||
|
<view class="upload-wrap"> |
||||||
|
<view |
||||||
|
class="upload-preview" |
||||||
|
wx:for="{{labReportFiles}}" |
||||||
|
wx:key="index" |
||||||
|
data-type="labReport" |
||||||
|
data-index="{{index}}" |
||||||
|
bind:tap="handlePreviewImage" |
||||||
|
> |
||||||
|
<image class="upload-img" mode="aspectFill" src="{{item.imgUrl}}"></image> |
||||||
|
<view class="del" catch:tap="handleDeleteImage" data-type="labReport" data-index="{{index}}"> |
||||||
|
<van-icon name="cross" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<uploadFile |
||||||
|
fileTypes="{{['image']}}" |
||||||
|
bind:setData="handleSetData" |
||||||
|
data-type="labReport" |
||||||
|
maxNum="{{10}}" |
||||||
|
isSlot="{{true}}" |
||||||
|
> |
||||||
|
<view class="upload-container"> |
||||||
|
<image |
||||||
|
class="icon" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon135' : 'icon48'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
<view class="name">添加文件照片</view> |
||||||
|
</view> |
||||||
|
</uploadFile> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 影像学检查 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label">影像学检查(CT/超声/核磁)</view> |
||||||
|
</view> |
||||||
|
<view class="content">超声、CT、核磁共振的描述结论、电子胶片</view> |
||||||
|
<view class="upload-wrap"> |
||||||
|
<view |
||||||
|
class="upload-preview" |
||||||
|
wx:for="{{imagingExamFiles}}" |
||||||
|
wx:key="index" |
||||||
|
data-type="imagingExam" |
||||||
|
data-index="{{index}}" |
||||||
|
bind:tap="handlePreviewImage" |
||||||
|
> |
||||||
|
<image class="upload-img" mode="aspectFill" src="{{item.imgUrl}}"></image> |
||||||
|
<view class="del" catch:tap="handleDeleteImage" data-type="imagingExam" data-index="{{index}}"> |
||||||
|
<van-icon name="cross" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<uploadFile |
||||||
|
fileTypes="{{['image']}}" |
||||||
|
bind:setData="handleSetData" |
||||||
|
data-type="imagingExam" |
||||||
|
maxNum="{{10}}" |
||||||
|
isSlot="{{true}}" |
||||||
|
> |
||||||
|
<view class="upload-container"> |
||||||
|
<image |
||||||
|
class="icon" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon135' : 'icon48'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
<view class="name">添加文件照片</view> |
||||||
|
</view> |
||||||
|
</uploadFile> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 病理诊断及活检 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label">病理诊断及活检</view> |
||||||
|
</view> |
||||||
|
<view class="content">受累组织穿刺或切除免疫组化报告</view> |
||||||
|
<view class="upload-wrap"> |
||||||
|
<view |
||||||
|
class="upload-preview" |
||||||
|
wx:for="{{pathologyFiles}}" |
||||||
|
wx:key="index" |
||||||
|
data-type="pathology" |
||||||
|
data-index="{{index}}" |
||||||
|
bind:tap="handlePreviewImage" |
||||||
|
> |
||||||
|
<image class="upload-img" mode="aspectFill" src="{{item.imgUrl}}"></image> |
||||||
|
<view class="del" catch:tap="handleDeleteImage" data-type="pathology" data-index="{{index}}"> |
||||||
|
<van-icon name="cross" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<uploadFile |
||||||
|
fileTypes="{{['image']}}" |
||||||
|
bind:setData="handleSetData" |
||||||
|
data-type="pathology" |
||||||
|
maxNum="{{10}}" |
||||||
|
isSlot="{{true}}" |
||||||
|
> |
||||||
|
<view class="upload-container"> |
||||||
|
<image |
||||||
|
class="icon" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon135' : 'icon48'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
<view class="name">添加文件照片</view> |
||||||
|
</view> |
||||||
|
</uploadFile> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 外周血免疫功能评估 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label">外周血免疫功能评估</view> |
||||||
|
</view> |
||||||
|
<view class="content">免疫细胞亚群、淋巴细胞比例测定</view> |
||||||
|
<view class="upload-wrap"> |
||||||
|
<view |
||||||
|
class="upload-preview" |
||||||
|
wx:for="{{immuneFunctionFiles}}" |
||||||
|
wx:key="index" |
||||||
|
data-type="immuneFunction" |
||||||
|
data-index="{{index}}" |
||||||
|
bind:tap="handlePreviewImage" |
||||||
|
> |
||||||
|
<image class="upload-img" mode="aspectFill" src="{{item.imgUrl}}"></image> |
||||||
|
<view class="del" catch:tap="handleDeleteImage" data-type="immuneFunction" data-index="{{index}}"> |
||||||
|
<van-icon name="cross" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<uploadFile |
||||||
|
fileTypes="{{['image']}}" |
||||||
|
bind:setData="handleSetData" |
||||||
|
data-type="immuneFunction" |
||||||
|
maxNum="{{10}}" |
||||||
|
isSlot="{{true}}" |
||||||
|
> |
||||||
|
<view class="upload-container"> |
||||||
|
<image |
||||||
|
class="icon" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon135' : 'icon48'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
<view class="name">添加文件照片</view> |
||||||
|
</view> |
||||||
|
</uploadFile> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<view class="step2-footer"> |
||||||
|
<view class="prev" bind:tap="handleStep2Prev">上一页</view> |
||||||
|
<view class="next" bind:tap="handleStep2Next">下一页</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- Step3: 核对指标 --> |
||||||
|
<view class="step3" wx:if="{{step==3}}"> |
||||||
|
<view class="tip-card">请填写本次就诊的以下信息,方便医生关注您的健康情况</view> |
||||||
|
|
||||||
|
<!-- IgG4 数值 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label doboule"> |
||||||
|
<view> |
||||||
|
本次就诊检验的 |
||||||
|
<view></view> |
||||||
|
IgG4(免疫球蛋白G4亚类) |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="num"> |
||||||
|
<view class="wrap"> |
||||||
|
<input |
||||||
|
class="input" |
||||||
|
placeholder-class="place-input" |
||||||
|
type="digit" |
||||||
|
placeholder="请输入" |
||||||
|
maxlength="{{8}}" |
||||||
|
value="{{igG4Value}}" |
||||||
|
bindinput="handleIgG4Input" |
||||||
|
/> |
||||||
|
<image |
||||||
|
class="icon" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon136' : 'icon117'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
</view> |
||||||
|
<view class="sub">mg/dL</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 本次就诊已使用的药物 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label">本次就诊已使用的药物</view> |
||||||
|
</view> |
||||||
|
<view class="check-group col1"> |
||||||
|
<view |
||||||
|
class="check-item {{item.active && 'active'}}" |
||||||
|
wx:for="{{medicationsOptions}}" |
||||||
|
wx:key="id" |
||||||
|
bind:tap="handleMedicationToggle" |
||||||
|
data-id="{{item.id}}" |
||||||
|
> |
||||||
|
<view class="check"> |
||||||
|
<van-icon name="success" class="icon" /> |
||||||
|
</view> |
||||||
|
<view class="content">{{item.label}}</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="custom-num" wx:if="{{medicationsOtherShow}}" style="margin-top: 20rpx"> |
||||||
|
<input |
||||||
|
class="input" |
||||||
|
placeholder-class="place-input" |
||||||
|
maxlength="{{20}}" |
||||||
|
placeholder="请输入其他药物名称" |
||||||
|
value="{{medicationsUsedOtherText}}" |
||||||
|
bindinput="handleMedicationsUsedOtherInput" |
||||||
|
/> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 口服激素每日用量 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label">最近一周,口服激素的每日用量</view> |
||||||
|
</view> |
||||||
|
<view class="num-select"> |
||||||
|
<view |
||||||
|
class="n-item {{oralHormoneDosage === item && 'active'}}" |
||||||
|
wx:for="{{hormoneDosageOptions}}" |
||||||
|
wx:key="*this" |
||||||
|
bind:tap="handleDosageSelect" |
||||||
|
data-val="{{item}}" |
||||||
|
> |
||||||
|
{{item == '0.5' ? '半':item}}片 |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="custom-num"> |
||||||
|
<input |
||||||
|
class="input" |
||||||
|
placeholder-class="place-input" |
||||||
|
type="digit" |
||||||
|
maxlength="{{5}}" |
||||||
|
placeholder="自定义用量" |
||||||
|
value="{{customDosage}}" |
||||||
|
bindinput="handleCustomDosageInput" |
||||||
|
/> |
||||||
|
<view class="sub">片</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 免疫抑制剂名称 --> |
||||||
|
<view class="form-card"> |
||||||
|
<view class="f-header"> |
||||||
|
<view class="label">正在使用的免疫抑制剂名称</view> |
||||||
|
</view> |
||||||
|
<view class="check-group col1"> |
||||||
|
<view |
||||||
|
class="check-item {{item.active && 'active'}}" |
||||||
|
wx:for="{{immunosuppressantOptions}}" |
||||||
|
wx:key="id" |
||||||
|
bind:tap="handleImmunosuppressantToggle" |
||||||
|
data-id="{{item.id}}" |
||||||
|
> |
||||||
|
<view class="check"> |
||||||
|
<van-icon name="success" class="icon" /> |
||||||
|
</view> |
||||||
|
<view class="content">{{item.label}}</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="custom-num" wx:if="{{immunosuppressantOtherShow}}" style="margin-top: 20rpx"> |
||||||
|
<input |
||||||
|
class="input" |
||||||
|
placeholder-class="place-input" |
||||||
|
maxlength="{{20}}" |
||||||
|
placeholder="请输入其他免疫抑制剂名称" |
||||||
|
value="{{immunosuppressantNameOtherText}}" |
||||||
|
bindinput="handleImmunosuppressantNameOtherInput" |
||||||
|
/> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<view class="step3-footer"> |
||||||
|
<view class="prev" bind:tap="handleStep3Prev">上一页</view> |
||||||
|
<view class="submit" bind:tap="handleSubmit">提交</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<toast |
||||||
|
bind:cancel="handleToastCancel" |
||||||
|
bind:ok="handleToastOk" |
||||||
|
show="{{toastShow}}" |
||||||
|
type="{{toastType}}" |
||||||
|
params="{{toastParams}}" |
||||||
|
></toast> |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "就诊记录详情页", |
||||||
|
"navigationBarBackgroundColor": "#F6F6F6", |
||||||
|
"usingComponents": { |
||||||
|
"van-icon": "@vant/weapp/icon/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,189 @@ |
|||||||
|
page { |
||||||
|
background-color: #f6f6f6; |
||||||
|
} |
||||||
|
.page { |
||||||
|
padding: 32rpx; |
||||||
|
.banner { |
||||||
|
padding: 28rpx 32rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
.b-header { |
||||||
|
margin-bottom: 24rpx; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
margin-left: -32rpx; |
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
border-radius: 0 8rpx 8rpx 0; |
||||||
|
background: #0eb66d; |
||||||
|
} |
||||||
|
} |
||||||
|
.row { |
||||||
|
display: flex; |
||||||
|
gap: 32rpx; |
||||||
|
&:last-of-type { |
||||||
|
margin-top: 22rpx; |
||||||
|
} |
||||||
|
.icon { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 96rpx; |
||||||
|
height: 96rpx; |
||||||
|
} |
||||||
|
.col { |
||||||
|
.name { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
.date { |
||||||
|
margin-top: 12rpx; |
||||||
|
font-size: 40rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.switch-wrap { |
||||||
|
margin-top: 32rpx; |
||||||
|
padding: 28rpx 32rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.name { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
.switch { |
||||||
|
transform: scale(0.8); |
||||||
|
} |
||||||
|
} |
||||||
|
.photos { |
||||||
|
margin-top: 32rpx; |
||||||
|
padding: 28rpx 32rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
.p-header { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
margin-left: -32rpx; |
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
border-radius: 0 8rpx 8rpx 0; |
||||||
|
background: #0eb66d; |
||||||
|
} |
||||||
|
} |
||||||
|
.p-container { |
||||||
|
margin-top: 24rpx; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(2, 1fr); |
||||||
|
gap: 22rpx; |
||||||
|
.photo { |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 168rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.container { |
||||||
|
margin-top: 32rpx; |
||||||
|
padding: 0 32rpx 28rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
.row { |
||||||
|
padding-top: 32rpx; |
||||||
|
border-bottom: 1px solid rgba(34, 34, 34, 0.08); |
||||||
|
&:last-of-type { |
||||||
|
border: none; |
||||||
|
} |
||||||
|
.name { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
margin-left: -32rpx; |
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
border-radius: 0 8rpx 8rpx 0; |
||||||
|
background: #0eb66d; |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 16rpx; |
||||||
|
padding-bottom: 28rpx; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.footer { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
.col { |
||||||
|
flex: 1; |
||||||
|
padding: 50rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
gap: 12rpx; |
||||||
|
.icon { |
||||||
|
width: 44rpx; |
||||||
|
height: 44rpx; |
||||||
|
} |
||||||
|
.name { |
||||||
|
font-size: 36rpx; |
||||||
|
color: #222222; |
||||||
|
&.name1 { |
||||||
|
color: #0eb66d; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.line { |
||||||
|
width: 1px; |
||||||
|
height: 36rpx; |
||||||
|
background: rgba(34, 34, 34, 0.1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#drug-page { |
||||||
|
.banner { |
||||||
|
.b-header::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.photos { |
||||||
|
.p-header::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.container { |
||||||
|
.name::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.footer { |
||||||
|
.name1 { |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,158 @@ |
|||||||
|
const MEDICATIONS_MAP: Record<number, string> = { |
||||||
|
1: '没有使用激素', |
||||||
|
2: '激素', |
||||||
|
3: '免疫抑制剂', |
||||||
|
4: '靶向CD19生物制剂', |
||||||
|
5: '靶向CD20生物制剂', |
||||||
|
99: '其他', |
||||||
|
} |
||||||
|
|
||||||
|
const IMMUNOSUPPRESSANT_MAP: Record<number, string> = { |
||||||
|
1: '吗替麦考酚酯', |
||||||
|
2: '替唑嘌呤', |
||||||
|
3: '环磷酰胺', |
||||||
|
4: '来氟米特', |
||||||
|
5: '甲氨蝶呤', |
||||||
|
6: '环孢素A', |
||||||
|
7: '他克莫司', |
||||||
|
8: '艾拉莫德', |
||||||
|
99: '其他', |
||||||
|
} |
||||||
|
|
||||||
|
const IMAGE_TYPE_LABELS: Record<string, string> = { |
||||||
|
outpatientRecord: '门诊病历及处方', |
||||||
|
labReport: '检验报告', |
||||||
|
imagingExam: '影像学检查', |
||||||
|
pathology: '病理诊断及活检', |
||||||
|
immuneFunction: '外周血免疫功能评估', |
||||||
|
} |
||||||
|
|
||||||
|
const app = getApp<IAppOption>() |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
id: 0, |
||||||
|
visitDate: '', |
||||||
|
nextVisitDate: '', |
||||||
|
displayIgG4Value: '' as string | number | null, |
||||||
|
medicationsLabel: '', |
||||||
|
oralHormoneDosageMg: '' as string | number | null, |
||||||
|
immunosuppressantLabel: '', |
||||||
|
reminderEnabled: 2, |
||||||
|
imageGroups: [] as { type: string; label: string; list: string[] }[], |
||||||
|
}, |
||||||
|
|
||||||
|
onLoad(options: any) { |
||||||
|
app.waitLogin().then(() => { |
||||||
|
app.mpBehavior({ PageName: 'PG_PatientRecordDetail', detailId: String(options.id) }) |
||||||
|
}) |
||||||
|
if (options.id) { |
||||||
|
this.setData({ id: Number(options.id) }) |
||||||
|
this.getDetail() |
||||||
|
this.getReminderConfig() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== API ==========
|
||||||
|
|
||||||
|
getDetail() { |
||||||
|
wx.ajax({ |
||||||
|
method: 'GET', |
||||||
|
url: '?r=igg4/medical-visit/detail', |
||||||
|
data: { id: this.data.id }, |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) return |
||||||
|
const medicationsLabel = (res.medicationsUsed || []) |
||||||
|
.map((id: number) => { |
||||||
|
if (id === 99 && res.medicationsUsedOtherText) return `其他:${res.medicationsUsedOtherText}` |
||||||
|
return MEDICATIONS_MAP[id] |
||||||
|
}) |
||||||
|
.filter(Boolean) |
||||||
|
.join('、') |
||||||
|
const immunosuppressantLabel = (res.immunosuppressantName || []) |
||||||
|
.map((id: number) => { |
||||||
|
if (id === 99 && res.immunosuppressantNameOtherText) return `其他:${res.immunosuppressantNameOtherText}` |
||||||
|
return IMMUNOSUPPRESSANT_MAP[id] |
||||||
|
}) |
||||||
|
.filter(Boolean) |
||||||
|
.join('、') |
||||||
|
|
||||||
|
const imageGroups: { type: string; label: string; list: string[] }[] = [] |
||||||
|
const images = res.images || {} |
||||||
|
for (const type of Object.keys(IMAGE_TYPE_LABELS)) { |
||||||
|
const list = (images[type] || []).map((img: any) => img.maskedImageUrl || img.imageUrl) |
||||||
|
if (list.length) { |
||||||
|
imageGroups.push({ type, label: IMAGE_TYPE_LABELS[type], list }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.setData({ |
||||||
|
visitDate: res.visitDate || '', |
||||||
|
nextVisitDate: res.nextVisitDate || '', |
||||||
|
displayIgG4Value: res.displayIgG4Value, |
||||||
|
medicationsLabel, |
||||||
|
oralHormoneDosageMg: res.oralHormoneDosageMg, |
||||||
|
immunosuppressantLabel, |
||||||
|
imageGroups, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
getReminderConfig() { |
||||||
|
wx.ajax({ |
||||||
|
method: 'GET', |
||||||
|
url: '?r=igg4/medical-visit/get-reminder-config', |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) return |
||||||
|
this.setData({ reminderEnabled: res.reminderEnabled || 2 }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
saveReminderConfig(e: any) { |
||||||
|
const val = e.detail.value ? 1 : 2 |
||||||
|
this.setData({ reminderEnabled: val }) |
||||||
|
wx.ajax({ |
||||||
|
method: 'POST', |
||||||
|
url: '?r=igg4/medical-visit/save-reminder-config', |
||||||
|
data: { reminderEnabled: val }, |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== 操作 ==========
|
||||||
|
|
||||||
|
handleDelete() { |
||||||
|
wx.showModal({ |
||||||
|
title: `删除${this.data.visitDate || ''}记录`, |
||||||
|
content: `删除后将无法恢复`, |
||||||
|
confirmText: '确认删除', |
||||||
|
confirmColor: '#ee0a24', |
||||||
|
success: (res) => { |
||||||
|
if (!res.confirm) return |
||||||
|
wx.ajax({ |
||||||
|
method: 'POST', |
||||||
|
url: '?r=igg4/medical-visit/delete', |
||||||
|
data: { id: this.data.id }, |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) return |
||||||
|
wx.navigateBack() |
||||||
|
}) |
||||||
|
}, |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
handleEdit() { |
||||||
|
wx.navigateTo({ url: `/gift/pages/record/index?id=${this.data.id}` }) |
||||||
|
}, |
||||||
|
|
||||||
|
handlePreviewImage(e: any) { |
||||||
|
const { url, type } = e.currentTarget.dataset |
||||||
|
const group = this.data.imageGroups.find((g) => g.type === type) |
||||||
|
if (!group) return |
||||||
|
wx.previewImage({ |
||||||
|
current: url, |
||||||
|
urls: group.list, |
||||||
|
}) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
<view class="page" id="{{theme === 'DRUG' && 'drug-page'}}"> |
||||||
|
<view class="banner"> |
||||||
|
<view class="b-header">就诊时间</view> |
||||||
|
<view class="row"> |
||||||
|
<!-- <image class="icon" src="{{imageUrl}}{{theme === 'DRUG' ? 'icon137' : 'icon121'}}.png?t={{Timestamp}}"></image> --> |
||||||
|
<view class="col"> |
||||||
|
<view class="name">我的本次就诊时间</view> |
||||||
|
<view class="date">{{visitDate || '--'}}</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="row"> |
||||||
|
<!-- <image class="icon" src="{{imageUrl}}{{theme === 'DRUG' ? 'icon138' : 'icon122'}}.png?t={{Timestamp}}"></image> --> |
||||||
|
<view class="col"> |
||||||
|
<view class="name">我的下次就诊时间</view> |
||||||
|
<view class="date">{{nextVisitDate || '--'}}</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="switch-wrap"> |
||||||
|
<view class="name">复诊提醒</view> |
||||||
|
<switch class="switch" checked="{{reminderEnabled === 1}}" bind:change="saveReminderConfig" color="{{theme === 'DRUG' ? 'rgba(89, 86, 233, 1)' : 'rgba(14, 182, 109, 1)'}}" /> |
||||||
|
</view> |
||||||
|
<view class="photos" wx:for="{{imageGroups}}" wx:key="type"> |
||||||
|
<view class="p-header">{{item.label}}</view> |
||||||
|
<view class="p-container"> |
||||||
|
<image |
||||||
|
class="photo" |
||||||
|
wx:for="{{item.list}}" |
||||||
|
wx:key="*this" |
||||||
|
wx:for-item="imgUrl" |
||||||
|
src="{{imgUrl}}" |
||||||
|
mode="aspectFill" |
||||||
|
bind:tap="handlePreviewImage" |
||||||
|
data-url="{{imgUrl}}" |
||||||
|
data-type="{{item.type}}" |
||||||
|
></image> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="container"> |
||||||
|
<view class="row" wx:if="{{displayIgG4Value !== null && displayIgG4Value !== ''}}"> |
||||||
|
<view class="name">IgG4值</view> |
||||||
|
<view class="content">{{displayIgG4Value}} mg/dL</view> |
||||||
|
</view> |
||||||
|
<view class="row" wx:if="{{medicationsLabel}}"> |
||||||
|
<view class="name">本次就诊已使用的药物</view> |
||||||
|
<view class="content">{{medicationsLabel}}</view> |
||||||
|
</view> |
||||||
|
<view class="row" wx:if="{{oralHormoneDosageMg !== null && oralHormoneDosageMg !== ''}}"> |
||||||
|
<view class="name">最近一周,每日激素使用剂量</view> |
||||||
|
<view class="content">{{oralHormoneDosageMg}}mg/天</view> |
||||||
|
</view> |
||||||
|
<view class="row" wx:if="{{immunosuppressantLabel}}"> |
||||||
|
<view class="name">正在使用的免疫抑制剂名称</view> |
||||||
|
<view class="content">{{immunosuppressantLabel}}</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="footer"> |
||||||
|
<view class="col" bind:tap="handleDelete"> |
||||||
|
<image class="icon" src="{{imageUrl}}icon123.png?t={{Timestamp}}"></image> |
||||||
|
<view class="name">删除</view> |
||||||
|
</view> |
||||||
|
<view class="line"></view> |
||||||
|
<view class="col" bind:tap="handleEdit"> |
||||||
|
<image class="icon" src="{{imageUrl}}{{theme === 'DRUG' ? 'icon139' : 'icon124'}}.png?t={{Timestamp}}"></image> |
||||||
|
<view class="name name1">编辑</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "就诊记录", |
||||||
|
"navigationBarBackgroundColor": "#F6F6F6", |
||||||
|
"usingComponents": { |
||||||
|
"uploadFile": "/components/uploadFile/index", |
||||||
|
"van-icon": "@vant/weapp/icon/index", |
||||||
|
"ec-canvas": "/components/ec-canvas/ec-canvas", |
||||||
|
"pagination": "/components/pagination/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,305 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(246, 246, 246, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.page { |
||||||
|
padding: 0 32rpx 300rpx; |
||||||
|
.user { |
||||||
|
.name { |
||||||
|
font-size: 44rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 12rpx; |
||||||
|
.icon { |
||||||
|
width: 57rpx; |
||||||
|
height: 73rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 12rpx; |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
.high { |
||||||
|
color: rgba(14, 182, 109, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.chart { |
||||||
|
.c-header { |
||||||
|
margin-top: 42rpx; |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
.title { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(0, 0, 0, 1); |
||||||
|
font-weight: bold; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
background: #0eb66d; |
||||||
|
border-radius: 32rpx 32rpx 32rpx 32rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.sub { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(34, 34, 34, 0.4); |
||||||
|
} |
||||||
|
} |
||||||
|
.chart-container { |
||||||
|
margin-top: 32rpx; |
||||||
|
padding: 32rpx; |
||||||
|
background: linear-gradient(180deg, #f5f4f4 0%, #ffffff 17.72%, #ffffff 100%); |
||||||
|
border-radius: 24rpx 24rpx 24rpx 24rpx; |
||||||
|
border: 2rpx solid #ffffff; |
||||||
|
.chart-header { |
||||||
|
display: flex; |
||||||
|
gap: 24rpx; |
||||||
|
.type { |
||||||
|
padding: 8rpx 32rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(105, 104, 110, 1); |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 58rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 14rpx; |
||||||
|
border: 1px solid #f3f3f3; |
||||||
|
.icon { |
||||||
|
font-size: 24rpx; |
||||||
|
color: rgba(105, 104, 110, 1); |
||||||
|
transform: rotate(90deg); |
||||||
|
} |
||||||
|
} |
||||||
|
.date { |
||||||
|
padding: 8rpx 32rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(105, 104, 110, 1); |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 58rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 6rpx; |
||||||
|
border: 1px solid #f3f3f3; |
||||||
|
.start, |
||||||
|
.end { |
||||||
|
color: rgba(105, 104, 110, 1); |
||||||
|
&.active { |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.line { |
||||||
|
margin: 0 10rpx; |
||||||
|
} |
||||||
|
.icon { |
||||||
|
transform: rotate(90deg); |
||||||
|
color: rgba(105, 104, 110, 1); |
||||||
|
font-size: 24rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.w-chart { |
||||||
|
height: 380rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.list { |
||||||
|
margin-top: 36rpx; |
||||||
|
.l-header { |
||||||
|
padding-bottom: 32rpx; |
||||||
|
.title { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
font-size: 36rpx; |
||||||
|
color: #000; |
||||||
|
font-weight: bold; |
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
width: 8rpx; |
||||||
|
height: 36rpx; |
||||||
|
background: #0eb66d; |
||||||
|
border-radius: 32rpx 32rpx 32rpx 32rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.card { |
||||||
|
display: flex; |
||||||
|
gap: 20rpx; |
||||||
|
.aside { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
flex-shrink: 0; |
||||||
|
.line-top, |
||||||
|
.line-bottom { |
||||||
|
border-right: 1px dashed rgba(34, 34, 34, 0.2); |
||||||
|
} |
||||||
|
.line-top { |
||||||
|
flex-shrink: 0; |
||||||
|
} |
||||||
|
.line-bottom { |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
.circle { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 20rpx; |
||||||
|
height: 20rpx; |
||||||
|
border-radius: 50%; |
||||||
|
background-color: rgba(14, 182, 109, 1); |
||||||
|
background-clip: content-box; |
||||||
|
border: 6rpx solid #e1f0e9; |
||||||
|
} |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
flex: 1; |
||||||
|
min-width: 0; |
||||||
|
padding-bottom: 48rpx; |
||||||
|
.date { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
line-height: 1; |
||||||
|
} |
||||||
|
.container { |
||||||
|
position: relative; |
||||||
|
margin-top: 24rpx; |
||||||
|
padding: 32rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
.del { |
||||||
|
position: absolute; |
||||||
|
top: 1px; |
||||||
|
right: 1px; |
||||||
|
padding: 8rpx 16rpx; |
||||||
|
background-color: rgba(246, 246, 246, 1); |
||||||
|
font-size: 28rpx; |
||||||
|
font-weight: bold; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
border-radius: 0 24rpx 0 24rpx; |
||||||
|
} |
||||||
|
.name { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 16rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.c-left { |
||||||
|
display: flex; |
||||||
|
gap: 8rpx; |
||||||
|
align-items: baseline; |
||||||
|
line-height: 84rpx; |
||||||
|
.num { |
||||||
|
font-size: 72rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.sub { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
.icon { |
||||||
|
width: 44rpx; |
||||||
|
height: 51rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.status { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(14, 182, 109, 1); |
||||||
|
padding: 0 12rpx; |
||||||
|
background-color: rgba(14, 182, 109, 0.08); |
||||||
|
border-radius: 8rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.row { |
||||||
|
margin-top: 32rpx; |
||||||
|
padding: 32rpx; |
||||||
|
background-color: rgba(246, 246, 246, 1); |
||||||
|
border-radius: 16rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 8rpx; |
||||||
|
.col { |
||||||
|
flex: 1; |
||||||
|
.c-name { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(34, 34, 34, 0.5); |
||||||
|
} |
||||||
|
.c-content { |
||||||
|
margin-top: 16rpx; |
||||||
|
font-size: 40rpx; |
||||||
|
color: rgba(34, 34, 34, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.page-footer { |
||||||
|
position: fixed; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 32rpx 32rpx calc(env(safe-area-inset-bottom) + 32rpx); |
||||||
|
background-color: #fff; |
||||||
|
box-shadow: 0rpx -10rpx 30rpx 0rpx rgba(0, 0, 0, 0.01); |
||||||
|
.btn { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(255, 255, 255, 1); |
||||||
|
height: 96rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
background: linear-gradient(86deg, #0eb66d 0%, #00d277 100%); |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#drug-page { |
||||||
|
.user { |
||||||
|
.content { |
||||||
|
.high { |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.chart { |
||||||
|
.c-header { |
||||||
|
.title::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.list { |
||||||
|
.l-header { |
||||||
|
.title::before { |
||||||
|
background: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.card { |
||||||
|
.circle { |
||||||
|
background-color: rgba(89, 86, 233, 1); |
||||||
|
border-color: rgba(89, 86, 233, 0.08); |
||||||
|
} |
||||||
|
.status { |
||||||
|
background-color: rgba(89, 86, 233, 0.08); |
||||||
|
color: rgba(89, 86, 233, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.page-footer { |
||||||
|
.btn { |
||||||
|
background: linear-gradient(274deg, #5956e9 0%, #b384f4 100%); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,351 @@ |
|||||||
|
import dayjs from 'dayjs' |
||||||
|
const _app = getApp<IAppOption>() |
||||||
|
let echarts: any = null |
||||||
|
|
||||||
|
const MEDICATIONS_MAP: Record<number, string> = { |
||||||
|
1: '没有使用激素', |
||||||
|
2: '激素', |
||||||
|
3: '免疫抑制剂', |
||||||
|
4: '靶向CD19生物制剂', |
||||||
|
5: '靶向CD20生物制剂', |
||||||
|
99: '其他', |
||||||
|
} |
||||||
|
|
||||||
|
const IMMUNOSUPPRESSANT_MAP: Record<number, string> = { |
||||||
|
1: '吗替麦考酚酯', |
||||||
|
2: '替唑嘌呤', |
||||||
|
3: '环磷酰胺', |
||||||
|
4: '来氟米特', |
||||||
|
5: '甲氨蝶呤', |
||||||
|
6: '环孢素A', |
||||||
|
7: '他克莫司', |
||||||
|
8: '艾拉莫德', |
||||||
|
99: '其他', |
||||||
|
} |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
list: [] as any[], |
||||||
|
pagination: { count: 0, page: 1, pages: 1 }, |
||||||
|
year: 0 as number, |
||||||
|
yearOptions: [ |
||||||
|
{ |
||||||
|
label: '按年', |
||||||
|
value: 1, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '按次', |
||||||
|
value: 2, |
||||||
|
}, |
||||||
|
] as { label: string; value: number }[], |
||||||
|
beginDate: dayjs().subtract(1, 'year').add(1, 'month').format('YYYY-MM'), |
||||||
|
endDate: dayjs().format('YYYY-MM'), |
||||||
|
|
||||||
|
// 用户头部
|
||||||
|
latestIgG4: '' as string | number, |
||||||
|
|
||||||
|
// 图表
|
||||||
|
chartUnit: '', |
||||||
|
chartList: [] as any[], |
||||||
|
|
||||||
|
selected: 1, |
||||||
|
selectedLabel: '按年', |
||||||
|
}, |
||||||
|
|
||||||
|
ecDataTrendComponent: null as any, |
||||||
|
|
||||||
|
onLoad() { |
||||||
|
echarts = require.async('../../../resource/components/echart/echarts.js').then((mod: any) => { |
||||||
|
echarts = mod |
||||||
|
}) |
||||||
|
_app.waitLogin().then(() => { |
||||||
|
_app.mpBehavior({ PageName: 'PG_PatientRecordList' }) |
||||||
|
this.getLatestRecord() |
||||||
|
this.getList() |
||||||
|
this.getIndicatorHistory() |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
onShow() { |
||||||
|
if (this.data.list.length) { |
||||||
|
this.getLatestRecord() |
||||||
|
this.getList(true) |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== API ==========
|
||||||
|
|
||||||
|
getList(reset = false) { |
||||||
|
const page = reset ? 1 : this.data.pagination.page |
||||||
|
const data: Record<string, any> = { page, pageSize: 10 } |
||||||
|
|
||||||
|
wx.ajax({ |
||||||
|
method: 'GET', |
||||||
|
url: '?r=igg4/medical-visit/list', |
||||||
|
data, |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) return |
||||||
|
const list = (res.list || []).map((item: any) => ({ |
||||||
|
...item, |
||||||
|
medicationLabels: (item.medicationsUsed || []).map((id: number) => MEDICATIONS_MAP[id]).filter(Boolean), |
||||||
|
immunosuppressantLabels: (item.immunosuppressantName || []) |
||||||
|
.map((id: number) => IMMUNOSUPPRESSANT_MAP[id]) |
||||||
|
.filter(Boolean), |
||||||
|
statusText: item.recordStatus === 1 ? '草稿' : '已提交', |
||||||
|
})) |
||||||
|
|
||||||
|
// 提取年份选项
|
||||||
|
const yearSet = new Set<number>() |
||||||
|
for (const item of res.list || []) { |
||||||
|
if (item.visitDate) yearSet.add(Number(item.visitDate.substring(0, 4))) |
||||||
|
} |
||||||
|
|
||||||
|
this.setData({ |
||||||
|
list: reset ? list : [...this.data.list, ...list], |
||||||
|
pagination: { |
||||||
|
count: res.totalCount || 0, |
||||||
|
page: res.page || 1, |
||||||
|
pages: res.pages || 1, |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
getLatestRecord() { |
||||||
|
wx.ajax({ |
||||||
|
method: 'GET', |
||||||
|
url: '?r=igg4/medical-visit/get-latest-record', |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) { |
||||||
|
this.setData({ latestIgG4: '' }) |
||||||
|
return |
||||||
|
} |
||||||
|
this.setData({ latestIgG4: res.displayIgG4Value ?? '' }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
getIndicatorHistory() { |
||||||
|
const data: Record<string, any> = { indicatorField: 'igG4' } |
||||||
|
const { beginDate, endDate, selected } = this.data |
||||||
|
|
||||||
|
if (beginDate && endDate && selected === 1) { |
||||||
|
data.yearStart = Number(beginDate.substring(0, 4)) |
||||||
|
data.yearEnd = Number(endDate.substring(0, 4)) |
||||||
|
} |
||||||
|
|
||||||
|
wx.ajax({ |
||||||
|
method: 'GET', |
||||||
|
url: '?r=igg4/medical-visit/indicator-history', |
||||||
|
data, |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) return |
||||||
|
this.setData({ |
||||||
|
chartUnit: res.unit || 'mg/dL', |
||||||
|
chartList: res.list || [], |
||||||
|
}) |
||||||
|
this.initChart(res.list || []) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
handleDelete(e: any) { |
||||||
|
const { id, date } = e.currentTarget.dataset |
||||||
|
wx.showModal({ |
||||||
|
title: `删除${date}记录`, |
||||||
|
content: `删除后将无法恢复`, |
||||||
|
confirmText: '确认删除', |
||||||
|
confirmColor: '#cf5375', |
||||||
|
success: (res) => { |
||||||
|
if (!res.confirm) return |
||||||
|
wx.ajax({ |
||||||
|
method: 'POST', |
||||||
|
url: '?r=igg4/medical-visit/delete', |
||||||
|
data: { id }, |
||||||
|
}).then((res: any) => { |
||||||
|
if (!res) return |
||||||
|
const list = this.data.list.filter((item: any) => item.id !== id) |
||||||
|
this.setData({ list }) |
||||||
|
}) |
||||||
|
}, |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== 图表 ==========
|
||||||
|
|
||||||
|
initChart(defaultList: any[] = []) { |
||||||
|
if (!defaultList.length) return |
||||||
|
if (!echarts || !this.ecDataTrendComponent) { |
||||||
|
this.ecDataTrendComponent = this.selectComponent('#chart1') |
||||||
|
if (!echarts || !this.ecDataTrendComponent) return |
||||||
|
} |
||||||
|
|
||||||
|
const list = defaultList |
||||||
|
|
||||||
|
const { theme } = this.data |
||||||
|
this.ecDataTrendComponent.init((canvas: any, width: number, height: number, dpr: number) => { |
||||||
|
const chart = echarts.init(canvas, null, { |
||||||
|
width, |
||||||
|
height, |
||||||
|
devicePixelRatio: dpr, |
||||||
|
}) |
||||||
|
canvas.setChart(chart) |
||||||
|
|
||||||
|
const option: any = { |
||||||
|
tooltip: { |
||||||
|
show: false, |
||||||
|
trigger: 'axis', |
||||||
|
axisPointer: { type: 'shadow' }, |
||||||
|
confine: true, |
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)', |
||||||
|
textStyle: { color: '#fff', fontSize: 10 }, |
||||||
|
order: 'seriesDesc', |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
top: '30', |
||||||
|
left: '0', |
||||||
|
right: '0', |
||||||
|
bottom: '10', |
||||||
|
containLabel: true, |
||||||
|
}, |
||||||
|
xAxis: [ |
||||||
|
{ |
||||||
|
type: 'category', |
||||||
|
axisTick: { alignWithLabel: true }, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { color: 'rgba(34, 34, 34, 0.20)', type: 'dashed' }, |
||||||
|
}, |
||||||
|
data: list.map((item: any) => dayjs(item.visitDate).format('MM-DD')), |
||||||
|
}, |
||||||
|
], |
||||||
|
yAxis: [ |
||||||
|
{ |
||||||
|
type: 'value', |
||||||
|
min: 0, |
||||||
|
minInterval: 1, |
||||||
|
axisLabel: { color: 'rgba(34, 34, 34, 0.40)' }, |
||||||
|
splitLine: { |
||||||
|
show:false, |
||||||
|
lineStyle: { color: 'rgba(34, 34, 34, 0.20)', type: 'dashed' }, |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: 'IgG4', |
||||||
|
data: list.length ? list.map((item: any) => item.value) : [0], |
||||||
|
barWidth: '16', |
||||||
|
label: { |
||||||
|
show: true, |
||||||
|
position: 'top', |
||||||
|
color: theme === 'DRUG' ? 'rgba(89, 86, 233, 1)' : 'rgba(14, 182, 109, 1)', |
||||||
|
}, |
||||||
|
type: 'line', |
||||||
|
symbol: 'circle', |
||||||
|
symbolSize: 8, |
||||||
|
showSymbol: list.length >= 1, |
||||||
|
connectNulls: true, |
||||||
|
z: 10, |
||||||
|
itemStyle: { |
||||||
|
color: theme === 'DRUG' ? 'rgba(89, 86, 233, 1)' : 'rgba(14, 182, 109, 1)', |
||||||
|
}, |
||||||
|
markLine: { |
||||||
|
symbol: ['none', 'none'], |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
name: '135 安全区', |
||||||
|
yAxis: 135, |
||||||
|
label: { |
||||||
|
formatter: '{b}', |
||||||
|
position: 'insideMiddle', |
||||||
|
color: theme === 'DRUG' ? 'rgba(89, 86, 233, 1)' : 'rgba(14, 182, 109, 1)', |
||||||
|
fontSize: '10', |
||||||
|
}, |
||||||
|
lineStyle: { |
||||||
|
cap: '', |
||||||
|
color: theme === 'DRUG' ? 'rgba(89, 86, 233, 1)' : 'rgba(14, 182, 109, 1)', |
||||||
|
type: 'dashed', |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
markArea: { |
||||||
|
itemStyle: { |
||||||
|
color: theme === 'DRUG' ? 'rgba(89, 86, 233, 0.06)' : 'rgba(14, 182, 109, 0.06)', |
||||||
|
}, |
||||||
|
data: [[{ yAxis: 0 }, { yAxis: 135 }]], |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
dataZoom: { |
||||||
|
type: 'inside', |
||||||
|
startValue: 0, |
||||||
|
endValue: 4, |
||||||
|
filterMode: 'none', |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
chart.setOption(option) |
||||||
|
return chart |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== 筛选 ==========
|
||||||
|
|
||||||
|
handleChange(e: any) { |
||||||
|
const idx = Number(e.detail.value) |
||||||
|
const selected = this.data.yearOptions[idx] |
||||||
|
if (!selected) return |
||||||
|
// 按年模式重置为默认12个月范围,按次模式清空让用户选择
|
||||||
|
const isByYear = selected.value === 1 |
||||||
|
this.setData({ |
||||||
|
selected: selected.value, |
||||||
|
selectedLabel: selected.label, |
||||||
|
beginDate: isByYear ? dayjs().subtract(1, 'year').add(1, 'month').format('YYYY-MM') : '', |
||||||
|
endDate: isByYear ? dayjs().format('YYYY-MM') : '', |
||||||
|
}) |
||||||
|
this.getIndicatorHistory() |
||||||
|
}, |
||||||
|
|
||||||
|
handleBeginDateChange(e: any) { |
||||||
|
const beginDate = e.detail.value |
||||||
|
this.setData({ beginDate }) |
||||||
|
// 开始和结束日期都选择了才请求
|
||||||
|
if (this.data.endDate) { |
||||||
|
this.getIndicatorHistory() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
handleEndDateChange(e: any) { |
||||||
|
const endDate = e.detail.value |
||||||
|
this.setData({ endDate }) |
||||||
|
if (this.data.beginDate) { |
||||||
|
this.getIndicatorHistory() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// ========== 导航 ==========
|
||||||
|
|
||||||
|
handleDetail(e: any) { |
||||||
|
_app.mpBehavior({ PageName: 'BTN_PatientRecordListItem' }) |
||||||
|
const { id, status } = e.currentTarget.dataset |
||||||
|
if (status === 1) { |
||||||
|
wx.navigateTo({ url: `/gift/pages/record/index?id=${id}` }) |
||||||
|
} else { |
||||||
|
wx.navigateTo({ url: `/gift/pages/recordDetail/index?id=${id}` }) |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
handleAdd() { |
||||||
|
_app.mpBehavior({ PageName: 'BTN_PatientRecordListAdd' }) |
||||||
|
wx.navigateTo({ url: '/gift/pages/record/index' }) |
||||||
|
}, |
||||||
|
|
||||||
|
onReachBottom() { |
||||||
|
const { page, pages } = this.data.pagination |
||||||
|
if (page < pages) { |
||||||
|
this.setData({ 'pagination.page': page + 1 }) |
||||||
|
this.getList() |
||||||
|
} |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,99 @@ |
|||||||
|
<view class="page" id="{{theme === 'DRUG' && 'drug-page'}}"> |
||||||
|
<view class="user"> |
||||||
|
<!-- <view class="name"> --> |
||||||
|
<!-- 张先生,您好 --> |
||||||
|
<!-- <image class="icon" src="{{imageUrl}}{{theme === 'DRUG' ? 'icon131' : 'icon119'}}.png?t={{Timestamp}}"></image> --> |
||||||
|
<!-- </view> --> |
||||||
|
<view class="content" wx:if="{{latestIgG4 !== ''}}"> |
||||||
|
您的最近一次IgG4数值为: |
||||||
|
<text class="high">{{latestIgG4}} mg/dL</text> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="chart" wx:if="{{chartList.length}}"> |
||||||
|
<view class="c-header"> |
||||||
|
<view class="title">血清IgG4趋势监测</view> |
||||||
|
<view class="sub">单位{{chartUnit}}</view> |
||||||
|
</view> |
||||||
|
<view class="chart-container"> |
||||||
|
<view class="chart-header"> |
||||||
|
<picker range="{{yearOptions}}" range-key="label" bindchange="handleChange"> |
||||||
|
<view class="type"> |
||||||
|
{{selectedLabel}} |
||||||
|
<van-icon class="icon" name="play" /> |
||||||
|
</view> |
||||||
|
</picker> |
||||||
|
<view class="date" wx:if="{{selected === 1}}"> |
||||||
|
<picker mode="date" fields="month" bindchange="handleBeginDateChange" value="{{beginDate}}" end="{{endDate}}"> |
||||||
|
<view class="start {{beginDate && 'active'}}">{{beginDate ? beginDate : '开始时间'}}</view> |
||||||
|
</picker> |
||||||
|
<view class="line">-</view> |
||||||
|
<picker mode="date" fields="month" bindchange="handleEndDateChange" value="{{endDate}}" start="{{beginDate}}"> |
||||||
|
<view class="end {{endDate && 'active'}}">{{endDate ? endDate : '结束时间'}}</view> |
||||||
|
</picker> |
||||||
|
<van-icon class="icon" name="play" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="w-chart"> |
||||||
|
<ec-canvas id="chart1" ec="{{ ec }}"></ec-canvas> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="list"> |
||||||
|
<view class="l-header"> |
||||||
|
<view class="title">就诊历史记录</view> |
||||||
|
</view> |
||||||
|
<view |
||||||
|
class="card" |
||||||
|
wx:for="{{list}}" |
||||||
|
wx:key="id" |
||||||
|
bind:tap="handleDetail" |
||||||
|
data-id="{{item.id}}" |
||||||
|
data-status="{{item.recordStatus}}" |
||||||
|
> |
||||||
|
<view class="aside"> |
||||||
|
<view class="line-top"></view> |
||||||
|
<view class="circle"></view> |
||||||
|
<view class="line-bottom"></view> |
||||||
|
</view> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="date">{{item.visitDate}}</view> |
||||||
|
<view class="container"> |
||||||
|
<view class="del" catch:tap="handleDelete" data-id="{{item.id}}" data-date="{{item.visitDate}}"> |
||||||
|
<van-icon name="cross" /> |
||||||
|
</view> |
||||||
|
<view class="name">IgG4值</view> |
||||||
|
<view class="content"> |
||||||
|
<view class="c-left"> |
||||||
|
<view class="num">{{item.displayIgG4Value || '--'}}</view> |
||||||
|
<view class="sub">mg/dL</view> |
||||||
|
<image class="icon" wx:if="{{item.change > 0}}" src="{{imageUrl}}icon120.png?t={{Timestamp}}"></image> |
||||||
|
<image |
||||||
|
class="icon" |
||||||
|
wx:elif="{{item.change < 0}}" |
||||||
|
src="{{imageUrl}}{{theme === 'DRUG' ? 'icon132' : 'icon118'}}.png?t={{Timestamp}}" |
||||||
|
></image> |
||||||
|
</view> |
||||||
|
<view wx:if="{{item.recordStatus === 1}}" class="status">{{item.statusText}}</view> |
||||||
|
</view> |
||||||
|
<view class="row"> |
||||||
|
<view class="col"> |
||||||
|
<view class="c-name">激素用量</view> |
||||||
|
<view class="c-content" wx:if="{{item.oralHormoneDosageMg}}">{{item.oralHormoneDosageMg}}mg/天</view> |
||||||
|
<view class="c-content" wx:else>--</view> |
||||||
|
</view> |
||||||
|
<view class="col"> |
||||||
|
<view class="c-name">下次复诊时间</view> |
||||||
|
<view class="c-content">{{item.nextVisitDate || '--'}}</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<pagination pagination="{{pagination}}" customEmpty emptyTitle="暂无就诊记录"> |
||||||
|
<view class="empty-text" slot>暂无就诊记录</view> |
||||||
|
</pagination> |
||||||
|
</view> |
||||||
|
<view class="page-footer" bind:tap="handleAdd"> |
||||||
|
<view class="btn">新增就诊记录</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 563 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 1008 B |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 896 B |
|
After Width: | Height: | Size: 461 B |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 372 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |