Browse Source

Merge branch 'dev2.0' into dev

dev
kola-web 2 weeks ago
parent
commit
841ed552ed
  1. 310
      .trae/documents/igg4-tracking-implementation-plan.md
  2. 1419
      doc/api.md
  3. 572
      doc/api_doctor_msg_task.md
  4. 216
      doc/track.md
  5. 60
      project.private.config.json
  6. 2
      src/api/request.ts
  7. 14
      src/app.json
  8. 2
      src/app.ts
  9. 126
      src/components/toast/index.scss
  10. 2
      src/components/toast/index.ts
  11. 33
      src/components/toast/index.wxml
  12. 49
      src/custom-tab-bar/index.scss
  13. 22
      src/custom-tab-bar/index.ts
  14. 13
      src/custom-tab-bar/index.wxml
  15. 8
      src/doctor/pages/d_caseReport/index.json
  16. 72
      src/doctor/pages/d_caseReport/index.scss
  17. 58
      src/doctor/pages/d_caseReport/index.ts
  18. 31
      src/doctor/pages/d_caseReport/index.wxml
  19. 4
      src/doctor/pages/d_changeDoctor/index.ts
  20. 143
      src/doctor/pages/d_createTask/index.scss
  21. 179
      src/doctor/pages/d_createTask/index.ts
  22. 253
      src/doctor/pages/d_createTask/index.wxml
  23. 1
      src/doctor/pages/d_customExport/index.ts
  24. 4
      src/doctor/pages/d_home/index.ts
  25. 20
      src/doctor/pages/d_interactive/index.scss
  26. 8
      src/doctor/pages/d_interactive/index.ts
  27. 17
      src/doctor/pages/d_interactive/index.wxml
  28. 2
      src/doctor/pages/d_interactiveDoctor/index.ts
  29. 2
      src/doctor/pages/d_invite/index.ts
  30. 1
      src/doctor/pages/d_my/index.ts
  31. 25
      src/doctor/pages/d_patient/index.scss
  32. 3
      src/doctor/pages/d_patient/index.ts
  33. 11
      src/doctor/pages/d_patient/index.wxml
  34. 4
      src/doctor/pages/d_patientDetail/index.json
  35. 316
      src/doctor/pages/d_patientDetail/index.scss
  36. 375
      src/doctor/pages/d_patientDetail/index.ts
  37. 137
      src/doctor/pages/d_patientDetail/index.wxml
  38. 27
      src/doctor/pages/d_taskDetail/index.scss
  39. 88
      src/doctor/pages/d_taskDetail/index.ts
  40. 105
      src/doctor/pages/d_taskDetail/index.wxml
  41. 5
      src/doctor/pages/d_taskList/index.json
  42. 63
      src/doctor/pages/d_taskList/index.scss
  43. 74
      src/doctor/pages/d_taskList/index.ts
  44. 113
      src/doctor/pages/d_taskList/index.wxml
  45. 1
      src/doctor/pages/d_userInfo/index.ts
  46. 9
      src/gift/pages/record/index.json
  47. 607
      src/gift/pages/record/index.scss
  48. 466
      src/gift/pages/record/index.ts
  49. 418
      src/gift/pages/record/index.wxml
  50. 7
      src/gift/pages/recordDetail/index.json
  51. 189
      src/gift/pages/recordDetail/index.scss
  52. 158
      src/gift/pages/recordDetail/index.ts
  53. 68
      src/gift/pages/recordDetail/index.wxml
  54. 10
      src/gift/pages/recordList/index.json
  55. 305
      src/gift/pages/recordList/index.scss
  56. 351
      src/gift/pages/recordList/index.ts
  57. 99
      src/gift/pages/recordList/index.wxml
  58. BIN
      src/images/avatar1.png
  59. BIN
      src/images/bg38.png
  60. BIN
      src/images/bg39.png
  61. BIN
      src/images/bg40.png
  62. BIN
      src/images/bg41.png
  63. BIN
      src/images/bg42.png
  64. BIN
      src/images/bg43.png
  65. BIN
      src/images/bg44.png
  66. BIN
      src/images/icon108.png
  67. BIN
      src/images/icon109.png
  68. BIN
      src/images/icon110.png
  69. BIN
      src/images/icon111.png
  70. BIN
      src/images/icon112.png
  71. BIN
      src/images/icon113.png
  72. BIN
      src/images/icon114.png
  73. BIN
      src/images/icon115.png
  74. BIN
      src/images/icon116.png
  75. BIN
      src/images/icon117.png
  76. BIN
      src/images/icon118.png
  77. BIN
      src/images/icon119.png
  78. BIN
      src/images/icon120.png
  79. BIN
      src/images/icon121.png
  80. BIN
      src/images/icon122.png
  81. BIN
      src/images/icon123.png
  82. BIN
      src/images/icon124.png
  83. BIN
      src/images/icon125.png
  84. BIN
      src/images/icon126.png
  85. BIN
      src/images/icon127.png
  86. BIN
      src/images/icon128.png
  87. BIN
      src/images/icon129.png
  88. BIN
      src/images/icon130.png
  89. BIN
      src/images/icon131.png
  90. BIN
      src/images/icon132.png
  91. BIN
      src/images/icon133.png
  92. BIN
      src/images/icon134.png
  93. BIN
      src/images/icon135.png
  94. BIN
      src/images/icon136.png
  95. BIN
      src/images/icon137.png
  96. BIN
      src/images/icon138.png
  97. BIN
      src/images/icon139.png
  98. BIN
      src/images/icon140.png
  99. BIN
      src/images/icon141.png
  100. BIN
      src/images/icon142.png
  101. Some files were not shown because too many files have changed in this diff Show More

310
.trae/documents/igg4-tracking-implementation-plan.md

@ -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

1419
doc/api.md

File diff suppressed because it is too large Load Diff

572
doc/api_doctor_msg_task.md

@ -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)
```

216
doc/track.md

@ -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` | 切换医生选择 |

60
project.private.config.json

