@ -0,0 +1,310 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,6 +1,9 @@
|
||||
{ |
||||
"navigationStyle": "custom", |
||||
"navigationBarTitleText": "任务列表", |
||||
"usingComponents": { |
||||
"pagination": "/components/pagination/index", |
||||
"van-divider": "@vant/weapp/divider/index", |
||||
"van-loading": "@vant/weapp/loading/index", |
||||
"navBar": "/components/navBar/navBar" |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,9 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,7 @@
|
||||
{ |
||||
"navigationBarTitleText": "就诊记录详情页", |
||||
"navigationBarBackgroundColor": "#F6F6F6", |
||||
"usingComponents": { |
||||
"van-icon": "@vant/weapp/icon/index" |
||||
} |
||||
} |
||||
@ -0,0 +1,189 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 |