@ -22,11 +22,60 @@
"miniprogram": { "miniprogram": {
"list": [ "list": [
{ {
"name": "就诊记录",
"pathName": "gift/pages/record/index",
"query": "id=15",
"scene": null,
"launchMode": "default"
},
{
"name": "医生-患者详情",
"pathName": "doctor/pages/d_patientDetail/index",
"query": "id=67",
"launchMode": "default",
"scene": null
},
{
"name": "病例报告",
"pathName": "pages/caseReport/index",
"query": "name=门诊病历及处方",
"launchMode": "default",
"scene": null
},
{
"name": "就诊记录详情",
"pathName": "gift/pages/recordDetail/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "就诊记录列表页",
"pathName": "gift/pages/recordList/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "医生-新建消息",
"pathName": "doctor/pages/d_createTask/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "医生-任务列表",
"pathName": "doctor/pages/d_taskList/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "患者-问题反馈", "name": "患者-问题反馈",
"pathName": "pages/feedback/index", "pathName": "pages/feedback/index",
"query": "", "query": "",
"scene": null, "launchMode": "default",
"launchMode": "default" "scene": null
}, },
{ {
"name": "患者-更改手机号", "name": "患者-更改手机号",
@ -134,13 +183,6 @@
"scene": null "scene": null
}, },
{ {
"name": "医生-患者详情",
"pathName": "doctor/pages/d_patientDetail/index",
"query": "id=1313",
"launchMode": "default",
"scene": null
},
{
"name": "医生-患者列表", "name": "医生-患者列表",
"pathName": "doctor/pages/d_patient/index", "pathName": "doctor/pages/d_patient/index",
"query": "", "query": "",

2
src/api/request.ts

@ -20,7 +20,7 @@ export const request = function (
...header, ...header,
}, },
url: gUrl + url, url: gUrl + url,
method: method, method,
data: { data: {
loginState: getApp().globalData.loginState, loginState: getApp().globalData.loginState,
...(data as object), ...(data as object),

14
src/app.json

@ -47,9 +47,9 @@
"pages/webview/index", "pages/webview/index",
"pages/infusionCenter/index", "pages/infusionCenter/index",
"pages/interactivePatient/index", "pages/interactivePatient/index",
"pages/referral/index",
"pages/qaForm/index", "pages/qaForm/index",
"pages/qaFormDetail/index", "pages/qaFormDetail/index",
"pages/caseReport/index",
"pages/feedback/index" "pages/feedback/index"
], ],
"subpackages": [ "subpackages": [
@ -64,7 +64,10 @@
"pages/myHealthRecordChart/index", "pages/myHealthRecordChart/index",
"pages/cutaneous/index", "pages/cutaneous/index",
"pages/cutaneousDetail/index", "pages/cutaneousDetail/index",
"pages/cutaneousVideo/index" "pages/cutaneousVideo/index",
"pages/record/index",
"pages/recordList/index",
"pages/recordDetail/index"
] ]
}, },
{ {
@ -91,7 +94,8 @@
"pages/d_patientList/index", "pages/d_patientList/index",
"pages/d_transfer/index", "pages/d_transfer/index",
"pages/d_transferLog/index", "pages/d_transferLog/index",
"pages/d_transferDetail/index" "pages/d_transferDetail/index",
"pages/d_caseReport/index"
] ]
}, },
{ {
@ -120,10 +124,6 @@
"text": "知识" "text": "知识"
}, },
{ {
"pagePath": "pages/live/index",
"text": "活动"
},
{
"pagePath": "pages/my/index", "pagePath": "pages/my/index",
"text": "我的" "text": "我的"
} }

2
src/app.ts

@ -411,7 +411,7 @@ App<IAppOption>({
callback(res) callback(res)
}) })
}, },
mpBehavior(data: { PageName: string; doctor?: boolean }) { mpBehavior(data: { PageName: string; doctor?: boolean; detailId?: string }) {
let url = '?r=igg4/mp-behavior/add' let url = '?r=igg4/mp-behavior/add'
if (data.doctor) { if (data.doctor) {
url = '?r=igg4/doctor/mp-behavior/add' url = '?r=igg4/doctor/mp-behavior/add'

126
src/components/toast/index.scss

@ -2451,7 +2451,7 @@
width: 670rpx; width: 670rpx;
box-sizing: border-box; box-sizing: border-box;
border-radius: 24rpx; border-radius: 24rpx;
background: linear-gradient(180deg, #E6F4FF 0%, #FFFFFF 22.89%, #FFFFFF 100%); background: linear-gradient(180deg, #e6f4ff 0%, #ffffff 22.89%, #ffffff 100%);
.content { .content {
font-size: 36rpx; font-size: 36rpx;
color: rgba(34, 34, 34, 1); color: rgba(34, 34, 34, 1);
@ -2477,21 +2477,29 @@
} }
.popup-public-toast { .popup-public-toast {
.popup-container { .popup-container {
position: relative; padding: 226rpx 0;
padding: 360rpx 146rpx 0; width: 630rpx;
width: 590rpx; height: 864rpx;
height: 880rpx;
box-sizing: border-box; box-sizing: border-box;
.title {
font-size: 40rpx;
color: rgba(34, 34, 34, 1);
text-align: center;
}
.code { .code {
width: 286rpx; display: block;
height: 286rpx; width: 310rpx;
height: 310rpx;
margin: 20rpx auto 0;
border: 1px solid rgba(0, 0, 0, 0.03);
border-radius: 22rpx;
} }
} }
.close { .clsoe{
margin: 68rpx auto 0;
display: block; display: block;
width: 64rpx; margin: 52rpx auto 0;
height: 64rpx; width: 84rpx;
height: 84rpx;
} }
} }
@ -3621,6 +3629,102 @@
} }
} }
.popup-no-write {
.badge {
position: relative;
z-index: 1;
margin: 0 auto -90rpx;
display: block;
width: 204rpx;
height: 160rpx;
}
.popup-container {
padding: 144rpx 0 0;
width: 540rpx;
box-sizing: border-box;
background-color: #fff;
border-radius: 24rpx;
box-shadow: inset 0rpx -1rpx 0rpx 0rpx #e5e6eb;
.title {
padding-bottom: 48rpx;
font-size: 36rpx;
color: #222222;
font-weight: bold;
text-align: center;
}
.footer {
border-top: 1px solid #eaebef;
display: flex;
.btn1,
.btn2 {
flex: 1;
padding: 22rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
.btn1 {
border-right: 1px solid #eaebef;
color: #212120;
}
.btn2 {
color: #0eb66d;
}
}
}
&.drug {
.footer {
.btn2 {
color: rgba(89, 86, 233, 1);
}
}
}
}
.popup-one-write {
.badge {
position: relative;
z-index: 1;
margin: 0 auto -90rpx;
display: block;
width: 204rpx;
height: 160rpx;
}
.popup-container {
padding: 116rpx 0 32rpx;
width: 550rpx;
box-sizing: border-box;
background-color: #fff;
border-radius: 24rpx;
box-shadow: inset 0rpx -1rpx 0rpx 0rpx #e5e6eb;
.title {
padding-bottom: 48rpx;
font-size: 36rpx;
color: #222222;
font-weight: bold;
text-align: center;
}
.btn {
margin: 0 auto;
width: 486rpx;
height: 96rpx;
background: linear-gradient(86deg, #0eb66d 0%, #00d277 100%);
border-radius: 16rpx 16rpx 16rpx 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #ffffff;
}
}
&.drug {
.btn {
background: linear-gradient(274deg, #5956e9 0%, #b384f4 100%);
}
}
}
.close { .close {
margin: 48rpx auto 0; margin: 48rpx auto 0;
display: block; display: block;

2
src/components/toast/index.ts

@ -42,7 +42,7 @@ Component({
if (['storyLead', 'storyStar', 'storyShare'].includes(this.data.type)) { if (['storyLead', 'storyStar', 'storyShare'].includes(this.data.type)) {
this.getSettingInfo() this.getSettingInfo()
} }
if (this.data.type === 'feedback' || this.data.type === 'doctor-feedback') { if (this.data.type === 'feedback' || this.data.type === 'doctor-feedback' || this.data.type === 'public-toast') {
this.getFeedbackCode() this.getFeedbackCode()
} }
if (this.data.type === 'follow') { if (this.data.type === 'follow') {

33
src/components/toast/index.wxml

@ -718,16 +718,20 @@
bind:tap="handleCancel" bind:tap="handleCancel"
></image> ></image>
</view> </view>
<view class="popup-public-toast" wx:if="{{type == 'public-toast'}}"> <view class="popup-public-toast" wx:if="{{type==='public-toast'}}">
<view <view
class="popup-container" class="popup-container"
style="background: url({{imageUrl}}/5/public-bg1.png?t={{Timestamp}}) no-repeat top center/100%" style="background:url('{{imageUrl}}{{theme === 'DRUG' ? 'bg44' : 'bg43'}}.png?t={{Timestamp}}') no-repeat top center/100%"
> >
<image class="code" src="{{imageUrl}}5/code.png?t={{Timestamp}}" show-menu-by-longpress></image> <view class="title">
关注「IgG4愈见昕生」公众号
<view></view>
重要提醒不丢失
</view>
<image class="code" src="{{feedbackQrCode}}" show-menu-by-longpress></image>
</view> </view>
<image <image
class="close" class="close"
bind:tap="handleCancel"
src="{{imageUrl}}2/close.png?t={{Timestamp}}" src="{{imageUrl}}2/close.png?t={{Timestamp}}"
bind:tap="handleCancel" bind:tap="handleCancel"
></image> ></image>
@ -892,7 +896,9 @@
<text class="link" catch:tap="routerTo" data-url="/doc/pages/privacyAgreement/index?active=1">《用户协议》</text> <text class="link" catch:tap="routerTo" data-url="/doc/pages/privacyAgreement/index?active=1">《用户协议》</text>
<text class="link" catch:tap="routerTo" data-url="/doc/pages/privacyAgreement/index">《隐私政策》</text> <text class="link" catch:tap="routerTo" data-url="/doc/pages/privacyAgreement/index">《隐私政策》</text>
<view></view> <view></view>
<text class="link" catch:tap="routerTo" data-url="/doc/pages/privacyAgreement/index?active=2">《个人信息共享知情同意书》</text> <text class="link" catch:tap="routerTo" data-url="/doc/pages/privacyAgreement/index?active=2">
《个人信息共享知情同意书》
</text>
</view> </view>
<view class="footer"> <view class="footer">
@ -1092,6 +1098,23 @@
<image class="code" src="{{followQrCode}}" show-menu-by-longpress></image> <image class="code" src="{{followQrCode}}" show-menu-by-longpress></image>
</view> </view>
</view> </view>
<view class="popup-no-write {{theme === 'DRUG' && 'drug'}}" wx:if="{{type==='noWrite'}}">
<image class="badge" src="{{imageUrl}}{{theme === 'DRUG' ? 'icon140' : 'icon125'}}.png?t={{Timestamp}}"></image>
<view class="popup-container">
<view class="title">您有填写信息还未提交</view>
<view class="footer">
<view class="btn1" bind:tap="handleCancel">退出</view>
<view class="btn2" bind:tap="handleOk">提交</view>
</view>
</view>
</view>
<view class="popup-one-write {{theme === 'DRUG' && 'drug'}}" wx:if="{{type==='oneWrite'}}">
<image class="badge" src="{{imageUrl}}{{theme === 'DRUG' ? 'icon140' : 'icon125'}}.png?t={{Timestamp}}"></image>
<view class="popup-container">
<view class="title">请至少完成一项填写</view>
<view class="btn" bind:tap="handleCancel">继续</view>
</view>
</view>
<image <image
wx:if="{{params.close}}" wx:if="{{params.close}}"

49
src/custom-tab-bar/index.scss

@ -1,34 +1,33 @@
/* custom-tab-bar/index.wxss */ /* custom-tab-bar/index.wxss */
.circle { .tab-item {
display: block; flex: 1;
margin: 0 auto; width: 110rpx;
margin-top: -60rpx;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
border: 3px solid #fff;
background: linear-gradient(10deg, #b384f4 0%, #5956e9 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
.icon { .icon {
width: 60rpx; width: 60rpx;
height: 60rpx; height: 60rpx;
} }
.name {
margin-top: 4rpx;
font-size: 24rpx;
color: #cccccc;
text-align: center;
&.active {
color: rgba(14, 182, 109, 1);
} }
&.drug-active {
.tab-item { color: rgba(89, 86, 233, 1);
.icon {
width: 60rpx;
height: 60rpx;
} }
}
}
.custom-tab-item {
flex-shrink: 0;
text-align: center;
.name { .name {
margin-top: 4rpx; margin-top: 4rpx;
font-size: 24rpx; font-size: 24rpx;
color: #cccccc; color: #cccccc;
text-align: center;
&.active { &.active {
color: rgba(14, 182, 109, 1); color: rgba(14, 182, 109, 1);
} }
@ -37,3 +36,15 @@
} }
} }
} }
.circle {
position: relative;
z-index: 1;
display: block;
margin: -62rpx auto 0;
width: 116rpx;
height: 116rpx;
.icon {
width: 100%;
height: 100%;
}
}

22
src/custom-tab-bar/index.ts

@ -20,17 +20,17 @@ Component({
icon: 'story', icon: 'story',
iconActive: 'story-active', iconActive: 'story-active',
}, },
// { {
// pagePath: '/pages/certReslove/index', pagePath: '/gift/pages/recordList/index',
// text: '突眼日记', custom: true,
// custom: true, text: '就诊记录',
// }, },
// { {
// pagePath: '/pages/live/index', pagePath: '/pages/qaFormDetail/index',
// text: '活动', text: '健康档案',
// icon: 'gift', icon: 'gift',
// iconActive: 'gift-active', iconActive: 'gift-active',
// }, },
{ {
pagePath: '/pages/my/index', pagePath: '/pages/my/index',
text: '我的', text: '我的',

13
src/custom-tab-bar/index.wxml

@ -1,10 +1,15 @@
<van-tabbar active="{{ active }}" active-color="{{theme === 'DRUG' ? 'rgba(89, 86, 233, 1)' : 'rgba(14, 182, 109, 1)'}}" bind:change="onChange" inactive-color="#CCCCCC"> <van-tabbar
active="{{ active }}"
active-color="{{theme === 'DRUG' ? 'rgba(89, 86, 233, 1)' : 'rgba(14, 182, 109, 1)'}}"
bind:change="onChange"
inactive-color="#CCCCCC"
>
<block wx:for="{{list}}" wx:key="*this"> <block wx:for="{{list}}" wx:key="*this">
<view class="tab-item" wx:if="{{item.custom && theme === 'DRUG'}}" bind:tap="handleNav" data-index="{{index}}"> <view class="custom-tab-item" wx:if="{{item.custom}}" bind:tap="handleNav" data-index="{{index}}">
<view class="circle"> <view class="circle">
<image class="icon" src="/images/icon64.png"></image> <image class="icon" src="{{imageUrl}}/tabbar/{{theme === 'DRUG' ? 'drug-add' : 'add'}}.png"></image>
</view> </view>
<view class="name">用药提醒</view> <view class="name">就诊记录</view>
</view> </view>
<van-tabbar-item <van-tabbar-item
wx:elif="{{!item.custom}}" wx:elif="{{!item.custom}}"

8
src/doctor/pages/d_caseReport/index.json

@ -0,0 +1,8 @@
{
"navigationBarTitleText": "病例报告",
"usingComponents": {
"pagination": "/components/pagination/index",
"van-divider": "@vant/weapp/divider/index",
"van-loading": "@vant/weapp/loading/index"
}
}

72
src/doctor/pages/d_caseReport/index.scss

@ -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);
}
}
}

58
src/doctor/pages/d_caseReport/index.ts

@ -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 {}

31
src/doctor/pages/d_caseReport/index.wxml

@ -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>

4
src/doctor/pages/d_changeDoctor/index.ts

@ -14,6 +14,9 @@ Page({
timer = null timer = null
this.setData({ codeText: '发送验证码' }) this.setData({ codeText: '发送验证码' })
} }
app.waitLogin().then(() => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorChangeDoctor' })
})
}, },
onUnload() { onUnload() {
if (timer) { if (timer) {
@ -67,6 +70,7 @@ Page({
}) })
}, },
handleSubmit() { handleSubmit() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorChangeDoctorSelect' })
if (!this.data.mobile) { if (!this.data.mobile) {
wx.showToast({ wx.showToast({
icon: 'none', icon: 'none',

143
src/doctor/pages/d_createTask/index.scss

@ -18,11 +18,13 @@ page {
height: 48rpx; height: 48rpx;
text-align: center; text-align: center;
font-size: 32rpx; font-size: 32rpx;
color: rgba(103, 186, 202, 1); color: rgba(39, 66, 164, 1);
line-height: 48rpx; line-height: 48rpx;
border: 8rpx solid #85d4d5;
border-radius: 50%; border-radius: 50%;
background-color: #85d4d5; background-color: rgba(255, 255, 255, 1);
border: 8rpx solid rgba(255, 255, 255, 0);
background-clip: content-box;
opacity: 0.8;
} }
.order-end { .order-end {
width: 48rpx; width: 48rpx;
@ -36,6 +38,8 @@ page {
&.active { &.active {
.order { .order {
background-color: #fff; background-color: #fff;
border: 8rpx solid rgba(255, 255, 255, 0.19);
opacity: 1;
} }
.name { .name {
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
@ -45,9 +49,9 @@ page {
.line { .line {
margin: 30rpx -10rpx 0; margin: 30rpx -10rpx 0;
flex: 1; flex: 1;
border-top: 1px dashed #a3d6d7; border-top: 1px dashed rgba(255, 255, 255, 1);
&.active { &.active {
border-color: #fff; border-color: rgba(255, 255, 255, 1);
} }
} }
} }
@ -55,37 +59,68 @@ page {
margin-top: 24rpx; margin-top: 24rpx;
text-align: center; text-align: center;
font-size: 28rpx; font-size: 28rpx;
color: #ffffff; color: rgba(255, 255, 255, 0.8);
line-height: 40rpx; line-height: 40rpx;
} }
.step1 { .step1 {
margin: 32rpx 30rpx; margin: 32rpx 30rpx;
.form {
padding: 30rpx; padding: 30rpx;
margin-bottom: 24rpx;
border-radius: 24rpx; border-radius: 24rpx;
background-color: #fff; background-color: #fff;
.form {
.form-item { .form-item {
margin-bottom: 48rpx; margin-bottom: 48rpx;
&:last-of-type {
margin-bottom: 0;
}
&.vertical {
display: flex;
align-items: center;
justify-content: space-between;
}
.f-title { .f-title {
font-size: 36rpx; font-size: 36rpx;
color: rgba(40, 48, 49, 1); color: rgba(40, 48, 49, 1);
font-weight: bold; font-weight: bold;
} }
.checkbox {
.wx-checkbox-input {
width: 40rpx;
height: 40rpx;
&.wx-checkbox-input-checked {
background: rgba(22, 121, 203, 1);
border-color: rgba(22, 121, 203, 1);
&::before {
color: #fff;
}
}
}
}
.select { .select {
margin-top: 24rpx; margin-top: 24rpx;
display: grid; display: flex;
grid-template-columns: repeat(3, 1fr); flex-wrap: wrap;
gap: 16rpx; gap: 16rpx;
.s-item { .s-item {
padding: 18rpx 0; flex: 0 0 auto;
min-width: calc((100% - 44rpx) / 3);
padding: 22rpx 12rpx;
text-align: center; text-align: center;
border-radius: 12rpx; border-radius: 12rpx;
font-size: 28rpx; font-size: 32rpx;
color: rgba(103, 113, 114, 1); color: rgba(34, 34, 34, 0.6);
background: #f2f4f5; background: rgba(248, 248, 248, 1);
border: 1px solid rgba(248, 248, 248, 1);
box-sizing: border-box;
&.active { &.active {
color: #fff; color: rgba(22, 121, 203, 1);
background-color: rgba(103, 186, 202, 1); background-color: rgba(22, 121, 203, 0.1);
border-color: rgba(22, 121, 203, 1);
}
&:nth-of-type(4),
&:nth-of-type(5) {
width: 100%;
} }
} }
} }
@ -113,12 +148,10 @@ page {
.date { .date {
margin-top: 24rpx; margin-top: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
.picker { .picker {
flex: 1; flex: 1;
margin-top: 32rpx;
.d-item { .d-item {
flex: 1; flex: 1;
@ -126,17 +159,17 @@ page {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 18rpx 32rpx; padding: 18rpx 32rpx;
background: #f7f8f9; background: rgba(248, 248, 248, 1);
border-radius: 12rpx 12rpx 12rpx 12rpx; border-radius: 12rpx 12rpx 12rpx 12rpx;
.icon { .icon {
width: 32rpx; width: 40rpx;
height: 32rpx; height: 40rpx;
} }
.content { .content {
font-size: 32rpx; font-size: 32rpx;
color: #677172; color: rgba(34, 34, 34, 1);
&:empty::after { &:empty::after {
content: attr(data-place); content: attr(data-place);
@ -169,7 +202,7 @@ page {
align-items: baseline; align-items: baseline;
.title { .title {
font-size: 36rpx; font-size: 36rpx;
color: #283031; color: rgba(34, 34, 34, 1);
font-weight: bold; font-weight: bold;
} }
.tip { .tip {
@ -188,7 +221,7 @@ page {
align-items: center; align-items: center;
gap: 16rpx; gap: 16rpx;
font-size: 32rpx; font-size: 32rpx;
color: #283031; color: rgba(34, 34, 34, 1);
line-height: 1; line-height: 1;
.circle { .circle {
flex-shrink: 0; flex-shrink: 0;
@ -196,10 +229,10 @@ page {
height: 32rpx; height: 32rpx;
background: #ffffff; background: #ffffff;
border-radius: 50%; border-radius: 50%;
border: 2rpx solid #d4d9da; border: 2rpx solid rgba(212, 217, 218, 1);
&.active { &.active {
position: relative; position: relative;
border-color: #62bed0; border-color: rgba(22, 121, 203, 1);
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
@ -209,7 +242,7 @@ page {
width: 18rpx; width: 18rpx;
height: 18rpx; height: 18rpx;
border-radius: 50%; border-radius: 50%;
background-color: #62bed0; background-color: rgba(22, 121, 203, 1);
} }
} }
} }
@ -224,16 +257,16 @@ page {
flex: 1; flex: 1;
padding: 18rpx; padding: 18rpx;
font-size: 32rpx; font-size: 32rpx;
color: #677172; color: rgba(34, 34, 34, 0.6);
text-align: center; text-align: center;
background: #f7f8f9; background: rgba(248, 248, 248, 1);
border-radius: 12rpx 12rpx 12rpx 12rpx; border-radius: 12rpx 12rpx 12rpx 12rpx;
border: 2rpx solid rgba(207, 212, 213, 0.56); border: 2rpx solid rgba(248, 248, 248, 1);
font-weight: bold; font-weight: bold;
&.active { &.active {
color: #67baca; color: rgba(22, 121, 203, 1);
border-color: #67baca; border-color: rgba(22, 121, 203, 1);
background: rgba(103, 186, 202, 0.1); background: rgba(22, 121, 203, 0.1);
} }
} }
} }
@ -251,9 +284,10 @@ page {
padding: 22rpx 0; padding: 22rpx 0;
font-size: 32rpx; font-size: 32rpx;
text-align: center; text-align: center;
color: rgba(34, 34, 34, 1);
&:empty::after { &:empty::after {
content: attr(data-place); content: attr(data-place);
color: #c2c9ca; color: rgba(34, 34, 34, 0.2);
} }
} }
} }
@ -262,8 +296,8 @@ page {
color: #677172; color: #677172;
} }
.icon { .icon {
width: 32rpx; width: 40rpx;
height: 32rpx; height: 40rpx;
} }
} }
} }
@ -284,13 +318,14 @@ page {
display: none; display: none;
} }
.switch-item { .switch-item {
flex: 1;
position: relative; position: relative;
text-align: center; text-align: center;
font-size: 32rpx; font-size: 32rpx;
color: rgba(103, 113, 114, 1); color: rgba(103, 113, 114, 1);
white-space: nowrap; white-space: nowrap;
&.active { &.active {
color: rgba(98, 190, 208, 1); color: rgba(22, 121, 203, 1);
&::after { &::after {
position: absolute; position: absolute;
bottom: -14rpx; bottom: -14rpx;
@ -299,8 +334,8 @@ page {
content: ''; content: '';
width: 54rpx; width: 54rpx;
height: 8rpx; height: 8rpx;
border-radius: 12rpx 12rpx 0 0; border-radius: 8rpx;
background: #62bed0; background: rgba(22, 121, 203, 1);
} }
} }
} }
@ -315,14 +350,16 @@ page {
transform: scale(0.8); transform: scale(0.8);
} }
.wrap { .wrap {
flex: 1;
margin-left: 14rpx; margin-left: 14rpx;
padding: 30rpx; padding: 30rpx;
box-shadow: 0rpx 8rpx 32rpx 0rpx rgba(40, 48, 49, 0.08); box-shadow: 0rpx 8rpx 32rpx 0rpx rgba(40, 48, 49, 0.04);
border-radius: 24rpx 24rpx 24rpx 24rpx; border-radius: 24rpx;
border: 2rpx solid #f8f9f9; border: 2rpx solid #f8f9f9;
.content { .content {
flex: 1;
font-size: 32rpx; font-size: 32rpx;
color: rgba(40, 48, 49, 1); color: rgba(34, 34, 34, 1);
} }
.rows { .rows {
.row { .row {
@ -362,17 +399,17 @@ page {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 32rpx; font-size: 32rpx;
color: rgba(40, 48, 49, 1); color: rgba(34, 34, 34, 1);
.resource { .resource {
margin-left: 26rpx; margin-left: 26rpx;
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
} }
} }
.clear { .clear {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 32rpx; font-size: 32rpx;
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
gap: 12rpx; gap: 12rpx;
.icon { .icon {
width: 36rpx; width: 36rpx;
@ -388,27 +425,27 @@ page {
gap: 26rpx; gap: 26rpx;
.cancel { .cancel {
flex: 1; flex: 1;
height: 84rpx; height: 92rpx;
font-size: 36rpx; font-size: 36rpx;
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #ffffff; background: #ffffff;
border-radius: 98rpx 98rpx 98rpx 98rpx; border-radius: 12rpx;
border: 2rpx solid #67baca; border: 2rpx solid rgba(22, 121, 203, 1);
} }
.submit { .submit {
flex: 1; flex: 1;
height: 84rpx; height: 92rpx;
font-size: 36rpx; font-size: 36rpx;
color: #fff; color: #fff;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: rgba(103, 186, 202, 1); background: linear-gradient(96deg, #293b9f 0%, #1679cb 100%);
border-radius: 98rpx 98rpx 98rpx 98rpx; border-radius: 12rpx;
border: 2rpx solid #67baca; border: 2rpx solid transparent;
} }
} }
} }

179
src/doctor/pages/d_createTask/index.ts

@ -1,10 +1,11 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { filterColumns } from '@/utils/doctorPatientTask'
const licia = require('miniprogram-licia') const licia = require('miniprogram-licia')
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: { data: {
background: 'transparent',
step: 1, step: 1,
TemplateType: 1, TemplateType: 1,
@ -23,28 +24,24 @@ Page({
PatientNum: 0, //患者人数 PatientNum: 0, //患者人数
// form // form
Search: '', SelectAll: 0,
HasAdl: 0, ConfirmedIgg4: 0,
DoubleStandard: 0, MedicationType: [] as number[],
AdlTime: 0,
AdlScore: [] as any,
AdlScoreChange: [],
AdlScoreChangeType: 0,
AdlScoreChangeMax: '',
AdlScoreChangeMin: '',
SteroidDailyDose: [] as any,
SteroidDailyDoseChange: [] as any,
IsThisWeekAdl: 0,
PharyngealSymptoms: 0,
AntibodyType: 0,
ThyroidAbnormal: 0,
PreviousConvulsion: 0,
TraditionalImmunosuppressant: 0,
MedicalInsuranceType: 0,
BindStartDate: '', BindStartDate: '',
BindEndDate: '', BindEndDate: '',
...filterColumns, ConfirmedIgg4Column: [
{ id: 0, value: '全部' },
{ id: 1, value: '是' },
{ id: 2, value: '否' },
],
MedicationTypeColumn: [
{ id: 0, value: '全部' },
{ id: 2, value: '激素' },
{ id: 3, value: '免疫抑制剂' },
{ id: 4, value: '靶向CD19生物制剂(伊奈利珠单抗)' },
{ id: 5, value: '靶向CD20生物制剂(利妥昔单抗等)' },
],
}, },
onLoad() { onLoad() {
const SystemInfo = app.globalSystemInfo const SystemInfo = app.globalSystemInfo
@ -65,6 +62,7 @@ Page({
}) })
app.waitLogin().then(() => { app.waitLogin().then(() => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorCreateTask' })
if (this.data.step === 2) { if (this.data.step === 2) {
this.getTaskNum() this.getTaskNum()
} }
@ -104,34 +102,17 @@ Page({
if (typeof e === 'object') { if (typeof e === 'object') {
btn = e?.currentTarget?.dataset?.btn btn = e?.currentTarget?.dataset?.btn
} }
if (btn == 1) {
}
return wx return wx
.ajax({ .ajax({
method: 'GET', method: 'GET',
url: '?r=igg4/doctor/task/filter-patient-num', url: '?r=igg4/doctor/task/filter-patient-num',
data: { data: {
Search: this.data.Search, SelectAll: this.data.SelectAll,
HasAdl: this.data.HasAdl,
DoubleStandard: this.data.DoubleStandard,
AdlTime: this.data.AdlTime,
AdlScore: this.data.AdlScore.join('-'),
AdlScoreChange: this.data.AdlScoreChange.join('-'),
AdlScoreChangeType: this.data.AdlScoreChangeType,
AdlScoreChangeMax: this.data.AdlScoreChangeMax,
AdlScoreChangeMin: this.data.AdlScoreChangeMin,
SteroidDailyDose: this.data.SteroidDailyDose.join('-'),
SteroidDailyDoseChange: this.data.SteroidDailyDoseChange.join('-'),
IsThisWeekAdl: this.data.IsThisWeekAdl,
PharyngealSymptoms: this.data.PharyngealSymptoms,
AntibodyType: this.data.AntibodyType,
ThyroidAbnormal: this.data.ThyroidAbnormal,
PreviousConvulsion: this.data.PreviousConvulsion,
TraditionalImmunosuppressant: this.data.TraditionalImmunosuppressant,
MedicalInsuranceType: this.data.MedicalInsuranceType,
BindStartDate: this.data.BindStartDate, BindStartDate: this.data.BindStartDate,
BindEndDate: this.data.BindEndDate, BindEndDate: this.data.BindEndDate,
ConfirmedIgg4: this.data.ConfirmedIgg4,
MedicationType: this.data.MedicationType.join(','),
}, },
loading: true, loading: true,
}) })
@ -148,24 +129,9 @@ Page({
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
this.setData({ this.setData({
Search: '', SelectAll: 0,
HasAdl: 0, ConfirmedIgg4: 0,
DoubleStandard: 0, MedicationType: [],
AdlTime: 0,
AdlScore: [] as any,
AdlScoreChange: [],
AdlScoreChangeType: 0,
AdlScoreChangeMax: '',
AdlScoreChangeMin: '',
SteroidDailyDose: [] as any,
SteroidDailyDoseChange: [] as any,
IsThisWeekAdl: 0,
PharyngealSymptoms: 0,
AntibodyType: 0,
ThyroidAbnormal: 0,
PreviousConvulsion: 0,
TraditionalImmunosuppressant: 0,
MedicalInsuranceType: 0,
BindStartDate: '', BindStartDate: '',
BindEndDate: '', BindEndDate: '',
}) })
@ -184,14 +150,24 @@ Page({
handleCheckbox(e: any) { handleCheckbox(e: any) {
const { index, key } = e.currentTarget.dataset const { index, key } = e.currentTarget.dataset
const data = this.data[`${key}Column`] const data = this.data[`${key}Column`]
const clickedId = data[index].id
const oldValue = this.data[`${key}`] const oldValue = this.data[`${key}`]
const value = oldValue.includes(data[index].id)
? oldValue.filter((item: any) => item !== data[index].id)
: [...oldValue, data[index].id]
this.setData({ if (!clickedId) {
[`${key}`]: data[index].id ? value : [], // 点击"全部":清空其他选择
}) this.setData({ [`${key}`]: [] })
return
}
const value = oldValue.includes(clickedId)
? oldValue.filter((item: any) => item !== clickedId)
: [...oldValue.filter((item: any) => item !== 0), clickedId] // 选具体选项时移除"全部"
this.setData({ [`${key}`]: value })
},
handleSelectAll() {
this.setData({ SelectAll: this.data.SelectAll === 1 ? 0 : 1 })
this.getTaskNum()
}, },
handleDay(e) { handleDay(e) {
const { value } = e.currentTarget.dataset const { value } = e.currentTarget.dataset
@ -234,33 +210,12 @@ Page({
wx.navigateBack() wx.navigateBack()
}, },
async handleNext() { async handleNext() {
const anyValueKeys = [ const anyValueKeys = ['SelectAll', 'ConfirmedIgg4', 'MedicationType', 'BindStartDate', 'BindEndDate']
'Search',
'HasAdl',
'DoubleStandard',
'AdlTime',
'AdlScore',
'AdlScoreChange',
'AdlScoreChangeType',
'AdlScoreChangeMax',
'AdlScoreChangeMin',
'SteroidDailyDose',
'SteroidDailyDoseChange',
'IsThisWeekAdl',
'PharyngealSymptoms',
'AntibodyType',
'ThyroidAbnormal',
'PreviousConvulsion',
'TraditionalImmunosuppressant',
'MedicalInsuranceType',
'BindStartDate',
'BindEndDate',
]
const empty = anyValueKeys.every((item) => const empty = anyValueKeys.every((item) =>
Array.isArray(this.data[item]) ? licia.isEmpty(this.data[item]) : !this.data[item], Array.isArray(this.data[item]) ? licia.isEmpty(this.data[item]) : !this.data[item],
) )
const hasSelectAll = this.data.SelectAll === 1
if (empty) { if (empty && !hasSelectAll) {
wx.showToast({ wx.showToast({
icon: 'none', icon: 'none',
title: '请至少选择一个筛选条件', title: '请至少选择一个筛选条件',
@ -291,25 +246,11 @@ Page({
this.handleViewMp() this.handleViewMp()
}, },
handleSubmit() { handleSubmit() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorCreateTaskSubmit' })
const { const {
Search, SelectAll,
HasAdl, ConfirmedIgg4,
DoubleStandard, MedicationType,
AdlTime,
AdlScore,
AdlScoreChange,
AdlScoreChangeType,
AdlScoreChangeMax,
AdlScoreChangeMin,
SteroidDailyDose,
SteroidDailyDoseChange,
IsThisWeekAdl,
PharyngealSymptoms,
AntibodyType,
ThyroidAbnormal,
PreviousConvulsion,
TraditionalImmunosuppressant,
MedicalInsuranceType,
BindStartDate, BindStartDate,
BindEndDate, BindEndDate,
@ -348,30 +289,17 @@ Page({
title: '确认创建群发任务', title: '确认创建群发任务',
content: '将会按您创建的规则给所有符合条件患者发送消息', content: '将会按您创建的规则给所有符合条件患者发送消息',
confirmColor: 'rgba(22, 121, 203, 1)', confirmColor: 'rgba(22, 121, 203, 1)',
confirmText: '确认',
cancelText: '取消',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
const params = { const params = {
FilterCondition: { FilterCondition: {
Search, SelectAll,
HasAdl,
DoubleStandard,
AdlTime,
AdlScore: AdlScore.join('-'),
AdlScoreChange: AdlScoreChange.join('-'),
AdlScoreChangeType,
AdlScoreChangeMax,
AdlScoreChangeMin,
SteroidDailyDose: SteroidDailyDose.join('-'),
SteroidDailyDoseChange: SteroidDailyDoseChange.join('-'),
IsThisWeekAdl,
PharyngealSymptoms,
AntibodyType,
ThyroidAbnormal,
PreviousConvulsion,
TraditionalImmunosuppressant,
MedicalInsuranceType,
BindStartDate, BindStartDate,
BindEndDate, BindEndDate,
ConfirmedIgg4,
MedicationType: MedicationType.join(','),
}, },
PushTemplateId, PushTemplateId,
IsSendNow, IsSendNow,
@ -401,6 +329,5 @@ Page({
handleBack() { handleBack() {
wx.navigateBack() wx.navigateBack()
}, },
handleViewMp() { handleViewMp() {},
},
}) })

253
src/doctor/pages/d_createTask/index.wxml

@ -1,7 +1,7 @@
<wxs src="../d_patient/index.wxs" module="tools" /> <wxs src="../d_patient/index.wxs" module="tools" />
<view <view
class="page" class="page"
style="padding-top: {{bottom+5}}px;background: url({{imageUrl}}/doctor/create-task-bg.png?t={{Timestamp}}) no-repeat top center / 100% 626rpx" style="padding-top: {{bottom+5}}px;background: url('{{imageUrl}}bg38.png?t={{Timestamp}}') no-repeat top center / 100% 472rpx"
> >
<navBar <navBar
title="新建消息" title="新建消息"
@ -27,173 +27,55 @@
<view class="page-tip">您配置的内容会通过对话列表及公众号提醒通知患者</view> <view class="page-tip">您配置的内容会通过对话列表及公众号提醒通知患者</view>
<view class="step1" wx:if="{{step===1}}"> <view class="step1" wx:if="{{step===1}}">
<view class="form"> <view class="form">
<view class="form-item vertical">
<view class="f-title">选择全员</view>
<checkbox class="checkbox" checked="{{SelectAll === 1}}" bind:tap="handleSelectAll"></checkbox>
</view>
</view>
<view class="form">
<view class="form-item"> <view class="form-item">
<view class="f-title">绑定时间</view> <view class="f-title">绑定时间</view>
<view class="date"> <view class="date">
<picker class="picker" model:value="{{BindStartDate}}" end="{{BindEndDate}}" mode="date"> <picker class="picker" model:value="{{BindStartDate}}" end="{{BindEndDate}}" mode="date">
<view class="d-item"> <view class="d-item">
<view class="content" data-place="选择开始时间">{{BindStartDate}}</view> <view class="content" data-place="选择开始时间">{{BindStartDate}}</view>
<image class="icon" src="{{imageUrl}}3/icon-clock.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon110.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
<view class="sion">-</view>
<picker class="picker" model:value="{{BindEndDate}}" start="{{BindStartDate}}" mode="date"> <picker class="picker" model:value="{{BindEndDate}}" start="{{BindStartDate}}" mode="date">
<view class="d-item"> <view class="d-item">
<view class="content" data-place="选择结束时间">{{BindEndDate}}</view> <view class="content" data-place="选择结束时间">{{BindEndDate}}</view>
<image class="icon" src="{{imageUrl}}3/icon-clock.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon110.png?t={{Timestamp}}"></image>
</view> </view>
</picker> </picker>
</view> </view>
</view> </view>
<view class="form-item">
<view class="f-title">本周ADL测评</view>
<view class="select">
<view
class="s-item {{IsThisWeekAdl === item.id && 'active'}}"
wx:for="{{IsThisWeekAdlColumn}}"
data-index="{{index}}"
data-key="IsThisWeekAdl"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">是否有ADL测评</view>
<view class="select">
<view
class="s-item {{HasAdl === item.id && 'active'}}"
wx:for="{{HasAdlColumn}}"
data-index="{{index}}"
data-key="HasAdl"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">ADL测评间隔时长</view>
<view class="select">
<view
class="s-item {{AdlTime === item.id && 'active'}}"
wx:for="{{AdlTimeColumn}}"
data-index="{{index}}"
data-key="AdlTime"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">是否双达标</view>
<view class="select">
<view
class="s-item {{DoubleStandard === item.id && 'active'}}"
wx:for="{{DoubleStandardColumn}}"
data-index="{{index}}"
data-key="DoubleStandard"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">ADL评分(多选)</view>
<view class="select">
<view
class="s-item {{(tools.include(item.id,AdlScore) || AdlScore.length===0 && !item.id) && 'active'}}"
wx:for="{{AdlScoreColumn}}"
data-key="AdlScore"
data-index="{{index}}"
wx:key="id"
bind:tap="handleCheckbox"
>
{{item.value}}
</view>
</view>
</view> </view>
<view class="form">
<view class="form-item"> <view class="form-item">
<view class="f-title">ADL分数变化(多选)</view> <view class="f-title">确诊IgG4-RD</view>
<view class="select"> <view class="select">
<view <view
class="s-item {{(tools.include(item.id,AdlScoreChange) || AdlScoreChange.length===0 && !item.id) && 'active'}}" class="s-item {{ConfirmedIgg4 === item.id && 'active'}}"
wx:for="{{AdlScoreChangeColumn}}" wx:for="{{ConfirmedIgg4Column}}"
data-key="AdlScoreChange"
data-index="{{index}}"
wx:key="id"
bind:tap="handleCheckbox"
>
{{item.value}}
</view>
</view>
<view class="select" wx:if="{{tools.include(1,AdlScoreChange)}}">
<view
class="s-item {{AdlScoreChangeType === item.id && 'active'}}"
wx:for="{{AdlScoreChangeTypeColumn}}"
data-index="{{index}}" data-index="{{index}}"
data-key="AdlScoreChangeType" data-key="ConfirmedIgg4"
bind:tap="handleRadio" bind:tap="handleRadio"
wx:key="id" wx:key="id"
> >
{{item.value}} {{item.value}}
</view> </view>
</view> </view>
<view class="custom" wx:if="{{tools.include(1,AdlScoreChange) && AdlScoreChangeType===2}}">
<view class="c-item">
<input
type="number"
model:value="{{AdlScoreChangeMin}}"
maxlength="{{2}}"
class="input"
placeholder-class="input-place"
placeholder="请输入"
/>
</view>
-
<view class="c-item">
<input
type="number"
model:value="{{AdlScoreChangeMax}}"
maxlength="{{2}}"
class="input"
placeholder-class="input-place"
placeholder="请输入"
/>
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">激素用量(多选)</view>
<view class="select">
<view
class="s-item {{(tools.include(item.id,SteroidDailyDose) || SteroidDailyDose.length===0 && !item.id) && 'active'}}"
wx:for="{{SteroidDailyDoseColumn}}"
data-key="SteroidDailyDose"
data-index="{{index}}"
wx:key="id"
bind:tap="handleCheckbox"
>
{{item.value}}
</view>
</view> </view>
</view> </view>
<view class="form">
<view class="form-item"> <view class="form-item">
<view class="f-title">激素变化(多选)</view> <view class="f-title">正在使用的药物</view>
<view class="select"> <view class="select">
<view <view
class="s-item {{(tools.include(item.id,SteroidDailyDoseChange) || SteroidDailyDoseChange.length===0 && !item.id) && 'active'}}" class="s-item {{(item.id === 0 ? !MedicationType.length : tools.include(item.id, MedicationType)) && 'active'}}"
wx:for="{{SteroidDailyDoseChangeColumn}}" wx:for="{{MedicationTypeColumn}}"
data-key="SteroidDailyDoseChange" data-key="MedicationType"
data-index="{{index}}" data-index="{{index}}"
wx:key="id" wx:key="id"
bind:tap="handleCheckbox" bind:tap="handleCheckbox"
@ -202,103 +84,12 @@
</view> </view>
</view> </view>
</view> </view>
<view class="form-item">
<view class="f-title">延髓症状</view>
<view class="select">
<view
class="s-item {{PharyngealSymptoms === item.id && 'active'}}"
wx:for="{{PharyngealSymptomsColumn}}"
data-index="{{index}}"
data-key="PharyngealSymptoms"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">抗体类型</view>
<view class="select">
<view
class="s-item {{AntibodyType === item.id && 'active'}}"
wx:for="{{AntibodyTypeColumn}}"
data-index="{{index}}"
data-key="AntibodyType"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">胸腺异常</view>
<view class="select">
<view
class="s-item {{ThyroidAbnormal === item.id && 'active'}}"
wx:for="{{ThyroidAbnormalColumn}}"
data-index="{{index}}"
data-key="ThyroidAbnormal"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">既往发生危象</view>
<view class="select">
<view
class="s-item {{PreviousConvulsion === item.id && 'active'}}"
wx:for="{{PreviousConvulsionColumn}}"
data-index="{{index}}"
data-key="PreviousConvulsion"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">传统免疫抑制剂</view>
<view class="select">
<view
class="s-item {{TraditionalImmunosuppressant === item.id && 'active'}}"
wx:for="{{TraditionalImmunosuppressantColumn}}"
data-index="{{index}}"
data-key="TraditionalImmunosuppressant"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
<view class="form-item">
<view class="f-title">医保类型</view>
<view class="select">
<view
class="s-item {{MedicalInsuranceType === item.id && 'active'}}"
wx:for="{{MedicalInsuranceTypeColumn}}"
data-index="{{index}}"
data-key="MedicalInsuranceType"
bind:tap="handleRadio"
wx:key="id"
>
{{item.value}}
</view>
</view>
</view>
</view> </view>
</view> </view>
<view class="step2" wx:elif="{{step===2}}"> <view class="step2" wx:elif="{{step===2}}">
<view class="banner-date"> <view class="banner-date">
<view class="b-header"> <view class="b-header">
<view class="title">配置发送时间</view> <view class="title">配置发送时间</view>
<view class="tip">每天可创建1条群发任务</view>
</view> </view>
<view class="radios"> <view class="radios">
<view class="radio-item" bind:tap="handleSendDay" data-value="{{1}}"> <view class="radio-item" bind:tap="handleSendDay" data-value="{{1}}">
@ -326,7 +117,7 @@
> >
<view class="content" data-place="请选择发送时间">{{time}}</view> <view class="content" data-place="请选择发送时间">{{time}}</view>
</picker> </picker>
<image class="icon" src="{{imageUrl}}3/icon-clock.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon110.png?t={{Timestamp}}"></image>
</view> </view>
</block> </block>
</view> </view>
@ -351,7 +142,7 @@
data-id="{{item.Id}}" data-id="{{item.Id}}"
> >
<view class="radio"> <view class="radio">
<radio checked="{{false}}" checked="{{item.Id === PushTemplateId}}" color="#62BED0" /> <radio checked="{{false}}" checked="{{item.Id === PushTemplateId}}" color="#3379CA" />
</view> </view>
<view class="wrap"> <view class="wrap">
<view class="content">{{item.InteractMsgContent}}</view> <view class="content">{{item.InteractMsgContent}}</view>
@ -369,7 +160,7 @@
<view class="resource" bind:tap="getTaskNum" data-btn="1">刷新</view> <view class="resource" bind:tap="getTaskNum" data-btn="1">刷新</view>
</view> </view>
<view class="clear" bind:tap="handleReset"> <view class="clear" bind:tap="handleReset">
<image class="icon" src="{{imageUrl}}/doctor/icon-clear.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon111.png?t={{Timestamp}}"></image>
清空选项 清空选项
</view> </view>
</view> </view>

1
src/doctor/pages/d_customExport/index.ts

@ -1,4 +1,3 @@
import dayjs from 'dayjs'
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({

4
src/doctor/pages/d_home/index.ts

@ -35,6 +35,7 @@ Page({
} }
echarts = await require.async('../../../resource/components/echart/echarts.js') echarts = await require.async('../../../resource/components/echart/echarts.js')
app.waitLogin().then((_res) => { app.waitLogin().then((_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorHome' })
this.getDoctorInfo() this.getDoctorInfo()
this.getDetail() this.getDetail()
this.getList1_1() this.getList1_1()
@ -65,6 +66,7 @@ Page({
}) })
}, },
handleSelectYear1() { handleSelectYear1() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorHomeSelectYear' })
this.getList1_1() this.getList1_1()
}, },
getList1_1() { getList1_1() {
@ -203,6 +205,7 @@ Page({
} as any) } as any)
}, },
handlePatient(e) { handlePatient(e) {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorHomePatient' })
const { type, name } = e.currentTarget.dataset const { type, name } = e.currentTarget.dataset
wx.navigateTo({ wx.navigateTo({
@ -210,6 +213,7 @@ Page({
}) })
}, },
handleInvite() { handleInvite() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorHomeInvite' })
wx.navigateTo({ wx.navigateTo({
url: '/doctor/pages/d_invite/index', url: '/doctor/pages/d_invite/index',
}) })

20
src/doctor/pages/d_interactive/index.scss

@ -13,9 +13,12 @@ page {
.wrap { .wrap {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16rpx;
.search { .search {
flex: 1; flex: 1;
padding: 16rpx 32rpx; padding: 16rpx 32rpx;
height: 76rpx;
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16rpx; gap: 16rpx;
@ -34,6 +37,23 @@ page {
color: rgba(173, 179, 180, 1); color: rgba(173, 179, 180, 1);
} }
} }
.send {
flex-shrink: 0;
padding: 16rpx 28rpx;
background: linear-gradient(96deg, #293b9f 0%, #1679cb 100%);
border-radius: 48rpx;
font-size: 32rpx;
color: #ffffff;
display: flex;
align-items: center;
gap: 12rpx;
height: 76rpx;
box-sizing: border-box;
.icon {
width: 40rpx;
height: 40rpx;
}
}
.clear { .clear {
margin-left: 32rpx; margin-left: 32rpx;
width: 56rpx; width: 56rpx;

8
src/doctor/pages/d_interactive/index.ts

@ -14,6 +14,7 @@ Page({
}, },
onShow() { onShow() {
app.waitLogin().then((_res) => { app.waitLogin().then((_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorInteractive' })
this.getList() this.getList()
app.setTabbarNoticeMessage() app.setTabbarNoticeMessage()
}) })
@ -21,6 +22,7 @@ Page({
handleTapSearch() { handleTapSearch() {
}, },
handleSearch() { handleSearch() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorInteractiveSearch' })
this.setData({ this.setData({
list: [], list: [],
pagination: { pagination: {
@ -67,6 +69,7 @@ Page({
} }
}, },
handleDetail(e) { handleDetail(e) {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorInteractiveItem' })
const { index } = e.currentTarget.dataset const { index } = e.currentTarget.dataset
const { list } = this.data const { list } = this.data
const listItem: any = list[index] const listItem: any = list[index]
@ -107,4 +110,9 @@ Page({
}, },
}) })
}, },
handleSendGroup() {
wx.navigateTo({
url: '/doctor/pages/d_taskList/index',
})
},
}) })

17
src/doctor/pages/d_interactive/index.wxml

@ -7,26 +7,19 @@
type="text" type="text"
class="input" class="input"
placeholder-class="input-place" placeholder-class="input-place"
placeholder="可根据姓名、手机号搜索患者" placeholder="搜索姓名、手机号"
model:value="{{search}}" model:value="{{search}}"
confirm-type="search" confirm-type="search"
bindconfirm="handleSearch" bindconfirm="handleSearch"
bind:tap="handleTapSearch" bind:tap="handleTapSearch"
/> />
</view> </view>
<view class="send" bind:tap="handleSendGroup">
<image class="icon" src="{{imageUrl}}icon108.png?t={{Timestamp}}"></image>
群发
</view>
<!-- <image class="clear" src="{{imageUrl}}icon47.png?t={{Timestamp}}" bind:tap="hancleClaer"></image> --> <!-- <image class="clear" src="{{imageUrl}}icon47.png?t={{Timestamp}}" bind:tap="hancleClaer"></image> -->
</view> </view>
<!-- <view class="navbar"> -->
<!-- <view class="nav {{!active && 'active'}}" bind:tap="handleNav" data-active=""> -->
<!-- 全部 -->
<!-- <view class="dot" wx:if="{{unReadCount>0}}">{{unReadCount}}</view> -->
<!-- </view> -->
<!-- <view class="nav {{active===1 && 'active'}}" bind:tap="handleNav" data-active="{{1}}">已读</view> -->
<!-- <view class="nav {{active===2 && 'active'}}" bind:tap="handleNav" data-active="{{2}}"> -->
<!-- 未读 -->
<!-- <view class="dot" wx:if="{{unReadCount>0}}">{{unReadCount}}</view> -->
<!-- </view> -->
<!-- </view> -->
</view> </view>
<view class="list"> <view class="list">

2
src/doctor/pages/d_interactiveDoctor/index.ts

@ -66,6 +66,7 @@ Page({
}, },
onShow() { onShow() {
app.waitLogin().then((_res) => { app.waitLogin().then((_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorInteractiveDoctor', detailId: this.data.patientId })
this.getDoctorDetail() this.getDoctorDetail()
this.getPatientDetail() this.getPatientDetail()
this.getMessageList() this.getMessageList()
@ -332,6 +333,7 @@ Page({
} }
}, },
handleConfirm() { handleConfirm() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorInteractiveSend' })
const { customMessage } = this.data const { customMessage } = this.data
this.handleHideKeyboard() this.handleHideKeyboard()
this.handleSendCustomMessage(customMessage) this.handleSendCustomMessage(customMessage)

2
src/doctor/pages/d_invite/index.ts

@ -18,6 +18,7 @@ Page({
}) })
} }
app.waitLogin().then((_res) => { app.waitLogin().then((_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorInvite' })
wx.showLoading({ wx.showLoading({
title: '加载中', title: '加载中',
}) })
@ -57,6 +58,7 @@ Page({
}) })
}, },
handleDownload() { handleDownload() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorInviteSend' })
wx.showToast({ wx.showToast({
title: '请长按海报图片进行保存', title: '请长按海报图片进行保存',
icon: 'none', icon: 'none',

1
src/doctor/pages/d_my/index.ts

@ -20,6 +20,7 @@ Page({
}, },
onShow() { onShow() {
app.waitLogin().then((_res) => { app.waitLogin().then((_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorMy' })
this.getDetail() this.getDetail()
}) })
}, },

25
src/doctor/pages/d_patient/index.scss

@ -14,13 +14,20 @@ page {
z-index: 10; z-index: 10;
background-color: #fff; background-color: #fff;
.search-wrap {
display: flex;
align-items: center;
gap: 16rpx;
.search { .search {
flex: 1;
padding: 16rpx 32rpx; padding: 16rpx 32rpx;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16rpx; gap: 16rpx;
background: rgba(243, 243, 243, 1); background: rgba(243, 243, 243, 1);
border-radius: 140rpx 140rpx 140rpx 140rpx; border-radius: 140rpx 140rpx 140rpx 140rpx;
height: 76rpx;
box-sizing: border-box;
.icon { .icon {
width: 40rpx; width: 40rpx;
@ -36,6 +43,24 @@ page {
color: rgba(33, 33, 32, 0.3); color: rgba(33, 33, 32, 0.3);
} }
} }
.send {
flex-shrink: 0;
padding: 16rpx 28rpx;
background: linear-gradient(96deg, #293b9f 0%, #1679cb 100%);
border-radius: 48rpx;
font-size: 32rpx;
color: #ffffff;
display: flex;
align-items: center;
gap: 12rpx;
height: 76rpx;
box-sizing: border-box;
.icon {
width: 40rpx;
height: 40rpx;
}
}
}
.navbar { .navbar {
margin-top: 32rpx; margin-top: 32rpx;
display: flex; display: flex;

3
src/doctor/pages/d_patient/index.ts

@ -57,6 +57,7 @@ Page({
IsThisWeekAdl: Number(options.IsThisWeekAdl || ''), IsThisWeekAdl: Number(options.IsThisWeekAdl || ''),
}) })
app.waitLogin().then((_res) => { app.waitLogin().then((_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorPatient' })
this.getList() this.getList()
}) })
}, },
@ -105,6 +106,7 @@ Page({
this.handleSearch() this.handleSearch()
}, },
handleSearch() { handleSearch() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorPatientSearch' })
this.getList() this.getList()
}, },
getList(newPage = 1) { getList(newPage = 1) {
@ -180,6 +182,7 @@ Page({
}) })
}, },
handleDetail(e: any) { handleDetail(e: any) {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorPatientItem' })
const { index } = e.currentTarget.dataset const { index } = e.currentTarget.dataset
const item = this.data.list[index] const item = this.data.list[index]

11
src/doctor/pages/d_patient/index.wxml

@ -1,6 +1,7 @@
<wxs src="../../../utils/tools.wxs" module="tools" /> <wxs src="../../../utils/tools.wxs" module="tools" />
<view class="page"> <view class="page">
<view class="page-header"> <view class="page-header">
<view class="search-wrap">
<view class="search"> <view class="search">
<image class="icon" src="{{imageUrl}}icon43.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon43.png?t={{Timestamp}}"></image>
<input <input
@ -14,6 +15,11 @@
bind:tap="handleTapSearch" bind:tap="handleTapSearch"
/> />
</view> </view>
<view class="send" bind:tap="handleSendGroup">
<image class="icon" src="{{imageUrl}}icon108.png?t={{Timestamp}}"></image>
群发
</view>
</view>
<view class="navbar"> <view class="navbar">
<view class="nav {{active===0 && 'active'}}" bind:tap="handleNav" data-active="{{0}}">全部</view> <view class="nav {{active===0 && 'active'}}" bind:tap="handleNav" data-active="{{0}}">全部</view>
<view class="nav {{active===1 && 'active'}}" bind:tap="handleNav" data-active="{{1}}">确诊IgG4-RD</view> <view class="nav {{active===1 && 'active'}}" bind:tap="handleNav" data-active="{{1}}">确诊IgG4-RD</view>
@ -27,7 +33,10 @@
<view class="container"> <view class="container">
<view class="title"> <view class="title">
<view class="name">{{item.Name}}</view> <view class="name">{{item.Name}}</view>
<view class="disease" wx:if="{{item.DiagnosisTimeRange}}">病程:{{item.DiagnosisTimeRange == 1 ? '6个月以内' : item.DiagnosisTimeRange == 2 ? '6个月-3年' : '3年以上'}}</view> <view class="disease" wx:if="{{item.DiagnosisTimeRange}}">
病程:{{item.DiagnosisTimeRange == 1 ? '6个月以内' : item.DiagnosisTimeRange == 2 ? '6个月-3年' :
'3年以上'}}
</view>
</view> </view>
<view class="replay-date">邀约时间:{{tools.formatDate(item.InviteTime)}}</view> <view class="replay-date">邀约时间:{{tools.formatDate(item.InviteTime)}}</view>
<view class="remark">备注:{{item.DoctorRemark || '无'}}</view> <view class="remark">备注:{{item.DoctorRemark || '无'}}</view>

4
src/doctor/pages/d_patientDetail/index.json

@ -3,6 +3,8 @@
"usingComponents": { "usingComponents": {
"van-icon": "@vant/weapp/icon/index", "van-icon": "@vant/weapp/icon/index",
"van-popup": "@vant/weapp/popup/index", "van-popup": "@vant/weapp/popup/index",
"toast": "/components/toast/index" "toast": "/components/toast/index",
"van-sticky": "@vant/weapp/sticky/index",
"ec-canvas": "/components/ec-canvas/ec-canvas"
} }
} }

316
src/doctor/pages/d_patientDetail/index.scss

@ -1,4 +1,5 @@
page { page {
background-color: #fcfcfc;
padding-bottom: calc(140rpx + env(safe-area-inset-bottom)); padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
} }
.page { .page {
@ -6,6 +7,7 @@ page {
overflow-x: hidden; overflow-x: hidden;
padding-bottom: 200rpx; padding-bottom: 200rpx;
.banner { .banner {
position: relative;
margin: 40rpx 40rpx 0; margin: 40rpx 40rpx 0;
padding: 48rpx 32rpx; padding: 48rpx 32rpx;
display: flex; display: flex;
@ -13,6 +15,25 @@ page {
border-radius: 12rpx 12rpx 12rpx 12rpx; border-radius: 12rpx 12rpx 12rpx 12rpx;
border: 1px solid #f4f3f3; border: 1px solid #f4f3f3;
box-shadow: 0 4rpx 32rpx 0 rgba(0, 0, 0, 0.08); box-shadow: 0 4rpx 32rpx 0 rgba(0, 0, 0, 0.08);
.generate-pdf {
position: absolute;
right: 0;
top: 0;
padding: 0 24rpx;
height: 56rpx;
background: linear-gradient(96deg, #293b9f 0%, #1679cb 100%);
font-size: 28rpx;
color: rgba(255, 255, 255, 1);
border-radius: 0 12rpx 0 24rpx;
line-height: 56rpx;
display: flex;
align-items: center;
gap: 12rpx;
.icon {
width: 28rpx;
height: 28rpx;
}
}
.avatar { .avatar {
flex-shrink: 0; flex-shrink: 0;
.avatar-img { .avatar-img {
@ -30,23 +51,48 @@ page {
flex-wrap: wrap; flex-wrap: wrap;
font-size: 44rpx; font-size: 44rpx;
color: rgba(33, 33, 32, 1); color: rgba(33, 33, 32, 1);
}
.content {
margin-top: 16rpx;
font-size: 32rpx;
color: rgba(33, 33, 32, 1);
line-height: 52rpx;
.tel-icon { .tel-icon {
flex-shrink: 0; display: inline-block;
margin-left: 10rpx; margin-left: 10rpx;
vertical-align: middle;
width: 40rpx; width: 40rpx;
height: 40rpx; height: 40rpx;
} }
} }
.content { }
margin-top: 16rpx; }
.nav {
padding: 32rpx 40rpx;
background-color: #fcfcfc;
.nav-wrap {
background-color: rgba(243, 243, 243, 1);
padding: 8rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
.nav-item {
flex: 1;
padding: 10rpx;
font-size: 32rpx; font-size: 32rpx;
color: rgba(33, 33, 32, 1); color: rgba(33, 33, 32, 0.4);
line-height: 52rpx; line-height: 48rpx;
text-align: center;
&.active {
background-color: #fff;
border-radius: 8rpx;
color: rgba(22, 121, 203, 1);
}
} }
} }
} }
.container { .container {
margin: 32rpx 40rpx; margin: 0 40rpx 32rpx;
padding: 0 32rpx; padding: 0 32rpx;
background: #ffffff; background: #ffffff;
box-shadow: 0rpx 4rpx 32rpx 0rpx rgba(0, 0, 0, 0.08); box-shadow: 0rpx 4rpx 32rpx 0rpx rgba(0, 0, 0, 0.08);
@ -57,6 +103,7 @@ page {
justify-content: space-between; justify-content: space-between;
padding: 30rpx 0; padding: 30rpx 0;
border-bottom: 1px solid rgba(244, 243, 243, 1); border-bottom: 1px solid rgba(244, 243, 243, 1);
gap: 32rpx;
&.row-vetical { &.row-vetical {
display: block; display: block;
.content { .content {
@ -73,22 +120,273 @@ page {
font-weight: bold; font-weight: bold;
} }
.content { .content {
flex: 1;
text-align: right; text-align: right;
font-size: 36rpx; font-size: 32rpx;
color: rgba(33, 33, 32, 1); color: rgba(33, 33, 32, 1);
} }
} }
} }
.chart {
margin: 32rpx 40rpx;
.chart-header {
margin-top: 48rpx;
display: flex;
gap: 24rpx;
.type {
padding: 8rpx 32rpx;
font-size: 32rpx;
color: rgba(34, 34, 34, 0.6);
background-color: rgba(246, 246, 246, 1);
border-radius: 58rpx;
display: flex;
align-items: center;
gap: 14rpx;
.icon {
font-size: 24rpx;
color: rgba(34, 34, 34, 0.6);
transform: rotate(90deg);
}
}
.date {
padding: 8rpx 32rpx;
font-size: 32rpx;
color: rgba(34, 34, 34, 0.6);
background-color: rgba(246, 246, 246, 1);
border-radius: 58rpx;
display: flex;
align-items: center;
gap: 14rpx;
.line {
margin: 0 10rpx;
}
.icon {
transform: rotate(90deg);
color: rgba(34, 34, 34, 0.6);
font-size: 24rpx;
}
}
}
.chart-container {
margin-top: 32rpx;
padding: 32rpx;
background-color: #fff;
border-radius: 24rpx 24rpx 24rpx 24rpx;
border: 2rpx solid #ffffff;
box-shadow: 0rpx 4rpx 32rpx 0rpx rgba(0, 0, 0, 0.08);
.c-header {
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: rgba(22, 121, 203, 1);
border-radius: 32rpx 32rpx 32rpx 32rpx;
}
}
.sub {
font-size: 32rpx;
color: rgba(34, 34, 34, 0.4);
}
}
.w-chart {
height: 460rpx;
}
}
}
.case-report {
margin: 64rpx 40rpx 0;
.c-header {
display: flex;
align-items: center;
gap: 32rpx;
.name {
flex-shrink: 0;
font-size: 36rpx;
color: rgba(0, 0, 0, 1);
font-weight: bold;
text-align: center;
position: relative;
z-index: 1;
&::after {
margin-top: -20rpx;
display: block;
content: '';
height: 20rpx;
background: linear-gradient(90deg, #1679cb 0%, rgba(22, 121, 203, 0) 100%);
border-radius: 0rpx 0rpx 0rpx 0rpx;
opacity: 0.2;
}
}
}
.list {
.row {
margin-top: 32rpx;
padding: 34rpx;
background-color: #fff;
border-radius: 24rpx;
display: flex;
gap: 24rpx;
box-shadow: 0rpx 8rpx 40rpx 0rpx rgba(0, 0, 0, 0.08);
.icon {
flex-shrink: 0;
width: 96rpx;
height: 96rpx;
}
.col {
.name {
font-size: 36rpx;
color: rgba(34, 34, 34, 1);
}
.content {
margin-top: 8rpx;
font-size: 28rpx;
color: rgba(34, 34, 34, 0.5);
}
}
}
}
}
.record-list {
margin: 32rpx 40rpx 0;
.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.1);
}
}
.wrap {
flex: 1;
min-width: 0;
padding-bottom: 48rpx;
.date {
font-size: 36rpx;
color: rgba(34, 34, 34, 1);
line-height: 1;
}
.w-container {
position: relative;
margin-top: 24rpx;
padding: 32rpx;
border-radius: 24rpx;
background-color: #fff;
box-shadow: 0rpx 4rpx 32rpx 0rpx rgba(0, 0, 0, 0.08);
.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);
}
}
}
}
}
}
}
.footer { .footer {
padding: 0 40rpx; padding: 32rpx 40rpx calc(env(safe-area-inset-bottom) + 32rpx);
position: fixed; position: fixed;
bottom: calc(env(safe-area-inset-bottom) + 32rpx); bottom: 0;
left: 0; left: 0;
z-index: 10;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 30rpx; gap: 30rpx;
background-color: #fff;
box-shadow: 0rpx -12rpx 44rpx 0rpx rgba(0, 0, 0, 0.05);
.remark { .remark {
flex: 1; flex: 1;
height: 96rpx; height: 96rpx;

375
src/doctor/pages/d_patientDetail/index.ts

@ -2,6 +2,29 @@ import dayjs from 'dayjs'
const app = getApp<IAppOption>() 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({ Page({
data: { data: {
toastShow: false, toastShow: false,
@ -106,16 +129,306 @@ Page({
2: '6个月-3年', 2: '6个月-3年',
3: '3年以上', 3: '3年以上',
}, },
// 图表筛选
yearOptions: [
{ label: '按年', value: 1 },
{ label: '按次', value: 2 },
] as { label: string; value: number }[],
selected: 1,
selectedLabel: '按年',
BeginMonth: dayjs().subtract(1, 'year').add(1, 'month').format('YYYY-MM'),
EndMonth: dayjs().format('YYYY-MM'),
chartUnit: '',
chartList: [] as any[],
// 病历报告
caseReportList: [
{
icon: 'icon142.png',
name: '门诊病历及处方',
content: '医生手写或电脑打印的病历、处方',
imageField: 'outpatientRecord',
},
{ icon: 'icon127.png', name: '检验报告', content: '抽血检测的IgG4、IgG、ESR等', imageField: 'labReport' },
{
icon: 'icon128.png',
name: '影像学检查',
content: '超声/CT/MRI的描述结论、电子胶片',
imageField: 'imagingExam',
},
{
icon: 'icon129.png',
name: '病理诊断及活检',
content: '受累组织穿刺或切除免疫组化报告',
imageField: 'pathology',
},
{
icon: 'icon130.png',
name: '外周血免疫功能评估',
content: '免疫细胞亚群、淋巴细胞比例测定',
imageField: 'immuneFunction',
},
],
// Tab 切换
currentTab: 0,
// 就诊记录列表
recordList: [] as any[],
recordPagination: { count: 0, page: 1, pages: 1 },
// 导出PPT
pptGenerating: false,
}, },
onLoad(option) { ecDataTrendComponent: null as any,
async onLoad(option) {
this.setData({ this.setData({
id: option.id, id: option.id,
}) })
echarts = await require.async('../../../resource/components/echart/echarts.js')
this.ecDataTrendComponent = this.selectComponent('#chart1')
app.waitLogin().then(async (_res) => { app.waitLogin().then(async (_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorPatientDetail', detailId: this.data.id })
this.getDoctorDetail() this.getDoctorDetail()
await this.getDetail() await this.getDetail()
this.getIndicatorHistory()
this.getRecordList(true)
})
},
// ========== Tab 切换 ==========
handleTabChange(e: any) {
const { index } = e.currentTarget.dataset
const tab = Number(index)
if (tab === this.data.currentTab) return
this.setData({ currentTab: tab })
wx.pageScrollTo({ scrollTop: 0, duration: 200 })
// 切换到就诊记录时如果列表为空则请求
if (tab === 1 && !this.data.recordList.length) {
this.getRecordList(true)
}
// 切换到基本信息时重新获取图表组件并渲染
if (tab === 0 && this.data.chartList.length) {
setTimeout(() => {
this.ecDataTrendComponent = this.selectComponent('#chart1')
this.initChart(this.data.chartList)
}, 100)
}
},
// ========== 图表筛选 ==========
handleChange(e: any) {
const idx = Number(e.detail.value)
const selected = this.data.yearOptions[idx]
if (!selected) return
const isByYear = selected.value === 1
this.setData({
selected: selected.value,
selectedLabel: selected.label,
BeginMonth: isByYear ? dayjs().subtract(1, 'year').add(1, 'month').format('YYYY-MM') : '',
EndMonth: isByYear ? dayjs().format('YYYY-MM') : '',
})
this.getIndicatorHistory()
},
handleBeginDateChange(e: any) {
const BeginMonth = e.detail.value
this.setData({ BeginMonth })
if (this.data.EndMonth) {
this.getIndicatorHistory()
}
},
handleEndDateChange(e: any) {
const EndMonth = e.detail.value
this.setData({ EndMonth })
if (this.data.BeginMonth) {
this.getIndicatorHistory()
}
},
// ========== 指标趋势图表 ==========
initChart(defaultList: any[] = []) {
if (!defaultList.length) return
if (!echarts || !this.ecDataTrendComponent) {
this.ecDataTrendComponent = this.selectComponent('#chart1')
if (!echarts || !this.ecDataTrendComponent) return
}
const list = defaultList
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 color = 'rgba(22, 121, 203, 1)'
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',
minInterval: 1,
axisLabel: { color: 'rgba(34, 34, 34, 0.40)' },
splitLine: {
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,
},
type: 'line',
symbol: 'circle',
symbolSize: 8,
showSymbol: list.length >= 1,
connectNulls: true,
z: 10,
itemStyle: {
color,
},
markLine: {
symbol: ['none', 'none'],
data: [
{
name: '135 安全区',
yAxis: 135,
label: {
formatter: '{b}',
position: 'insideMiddle',
color,
fontSize: '10',
},
lineStyle: {
cap: '',
color,
type: 'dashed',
},
},
],
},
markArea: {
itemStyle: {
color: 'rgba(22, 121, 203, 0.16)',
},
data: [[{ yAxis: 0 }, { yAxis: 135 }]],
},
},
],
dataZoom: {
type: 'inside',
startValue: 0,
endValue: 4,
filterMode: 'none',
},
}
chart.setOption(option)
return chart
})
},
getIndicatorHistory() {
const data: Record<string, any> = {
patientId: this.data.id,
indicatorField: 'igG4',
}
const { BeginMonth, EndMonth, selected } = this.data
if (BeginMonth && EndMonth && selected === 1) {
data.yearStart = Number(BeginMonth.substring(0, 4))
data.yearEnd = Number(EndMonth.substring(0, 4))
}
wx.ajax({
method: 'GET',
url: '?r=igg4/doctor/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 || [])
})
},
// ========== 就诊记录列表 ==========
getRecordList(reset = false) {
const page = reset ? 1 : this.data.recordPagination.page
wx.ajax({
method: 'GET',
url: '?r=igg4/doctor/medical-visit/list',
data: {
patientId: this.data.id,
page,
pageSize: 10,
},
}).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),
}))
this.setData({
recordList: reset ? list : [...this.data.recordList, ...list],
recordPagination: {
count: res.totalCount || 0,
page: res.page || 1,
pages: res.pages || 1,
},
})
}) })
}, },
// ========== 患者详情 ==========
getDoctorDetail() { getDoctorDetail() {
wx.ajax({ wx.ajax({
method: 'GET', method: 'GET',
@ -157,7 +470,7 @@ Page({
res.HealthQA.questions.forEach((q: any) => { res.HealthQA.questions.forEach((q: any) => {
q.isAnswerArray = Array.isArray(q.AnswerText) q.isAnswerArray = Array.isArray(q.AnswerText)
if (q.isAnswerArray) { if (q.isAnswerArray) {
q.AnswerTextArr = q.AnswerText.filter((a: any) => a !== null && a !== undefined) q.AnswerTextArr = q.AnswerText.filter((a: any) => a !== null && a !== undefined && a !== '其他')
q.AnswerTextStr = q.AnswerTextArr.join('、') + (q.OtherText ? `${q.OtherText}` : '') q.AnswerTextStr = q.AnswerTextArr.join('、') + (q.OtherText ? `${q.OtherText}` : '')
} else { } else {
q.AnswerTextStr = q.AnswerText || '' q.AnswerTextStr = q.AnswerText || ''
@ -167,10 +480,12 @@ Page({
} }
}) })
} }
wx.setNavigationBarTitle({
title: res.Name ? `${res.Name}的患者详情` : '患者详情',
})
this.setData({ this.setData({
detail: { detail: {
...res, ...res,
hideName: res.Name.length <= 2 ? res.Name.charAt(0) + '*' : res.Name.charAt(0) + '*' + res.Name.charAt(res.Name.length - 1),
hideTelephone: res.Telephone ? res.Telephone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '', hideTelephone: res.Telephone ? res.Telephone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '',
}, },
}) })
@ -275,4 +590,58 @@ Page({
handleToastCancel() { handleToastCancel() {
wx.navigateBack() wx.navigateBack()
}, },
// ========== 病历报告导航 ==========
handleDetail(e: any) {
const { name, imagefield } = e.currentTarget.dataset
wx.navigateTo({
url: `/doctor/pages/d_caseReport/index?name=${name}&imageField=${imagefield}&patientId=${this.data.id}`,
})
},
// ========== 导出PPT ==========
handleGeneratePdf() {
if (this.data.pptGenerating) return
this.setData({ pptGenerating: true })
const patientId = this.data.id
const downloadUrl = `${app.globalData.url}?r=igg4/doctor/medical-visit/export-ppt&loginState=${app.globalData.loginState}&patientId=${patientId}`
wx.downloadFile({
url: downloadUrl,
success: (res) => {
if (res.statusCode === 200) {
wx.shareFileMessage({
filePath: res.tempFilePath,
fileName: 'medical-visit.pptx',
success: () => {
wx.showToast({ title: '分享成功', icon: 'success' })
this.setData({ pptGenerating: false })
},
fail: () => {
wx.showToast({ title: '分享失败', icon: 'none' })
this.setData({ pptGenerating: false })
},
})
} else {
wx.showToast({ title: '下载失败', icon: 'none' })
this.setData({ pptGenerating: false })
}
},
fail: () => {
wx.showToast({ title: '下载失败', icon: 'none' })
this.setData({ pptGenerating: false })
},
})
},
// ========== 分页加载 ==========
onReachBottom() {
const { page, pages } = this.data.recordPagination
if (page < pages) {
this.setData({ 'recordPagination.page': page + 1 })
this.getRecordList()
}
},
}) })

137
src/doctor/pages/d_patientDetail/index.wxml

@ -1,21 +1,41 @@
<wxs src="../../../utils/tools.wxs" module="tools" /> <wxs src="../../../utils/tools.wxs" module="tools" />
<view class="page"> <view class="page">
<view class="banner"> <view class="banner">
<view class="generate-pdf" bind:tap="handleGeneratePdf">
{{pptGenerating ? '生成中...' : '一键生成病历'}}
<image class="icon" src="{{imageUrl}}icon144.png?t={{Timestamp}}" wx:if="{{!pptGenerating}}"></image>
</view>
<view class="avatar"> <view class="avatar">
<image class="avatar-img" src="{{detail.UserImg}}"></image> <image class="avatar-img" src="{{detail.UserImg}}"></image>
</view> </view>
<view class="wrap"> <view class="wrap">
<view class="name" bind:tap="handleToggleTel"> <view class="name">{{detail.Name}}</view>
{{showTel ? detail.Name : detail.hideName}} <view class="content" bind:tap="handleToggleTel">
<view>
{{detail.AgeRangeName}} | {{showTel ? detail.Telephone : detail.hideTelephone}}
<image wx:if="{{showTel}}" class="tel-icon" src="{{imageUrl}}/5/eye2.png?t={{Timestamp}}"></image> <image wx:if="{{showTel}}" class="tel-icon" src="{{imageUrl}}/5/eye2.png?t={{Timestamp}}"></image>
<image wx:else class="tel-icon" src="{{imageUrl}}/5/eye1.png?t={{Timestamp}}"></image> <image wx:else class="tel-icon" src="{{imageUrl}}/5/eye1.png?t={{Timestamp}}"></image>
</view> </view>
<view class="content">
<view>{{detail.AgeRangeName}} | {{showTel ? detail.Telephone : detail.hideTelephone}}</view>
<view>邀约时间:{{tools.formatDate(detail.InviteTime)}}</view> <view>邀约时间:{{tools.formatDate(detail.InviteTime)}}</view>
</view> </view>
</view> </view>
</view> </view>
<van-sticky>
<view class="nav">
<view class="nav-wrap">
<view class="nav-item {{currentTab === 0 && 'active'}}" data-index="0" bind:tap="handleTabChange">
基本信息
</view>
<view class="nav-item {{currentTab === 1 && 'active'}}" data-index="1" bind:tap="handleTabChange">
就诊记录
</view>
</view>
</view>
</van-sticky>
<!-- 基本信息 -->
<block wx:if="{{currentTab === 0}}">
<view class="container"> <view class="container">
<block wx:if="{{detail.HealthQA && detail.HealthQA.hasSubmitted && detail.HealthQA.questions.length > 0}}"> <block wx:if="{{detail.HealthQA && detail.HealthQA.hasSubmitted && detail.HealthQA.questions.length > 0}}">
<view class="row" wx:for="{{detail.HealthQA.questions}}" wx:key="QuestionNo"> <view class="row" wx:for="{{detail.HealthQA.questions}}" wx:key="QuestionNo">
@ -23,7 +43,12 @@
<view class="content" wx:if="{{item.AnswerTextStr}}"> <view class="content" wx:if="{{item.AnswerTextStr}}">
<text wx:if="{{!item.isAnswerArray}}">{{item.AnswerTextStr}}</text> <text wx:if="{{!item.isAnswerArray}}">{{item.AnswerTextStr}}</text>
<block wx:else> <block wx:else>
<view wx:for="{{item.AnswerTextArr}}" wx:for-item="ans" wx:key="index">{{ans}}<text wx:if="{{item.OtherText && index === item.AnswerTextArr.length - 1}}">({{item.OtherText}})</text></view> <view wx:for="{{item.AnswerTextArr}}" wx:for-item="ans" wx:key="index">
{{ans}}
<text wx:if="{{item.OtherText && index === item.AnswerTextArr.length - 1}}">
其他:{{item.OtherText}}
</text>
</view>
</block> </block>
</view> </view>
<view class="content" wx:else>无</view> <view class="content" wx:else>无</view>
@ -34,6 +59,108 @@
<view class="content">{{detail.DoctorRemark || '无'}}</view> <view class="content">{{detail.DoctorRemark || '无'}}</view>
</view> </view>
</view> </view>
<view class="chart">
<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="{{BeginMonth}}"
end="{{EndMonth}}"
>
<view class="start {{BeginMonth && 'active'}}">{{BeginMonth ? BeginMonth : '开始时间'}}</view>
</picker>
<view class="line">-</view>
<picker
mode="date"
fields="month"
bindchange="handleEndDateChange"
value="{{EndMonth}}"
start="{{BeginMonth}}"
>
<view class="end {{EndMonth && 'active'}}">{{EndMonth ? EndMonth : '结束时间'}}</view>
</picker>
<van-icon class="icon" name="play" />
</view>
</view>
<view class="chart-container">
<view class="c-header">
<view class="title">血清IgG4趋势监测</view>
<view class="sub">单位{{chartUnit}}</view>
</view>
<view class="w-chart">
<ec-canvas id="chart1" ec="{{ ec }}"></ec-canvas>
</view>
</view>
</view>
<view class="case-report">
<view class="c-header">
<view class="name">病历报告</view>
</view>
<view class="list">
<view
class="row"
wx:for="{{caseReportList}}"
wx:key="icon"
bind:tap="handleDetail"
data-name="{{item.name}}"
data-imagefield="{{item.imageField}}"
>
<image class="icon" src="{{imageUrl}}{{item.icon}}?t={{Timestamp}}"></image>
<view class="col">
<view class="name">{{item.name}}</view>
<view class="content">{{item.content}}</view>
</view>
</view>
</view>
</view>
</block>
<!-- 就诊记录 -->
<block wx:if="{{currentTab === 1}}">
<view class="record-list">
<view class="card" wx:for="{{recordList}}" wx:key="id">
<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="w-container">
<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}}icon143.png?t={{Timestamp}}"></image>
</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>
<view class="empty-text" wx:if="{{!recordList.length}}">暂无就诊记录</view>
</view>
</block>
<view class="footer"> <view class="footer">
<view class="remark" bind:tap="handleRemark">备注</view> <view class="remark" bind:tap="handleRemark">备注</view>

27
src/doctor/pages/d_taskDetail/index.scss

@ -1,5 +1,5 @@
page { page {
background-color: rgba(242, 244, 245, 1); background-color: rgba(246, 246, 246, 1);
padding-bottom: 200rpx; padding-bottom: 200rpx;
} }
@ -33,7 +33,7 @@ page {
} }
&.status4 { &.status4 {
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
} }
&.status5 { &.status5 {
@ -64,7 +64,7 @@ page {
.label { .label {
font-size: 32rpx; font-size: 32rpx;
color: rgba(173, 179, 180, 1); color: rgba(34, 34, 34, 0.40);
} }
.content { .content {
@ -112,7 +112,7 @@ page {
margin-right: 20rpx; margin-right: 20rpx;
font-size: 32rpx; font-size: 32rpx;
flex-shrink: 0; flex-shrink: 0;
color: rgba(173, 179, 180, 1); color: rgba(34, 34, 34, 0.40);
} }
} }
} }
@ -144,7 +144,7 @@ page {
.label { .label {
width: 5em; width: 5em;
font-size: 32rpx; font-size: 32rpx;
color: rgba(173, 179, 180, 1); color: rgba(34, 34, 34, 0.40);
} }
.content { .content {
@ -152,7 +152,7 @@ page {
color: rgba(40, 48, 49, 1); color: rgba(40, 48, 49, 1);
&.success { &.success {
color: rgba(98, 190, 208, 1); color: rgba(22, 121, 203, 1);
} }
&.warn { &.warn {
@ -179,23 +179,24 @@ page {
.copy-btn { .copy-btn {
width: 332rpx; width: 332rpx;
height: 84rpx; height: 96rpx;
box-sizing: border-box;
background: #ffffff; background: #ffffff;
border-radius: 98rpx 98rpx 98rpx 98rpx; border-radius: 12rpx;
border: 2rpx solid #67baca; border: 2rpx solid rgba(22, 121, 203, 1);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 36rpx; font-size: 36rpx;
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
} }
.btn { .btn {
width: 332rpx; width: 332rpx;
height: 84rpx; height: 96rpx;
box-sizing: border-box;
color: #fff; color: #fff;
background: #67BACA; background: linear-gradient( 96deg, #293B9F 0%, #1679CB 100%);
border-radius: 98rpx 98rpx 98rpx 98rpx; border-radius: 98rpx 98rpx 98rpx 98rpx;
border: 2rpx solid #67baca;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

88
src/doctor/pages/d_taskDetail/index.ts

@ -1,5 +1,4 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { filterObjs } from '@/utils/doctorPatientTask'
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
@ -12,39 +11,38 @@ Page({
Status: { Status: {
1: '待发送', 1: '待发送',
2: '发送中', 2: '发送中',
3: '暂停',
4: '已发送', 4: '已发送',
5: '已停止', 5: '已停止',
6: '已取消', 6: '已取消',
}, },
templateObj: {}, ConfirmedIgg4Obj: {
0: '全部',
...filterObjs, 1: '是',
2: '否',
},
MedicationTypeObj: {
0: '全部',
2: '激素',
3: '免疫抑制剂',
4: '靶向CD19生物制剂(伊奈利珠单抗)',
5: '靶向CD20生物制剂(利妥昔单抗等)',
},
TemplateTypeObj: {
1: '复诊提醒',
2: '激素风险提醒',
},
}, },
onLoad(options) { onLoad(options) {
this.setData({ this.setData({
id: options.id, id: options.id,
}) })
app.waitLogin().then(() => { app.waitLogin().then(() => {
this.getTemplate() app.mpBehavior({ doctor: true, PageName: 'PG_DoctorTaskDetail', detailId: this.data.id })
this.getDetail() this.getDetail()
}) })
}, },
getTemplate() {
wx.ajax({
method: 'GET',
url: '?r=igg4/doctor/task/get-template',
data: {},
}).then((res) => {
const templateObj = {}
res.forEach((item) => {
templateObj[item.TemplateType] = item.TemplateTypeText
})
this.setData({
templateObj,
})
})
},
getDetail() { getDetail() {
wx.ajax({ wx.ajax({
method: 'GET', method: 'GET',
@ -53,33 +51,10 @@ Page({
TaskId: this.data.id, TaskId: this.data.id,
}, },
}).then((res) => { }).then((res) => {
if (typeof res.FilterCondition.AdlScore === 'string') { if (typeof res.FilterCondition.MedicationType === 'string' && res.FilterCondition.MedicationType) {
res.FilterCondition.AdlScore = res.FilterCondition.AdlScore?.split('-') res.FilterCondition.MedicationTypeArr = res.FilterCondition.MedicationType.split(',').map(Number)
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else {
res.FilterCondition.AdlScore = []
}
if (typeof res.FilterCondition.AdlScoreChange === 'string') {
res.FilterCondition.AdlScoreChange = res.FilterCondition.AdlScoreChange?.split('-')
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else {
res.FilterCondition.AdlScoreChange = []
}
if (typeof res.FilterCondition.SteroidDailyDose === 'string') {
res.FilterCondition.SteroidDailyDose = res.FilterCondition.SteroidDailyDose?.split('-')
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else {
res.FilterCondition.SteroidDailyDose = []
}
if (typeof res.FilterCondition.SteroidDailyDoseChange === 'string') {
res.FilterCondition.SteroidDailyDoseChange = res.FilterCondition.SteroidDailyDoseChange?.split('-')
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else { } else {
res.FilterCondition.SteroidDailyDoseChange = [] res.FilterCondition.MedicationTypeArr = []
} }
if (res.CreateTime) { if (res.CreateTime) {
res.CreateTime = dayjs(res.CreateTime).format('YYYY-MM-DD HH:mm') res.CreateTime = dayjs(res.CreateTime).format('YYYY-MM-DD HH:mm')
@ -98,26 +73,11 @@ Page({
url: '/doctor/pages/d_createTask/index', url: '/doctor/pages/d_createTask/index',
success: (res) => { success: (res) => {
res.eventChannel.emit('FilterCondition', { res.eventChannel.emit('FilterCondition', {
Search: FilterCondition.Search, SelectAll: FilterCondition.SelectAll,
HasAdl: FilterCondition.HasAdl,
DoubleStandard: FilterCondition.DoubleStandard,
AdlTime: FilterCondition.AdlTime,
AdlScore: FilterCondition.AdlScore.filter((item) => Boolean(item)),
AdlScoreChange: FilterCondition.AdlScoreChange.filter((item) => Boolean(item)),
AdlScoreChangeType: FilterCondition.AdlScoreChangeType,
AdlScoreChangeMax: FilterCondition.AdlScoreChangeMax,
AdlScoreChangeMin: FilterCondition.AdlScoreChangeMin,
SteroidDailyDose: FilterCondition.SteroidDailyDose.filter((item) => Boolean(item)),
SteroidDailyDoseChange: FilterCondition.SteroidDailyDoseChange.filter((item) => Boolean(item)),
IsThisWeekAdl: FilterCondition.IsThisWeekAdl,
PharyngealSymptoms: FilterCondition.PharyngealSymptoms,
AntibodyType: FilterCondition.AntibodyType,
ThyroidAbnormal: FilterCondition.ThyroidAbnormal,
PreviousConvulsion: FilterCondition.PreviousConvulsion,
TraditionalImmunosuppressant: FilterCondition.TraditionalImmunosuppressant,
MedicalInsuranceType: FilterCondition.MedicalInsuranceType,
BindStartDate: FilterCondition.BindStartDate, BindStartDate: FilterCondition.BindStartDate,
BindEndDate: FilterCondition.BindEndDate, BindEndDate: FilterCondition.BindEndDate,
ConfirmedIgg4: FilterCondition.ConfirmedIgg4,
MedicationType: FilterCondition.MedicationType,
}) })
}, },
}) })

105
src/doctor/pages/d_taskDetail/index.wxml

@ -17,110 +17,39 @@
<view class="card"> <view class="card">
<view class="c-title"> <view class="c-title">
<image class="icon" src="{{imageUrl}}/doctor/icon-title-lable.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon147.png?t={{Timestamp}}"></image>
筛选患者 筛选患者
</view> </view>
<view class="c-wrap"> <view class="c-wrap">
<view class="row" wx:if="{{detail.FilterCondition.Search}}"> <view class="row" wx:if="{{detail.FilterCondition.SelectAll === 1}}">
<view class="label">搜索</view> <view class="label">选择范围</view>
<view class="content">{{detail.FilterCondition.Search}}</view> <view class="content">全部患者</view>
</view> </view>
<view class="row" wx:if="{{detail.FilterCondition.BindStartDate && detail.FilterCondition.BindEndDate}}"> <view class="row" wx:if="{{detail.FilterCondition.BindStartDate || detail.FilterCondition.BindEndDate}}">
<view class="label">绑定时间</view> <view class="label">绑定时间</view>
<view class="content">{{detail.FilterCondition.BindStartDate}}~{{detail.FilterCondition.BindEndDate}}</view> <view class="content">{{detail.FilterCondition.BindStartDate || '---'}}~{{detail.FilterCondition.BindEndDate || '---'}}</view>
</view> </view>
<view class="row" wx:if="{{detail.FilterCondition.IsThisWeekAdl}}"> <view class="row" wx:if="{{detail.FilterCondition.ConfirmedIgg4}}">
<view class="label">本周ADL测评</view> <view class="label">确诊IgG4-RD</view>
<view class="content">{{IsThisWeekAdlObj[detail.FilterCondition.IsThisWeekAdl]}}</view> <view class="content">{{ConfirmedIgg4Obj[detail.FilterCondition.ConfirmedIgg4]}}</view>
</view> </view>
<view class="row" wx:if="{{detail.FilterCondition.HasAdl}}"> <view class="row" wx:if="{{detail.FilterCondition.MedicationTypeArr.length}}">
<view class="label">是否有ADL测评</view> <view class="label">正在使用的药物</view>
<view class="content">{{HasAdlObj[detail.FilterCondition.HasAdl]}}</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.AdlTime}}">
<view class="label">ADL测评间隔时长</view>
<view class="content">{{AdlTimeObj[detail.FilterCondition.AdlTime]}}</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.DoubleStandard}}">
<view class="label">是否双达标</view>
<view class="content">{{DoubleStandardObj[detail.FilterCondition.DoubleStandard]}}</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.AdlScore.length}}">
<view class="label">ADL评分(多选)</view>
<view class="content">
<block wx:for="{{detail.FilterCondition.AdlScore}}" wx:for-item="subItem" wx:key="subItem">
{{AdlScorceObj[subItem]}}{{index < detail.FilterCondition.AdlScore.length - 1 ? ',' : ''}}
</block>
</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.AdlScoreChange.length}}">
<view class="label">ADL分数变化(多选)</view>
<view class="content">
<block wx:for="{{detail.FilterCondition.AdlScoreChange}}" wx:for-item="subItem" wx:key="subItem">
{{AdlScoreChangeObj[subItem]}}{{index < detail.FilterCondition.AdlScoreChange.length - 1 ? ',' : ''}}
</block>
<view>
<block wx:if="{{detail.FilterCondition.AdlScoreChangeType}}">
{{AdlScoreChangeTypeObj[detail.FilterCondition.AdlScoreChangeType]}}
</block>
<block wx:if="{{detail.FilterCondition.AdlScoreChangeType===2}}">
({{detail.FilterCondition.AdlScoreChangeMin}}-{{detail.FilterCondition.AdlScoreChangeMax}})
</block>
</view>
</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.SteroidDailyDose.length}}">
<view class="label">激素用量(多选)</view>
<view class="content"> <view class="content">
<block wx:for="{{detail.FilterCondition.SteroidDailyDose}}" wx:for-item="subItem" wx:key="subItem"> <block wx:for="{{detail.FilterCondition.MedicationTypeArr}}" wx:for-item="subItem" wx:key="subItem">
{{SteroidDailyDoseObj[subItem]}}{{index < detail.FilterCondition.SteroidDailyDose.length - 1 ? ',' : ''}} {{MedicationTypeObj[subItem]}}{{index < detail.FilterCondition.MedicationTypeArr.length - 1 ? '、' : ''}}
</block> </block>
</view> </view>
</view> </view>
<view class="row" wx:if="{{detail.FilterCondition.SteroidDailyDoseChange.length}}">
<view class="label">激素变化(多选)</view>
<view class="content">
<block wx:for="{{detail.FilterCondition.SteroidDailyDoseChange}}" wx:for-item="subItem" wx:key="subItem">
{{SteroidDailyDoseChangeObj[subItem]}}{{index < detail.FilterCondition.SteroidDailyDoseChange.length - 1 ?
',' : ''}}
</block>
</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.PharyngealSymptoms}}">
<view class="label">延髓症状</view>
<view class="content">{{PharyngealSymptomsObj[detail.FilterCondition.PharyngealSymptoms]}}</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.AntibodyType}}">
<view class="label">抗体类型</view>
<view class="content">{{AntibodyTypeObj[detail.FilterCondition.AntibodyType]}}</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.ThyroidAbnormal}}">
<view class="label">胸腺异常</view>
<view class="content">{{ThyroidAbnormalObj[detail.FilterCondition.ThyroidAbnormal]}}</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.PreviousConvulsion}}">
<view class="label">既往发生危象</view>
<view class="content">{{PreviousConvulsionObj[detail.FilterCondition.PreviousConvulsion]}}</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.TraditionalImmunosuppressant}}">
<view class="label">传统免疫抑制剂</view>
<view class="content">
{{TraditionalImmunosuppressantObj[detail.FilterCondition.TraditionalImmunosuppressant]}}
</view>
</view>
<view class="row" wx:if="{{detail.FilterCondition.MedicalInsuranceType}}">
<view class="label">医保类型</view>
<view class="content">{{MedicalInsuranceTypeObj[detail.FilterCondition.MedicalInsuranceType]}}</view>
</view>
</view> </view>
<view class="c-title"> <view class="c-title">
<image class="icon" src="{{imageUrl}}/doctor/icon-title-lable.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon147.png?t={{Timestamp}}"></image>
消息内容 消息内容
</view> </view>
<view class="c-wrap"> <view class="c-wrap">
<view class="row"> <view class="row">
<view class="label">消息标题</view> <view class="label">消息标题</view>
<view class="content">{{templateObj[detail.TemplateType]}}</view> <view class="content">{{TemplateTypeObj[detail.TemplateType]}}</view>
</view> </view>
<view class="row"> <view class="row">
<view class="label">消息内容</view> <view class="label">消息内容</view>
@ -128,7 +57,7 @@
</view> </view>
</view> </view>
<view class="c-title"> <view class="c-title">
<image class="icon" src="{{imageUrl}}/doctor/icon-title-lable.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon147.png?t={{Timestamp}}"></image>
发送时间 发送时间
</view> </view>
<view class="c-wrap"> <view class="c-wrap">
@ -140,7 +69,7 @@
</view> </view>
<view class="card" wx:if="{{detail.Status === '4' || detail.Status === '5'}}"> <view class="card" wx:if="{{detail.Status === '4' || detail.Status === '5'}}">
<view class="c-title"> <view class="c-title">
<image class="icon" src="{{imageUrl}}/doctor/icon-title-lable.png?t={{Timestamp}}"></image> <image class="icon" src="{{imageUrl}}icon147.png?t={{Timestamp}}"></image>
发送结果 发送结果
</view> </view>
<view class="c-prominent"> <view class="c-prominent">

5
src/doctor/pages/d_taskList/index.json

@ -1,6 +1,9 @@
{ {
"navigationStyle": "custom", "navigationBarTitleText": "任务列表",
"usingComponents": { "usingComponents": {
"pagination": "/components/pagination/index",
"van-divider": "@vant/weapp/divider/index",
"van-loading": "@vant/weapp/loading/index",
"navBar": "/components/navBar/navBar" "navBar": "/components/navBar/navBar"
} }
} }

63
src/doctor/pages/d_taskList/index.scss

@ -1,15 +1,18 @@
page { page {
background: #f2f4f5; background: rgba(246, 246, 246, 1);
} }
.page { .page {
padding-bottom: 300rpx; padding-bottom: 300rpx;
.card { .card {
margin: 30rpx; margin: 30rpx;
background-color: rgba(246, 246, 246, 1);
border: 1px solid rgba(255, 255, 255, 1);
border-radius: 24rpx;
.card-body {
padding: 32rpx 30rpx; padding: 32rpx 30rpx;
background-color: #fff; background-color: #fff;
border-radius: 24rpx; border-radius: 24rpx;
.c-header { .c-header {
padding-bottom: 32rpx; padding-bottom: 32rpx;
display: flex; display: flex;
@ -20,7 +23,7 @@ page {
.id { .id {
font-size: 32rpx; font-size: 32rpx;
color: rgba(40, 48, 49, 1); color: rgba(34, 34, 34, 1);
} }
.status { .status {
@ -35,8 +38,12 @@ page {
color: rgba(229, 154, 41, 1); color: rgba(229, 154, 41, 1);
} }
&.status3 {
color: rgba(229, 154, 41, 1);
}
&.status4 { &.status4 {
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
} }
&.status5 { &.status5 {
@ -60,13 +67,14 @@ page {
flex-shrink: 0; flex-shrink: 0;
width: 5em; width: 5em;
font-size: 32rpx; font-size: 32rpx;
color: rgba(173, 179, 180, 1); color: rgba(34, 34, 34, 1);
font-weight: bold;
} }
.content { .content {
flex: 1; flex: 1;
font-size: 32rpx; font-size: 32rpx;
color: rgba(40, 48, 49, 1); color: rgba(34, 34, 34, 1);
&:empty::after { &:empty::after {
display: block; display: block;
@ -91,6 +99,8 @@ page {
.f-content { .f-content {
display: inline; display: inline;
white-space: pre-lincwe;
}
} }
} }
} }
@ -98,7 +108,7 @@ page {
} }
.c-footer { .c-footer {
padding: 40rpx 0 0; padding: 24rpx 30rpx;
border-top: 1px solid rgba(242, 244, 245, 1); border-top: 1px solid rgba(242, 244, 245, 1);
display: flex; display: flex;
gap: 28rpx; gap: 28rpx;
@ -110,7 +120,7 @@ page {
color: rgba(173, 179, 180, 1); color: rgba(173, 179, 180, 1);
.num { .num {
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
font-weight: bold; font-weight: bold;
} }
} }
@ -125,13 +135,13 @@ page {
.copy { .copy {
padding: 12rpx 30rpx; padding: 12rpx 30rpx;
font-size: 32rpx; font-size: 28rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: rgba(103, 186, 202, 1); color: rgba(22, 121, 203, 1);
border-radius: 88rpx 88rpx 88rpx 88rpx; border-radius: 88rpx 88rpx 88rpx 88rpx;
border: 1rpx solid #67baca; border: 1rpx solid rgba(22, 121, 203, 1);
} }
.send { .send {
@ -139,7 +149,7 @@ page {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: rgba(40, 48, 49, 1); color: rgba(34, 34, 34, 1);
border-radius: 88rpx 88rpx 88rpx 88rpx; border-radius: 88rpx 88rpx 88rpx 88rpx;
border: 1rpx solid rgba(39, 58, 101, 0.15); border: 1rpx solid rgba(39, 58, 101, 0.15);
} }
@ -149,25 +159,32 @@ page {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: rgba(40, 48, 49, 1); color: rgba(34, 34, 34, 1);
border-radius: 88rpx 88rpx 88rpx 88rpx; border-radius: 88rpx 88rpx 88rpx 88rpx;
border: 1rpx solid rgba(39, 58, 101, 0.15); border: 1rpx solid rgba(39, 58, 101, 0.15);
} }
} }
} }
} }
}
.create { .footer {
position: fixed; position: fixed;
bottom: 100rpx; bottom: 0;
left: 50%; left: 0;
transform: translateX(-50%);
width: 178rpx;
height: 178rpx;
.icon {
width: 100%; width: 100%;
height: 100%; box-sizing: border-box;
} background-color: #fff;
padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0rpx 4rpx 32rpx 0rpx rgba(0, 0, 0, 0.15);
.btn {
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
color: rgba(255, 255, 255, 1);
background: linear-gradient(96deg, #293b9f 0%, #1679cb 100%);
border-radius: 12rpx 12rpx 12rpx 12rpx;
} }
} }

74
src/doctor/pages/d_taskList/index.ts

@ -1,11 +1,10 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { filterObjs } from '@/utils/doctorPatientTask'
const app = getApp<IAppOption>() const app = getApp<IAppOption>()
Page({ Page({
data: { data: {
list: [] as any[], list: [{}] as any[],
pagination: { pagination: {
page: 1, page: 1,
pages: 1, pages: 1,
@ -14,11 +13,27 @@ Page({
Status: { Status: {
1: '待发送', 1: '待发送',
2: '发送中', 2: '发送中',
3: '暂停',
4: '已发送', 4: '已发送',
5: '已停止', 5: '已停止',
6: '已取消', 6: '已取消',
}, },
...filterObjs, ConfirmedIgg4Obj: {
0: '全部',
1: '是',
2: '否',
},
MedicationTypeObj: {
0: '全部',
2: '激素',
3: '免疫抑制剂',
4: '靶向CD19生物制剂(伊奈利珠单抗)',
5: '靶向CD20生物制剂(利妥昔单抗等)',
},
TemplateTypeObj: {
1: '复诊提醒',
2: '激素风险提醒',
},
}, },
onLoad() { onLoad() {
const SystemInfo = app.globalSystemInfo const SystemInfo = app.globalSystemInfo
@ -32,6 +47,7 @@ Page({
}, },
onShow() { onShow() {
app.waitLogin().then(() => { app.waitLogin().then(() => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorTaskList' })
this.getList() this.getList()
}) })
}, },
@ -44,33 +60,10 @@ Page({
}, },
}).then((res) => { }).then((res) => {
res.list.forEach((item) => { res.list.forEach((item) => {
if (typeof item.FilterCondition.AdlScore === 'string') { if (typeof item.FilterCondition.MedicationType === 'string' && item.FilterCondition.MedicationType) {
item.FilterCondition.AdlScore = item.FilterCondition.AdlScore?.split('-') item.FilterCondition.MedicationTypeArr = item.FilterCondition.MedicationType.split(',').map(Number)
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else {
item.FilterCondition.AdlScore = []
}
if (typeof item.FilterCondition.AdlScoreChange === 'string') {
item.FilterCondition.AdlScoreChange = item.FilterCondition.AdlScoreChange?.split('-')
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else {
item.FilterCondition.AdlScoreChange = []
}
if (typeof item.FilterCondition.SteroidDailyDose === 'string') {
item.FilterCondition.SteroidDailyDose = item.FilterCondition.SteroidDailyDose?.split('-')
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else {
item.FilterCondition.SteroidDailyDose = []
}
if (typeof item.FilterCondition.SteroidDailyDoseChange === 'string') {
item.FilterCondition.SteroidDailyDoseChange = item.FilterCondition.SteroidDailyDoseChange?.split('-')
.map((item) => Number(item))
.filter((item) => Boolean(item))
} else { } else {
item.FilterCondition.SteroidDailyDoseChange = [] item.FilterCondition.MedicationTypeArr = []
} }
if (item.CreateTime) { if (item.CreateTime) {
@ -104,11 +97,13 @@ Page({
}) })
}, },
handleCreate() { handleCreate() {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorTaskListAdd' })
wx.navigateTo({ wx.navigateTo({
url: '/doctor/pages/d_createTask/index', url: '/doctor/pages/d_createTask/index',
}) })
}, },
handleDetail(e) { handleDetail(e) {
app.mpBehavior({ doctor: true, PageName: 'BTN_DoctorTaskListItem' })
const { id } = e.currentTarget.dataset const { id } = e.currentTarget.dataset
wx.navigateTo({ wx.navigateTo({
url: `/doctor/pages/d_taskDetail/index?id=${id}`, url: `/doctor/pages/d_taskDetail/index?id=${id}`,
@ -123,26 +118,11 @@ Page({
url: '/doctor/pages/d_createTask/index', url: '/doctor/pages/d_createTask/index',
success: (res) => { success: (res) => {
res.eventChannel.emit('FilterCondition', { res.eventChannel.emit('FilterCondition', {
Search: FilterCondition.Search, SelectAll: FilterCondition.SelectAll,
HasAdl: FilterCondition.HasAdl,
DoubleStandard: FilterCondition.DoubleStandard,
AdlTime: FilterCondition.AdlTime,
AdlScore: FilterCondition.AdlScore.filter((item) => Boolean(item)),
AdlScoreChange: FilterCondition.AdlScoreChange.filter((item) => Boolean(item)),
AdlScoreChangeType: FilterCondition.AdlScoreChangeType,
AdlScoreChangeMax: FilterCondition.AdlScoreChangeMax,
AdlScoreChangeMin: FilterCondition.AdlScoreChangeMin,
SteroidDailyDose: FilterCondition.SteroidDailyDose.filter((item) => Boolean(item)),
SteroidDailyDoseChange: FilterCondition.SteroidDailyDoseChange.filter((item) => Boolean(item)),
IsThisWeekAdl: FilterCondition.IsThisWeekAdl,
PharyngealSymptoms: FilterCondition.PharyngealSymptoms,
AntibodyType: FilterCondition.AntibodyType,
ThyroidAbnormal: FilterCondition.ThyroidAbnormal,
PreviousConvulsion: FilterCondition.PreviousConvulsion,
TraditionalImmunosuppressant: FilterCondition.TraditionalImmunosuppressant,
MedicalInsuranceType: FilterCondition.MedicalInsuranceType,
BindStartDate: FilterCondition.BindStartDate, BindStartDate: FilterCondition.BindStartDate,
BindEndDate: FilterCondition.BindEndDate, BindEndDate: FilterCondition.BindEndDate,
ConfirmedIgg4: FilterCondition.ConfirmedIgg4,
MedicationType: FilterCondition.MedicationType.split(',').map(Number),
}) })
}, },
}) })

113
src/doctor/pages/d_taskList/index.wxml

@ -1,9 +1,6 @@
<view <view class="page">
class="page"
style="padding-top: {{bottom}}px;background: url({{imageUrl}}/doctor/my-bg.png?t={{Timestamp}}) no-repeat top center / 100% 740rpx"
>
<navBar title="任务列表" background="{{background}}" back bind:back="handleBack" fixed></navBar>
<view class="card" wx:for="{{list}}" wx:key="Id" bind:tap="handleDetail" data-id="{{item.Id}}"> <view class="card" wx:for="{{list}}" wx:key="Id" bind:tap="handleDetail" data-id="{{item.Id}}">
<view class="card-body">
<view class="c-header"> <view class="c-header">
<view class="id">ID:{{item.IdNumber}}</view> <view class="id">ID:{{item.IdNumber}}</view>
<view class="status status{{item.Status}}">{{Status[item.Status]}}</view> <view class="status status{{item.Status}}">{{Status[item.Status]}}</view>
@ -15,107 +12,42 @@
</view> </view>
<view class="row"> <view class="row">
<view class="label">发布时间</view> <view class="label">发布时间</view>
<view class="content"> <view class="content">{{item.SendTime}}</view>
{{item.SendTime}}
<image
wx:if="{{item.Status==='1'}}"
class="icon"
src="{{imageUrl}}/doctor/icon-date.png?t={{Timestamp}}"
></image>
</view>
</view> </view>
<view class="row"> <view class="row">
<view class="label">筛选条件</view> <view class="label">筛选条件</view>
<view class="content"> <view class="content">
<view class="f-row" wx:if="{{item.FilterCondition.Search}}"> <view class="f-row" wx:if="{{item.FilterCondition.SelectAll === 1}}">
<view class="f-label">搜索:</view> <view class="f-label">选择范围:</view>
<view class="f-content">{{item.FilterCondition.Search}}</view> <view class="f-content">全部患者</view>
</view> </view>
<block wx:else>
<view class="f-row" wx:if="{{item.FilterCondition.BindStartDate || item.FilterCondition.BindEndDate}}"> <view class="f-row" wx:if="{{item.FilterCondition.BindStartDate || item.FilterCondition.BindEndDate}}">
<view class="f-label">绑定时间:</view> <view class="f-label">绑定时间:</view>
<view class="f-content">{{item.FilterCondition.BindStartDate || '---'}}~{{item.FilterCondition.BindEndDate || '---'}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.IsThisWeekAdl}}">
<view class="f-label">本周ADL测评:</view>
<view class="f-content">{{IsThisWeekAdlObj[item.FilterCondition.IsThisWeekAdl]}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.HasAdl}}">
<view class="f-label">是否有ADL测评:</view>
<view class="f-content">{{HasAdlObj[item.FilterCondition.HasAdl]}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.AdlTime}}">
<view class="f-label">ADL测评间隔时长:</view>
<view class="f-content">{{AdlTimeObj[item.FilterCondition.AdlTime]}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.DoubleStandard}}">
<view class="f-label">是否双达标:</view>
<view class="f-content">{{DoubleStandardObj[item.FilterCondition.DoubleStandard]}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.AdlScore.length}}">
<view class="f-label">ADL评分(多选):</view>
<view class="f-content">
<block wx:for="{{item.FilterCondition.AdlScore}}" wx:for-item="subItem" wx:key="subItem">
{{AdlScorceObj[subItem]}}{{index < item.FilterCondition.AdlScore.length - 1 ? ',' : ''}}
</block>
</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.AdlScoreChange.length}}">
<view class="f-label">ADL分数变化(多选):</view>
<view class="f-content"> <view class="f-content">
<block wx:for="{{item.FilterCondition.AdlScoreChange}}" wx:for-item="subItem" wx:key="subItem"> <view>
{{AdlScoreChangeObj[subItem]}}{{index < item.FilterCondition.AdlScoreChange.length - 1 ? ',' : ''}} {{item.FilterCondition.BindStartDate || '---'}}~{{item.FilterCondition.BindEndDate || '---'}}
</block>
<block wx:if="{{item.FilterCondition.AdlScoreChangeType}}">
{{AdlScoreChangeTypeObj[item.FilterCondition.AdlScoreChangeType]}}
</block>
<block wx:if="{{item.FilterCondition.AdlScoreChangeType===2}}">
({{item.FilterCondition.AdlScoreChangeMin}}-{{item.FilterCondition.AdlScoreChangeMax}})
</block>
</view> </view>
</view> </view>
<view class="f-row" wx:if="{{item.FilterCondition.SteroidDailyDose.length}}">
<view class="f-label">激素用量(多选):</view>
<view class="f-content">
<block wx:for="{{item.FilterCondition.SteroidDailyDose}}" wx:for-item="subItem" wx:key="subItem">
{{SteroidDailyDoseObj[subItem]}}{{index < item.FilterCondition.SteroidDailyDose.length - 1 ? ',' : ''}}
</block>
</view> </view>
<view class="f-row" wx:if="{{item.FilterCondition.ConfirmedIgg4}}">
<view class="f-label">确诊IgG4-RD:</view>
<view class="f-content">{{ConfirmedIgg4Obj[item.FilterCondition.ConfirmedIgg4]}}</view>
</view> </view>
<view class="f-row" wx:if="{{item.FilterCondition.SteroidDailyDoseChange.length}}"> <view class="f-row" wx:if="{{item.FilterCondition.MedicationTypeArr.length}}">
<view class="f-label">激素变化(多选):</view> <view class="f-label">正在使用的药物:</view>
<view class="f-content"> <view class="f-content">
<block wx:for="{{item.FilterCondition.SteroidDailyDoseChange}}" wx:for-item="subItem" wx:key="subItem"> <view wx:for="{{item.FilterCondition.MedicationTypeArr}}" wx:for-item="subItem" wx:key="subItem">
{{SteroidDailyDoseChangeObj[subItem]}}{{index < item.FilterCondition.SteroidDailyDoseChange.length - 1 ? {{MedicationTypeObj[subItem]}}
',' : ''}}
</block>
</view>
</view> </view>
<view class="f-row" wx:if="{{item.FilterCondition.PharyngealSymptoms}}">
<view class="f-label">延髓症状:</view>
<view class="f-content">{{PharyngealSymptomsObj[item.FilterCondition.PharyngealSymptoms]}}</view>
</view> </view>
<view class="f-row" wx:if="{{item.FilterCondition.AntibodyType}}">
<view class="f-label">抗体类型:</view>
<view class="f-content">{{AntibodyTypeObj[item.FilterCondition.AntibodyType]}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.ThyroidAbnormal}}">
<view class="f-label">胸腺异常:</view>
<view class="f-content">{{ThyroidAbnormalObj[item.FilterCondition.ThyroidAbnormal]}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.PreviousConvulsion}}">
<view class="f-label">既往发生危象:</view>
<view class="f-content">{{PreviousConvulsionObj[item.FilterCondition.PreviousConvulsion]}}</view>
</view>
<view class="f-row" wx:if="{{item.FilterCondition.TraditionalImmunosuppressant}}">
<view class="f-label">传统免疫抑制剂:</view>
<view class="f-content">
{{TraditionalImmunosuppressantObj[item.FilterCondition.TraditionalImmunosuppressant]}}
</view> </view>
</block>
</view> </view>
<view class="f-row" wx:if="{{item.FilterCondition.MedicalInsuranceType}}">
<view class="f-label">医保类型:</view>
<view class="f-content">{{MedicalInsuranceTypeObj[item.FilterCondition.MedicalInsuranceType]}}</view>
</view> </view>
<view class="row">
<view class="label">模板类型</view>
<view class="content">{{TemplateTypeObj[item.TemplateType]}}</view>
</view> </view>
</view> </view>
</view> </view>
@ -137,7 +69,8 @@
</view> </view>
</view> </view>
<pagination pagination="{{pagination}}"></pagination> <pagination pagination="{{pagination}}"></pagination>
<view class="create" bind:tap="handleCreate">
<image class="icon" src="{{imageUrl}}/doctor/icon-create.png?t={{Timestamp}}"></image>
</view> </view>
<view class="footer" bind:tap="handleCreate">
<view class="btn">新建群发消息</view>
</view> </view>

1
src/doctor/pages/d_userInfo/index.ts

@ -57,6 +57,7 @@ Page({
onLoad() {}, onLoad() {},
onShow() { onShow() {
app.waitLogin().then((_res) => { app.waitLogin().then((_res) => {
app.mpBehavior({ doctor: true, PageName: 'PG_DoctorUserInfo' })
this.getDetail() this.getDetail()
}) })
}, },

9
src/gift/pages/record/index.json

@ -0,0 +1,9 @@
{
"navigationBarTitleText": "就诊记录",
"navigationBarBackgroundColor": "#F6F6F6",
"usingComponents": {
"uploadFile": "/components/uploadFile/index",
"van-icon": "@vant/weapp/icon/index",
"toast": "/components/toast/index"
}
}

607
src/gift/pages/record/index.scss

@ -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%);
}
}
}

466
src/gift/pages/record/index.ts

@ -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 {}

418
src/gift/pages/record/index.wxml

@ -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>

7
src/gift/pages/recordDetail/index.json

@ -0,0 +1,7 @@
{
"navigationBarTitleText": "就诊记录详情页",
"navigationBarBackgroundColor": "#F6F6F6",
"usingComponents": {
"van-icon": "@vant/weapp/icon/index"
}
}

189
src/gift/pages/recordDetail/index.scss

@ -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);
}
}
}

158
src/gift/pages/recordDetail/index.ts

@ -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 {}

68
src/gift/pages/recordDetail/index.wxml

@ -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>

10
src/gift/pages/recordList/index.json

@ -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"
}
}

305
src/gift/pages/recordList/index.scss

@ -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%);
}
}
}

351
src/gift/pages/recordList/index.ts

@ -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 {}

99
src/gift/pages/recordList/index.wxml

@ -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>

BIN
src/images/avatar1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
src/images/bg38.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

BIN
src/images/bg39.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/images/bg40.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
src/images/bg41.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
src/images/bg42.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
src/images/bg43.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
src/images/bg44.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
src/images/icon108.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

BIN
src/images/icon109.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
src/images/icon110.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
src/images/icon111.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

BIN
src/images/icon112.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

BIN
src/images/icon113.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
src/images/icon114.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/icon115.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

BIN
src/images/icon116.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/icon117.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/images/icon118.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/images/icon119.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
src/images/icon120.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/images/icon121.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/images/icon122.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/icon123.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/icon124.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/images/icon125.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
src/images/icon126.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/images/icon127.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/images/icon128.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
src/images/icon129.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
src/images/icon130.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/images/icon131.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
src/images/icon132.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/images/icon133.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
src/images/icon134.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/icon135.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/icon136.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/images/icon137.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/images/icon138.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/images/icon139.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/images/icon140.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
src/images/icon141.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/images/icon142.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save