Browse Source

接口联调

master
kola-web 5 days ago
parent
commit
9523f9c27e
  1. 250
      api-doc/PSVT协议模块接口文档.md
  2. 848
      api-doc/psvt-patient-api-doc.md
  3. 2
      project.config.json
  4. 32
      project.private.config.json
  5. 14
      src/app.json
  6. 14
      src/app.ts
  7. 10
      src/doc/pages/doc/index.json
  8. 46
      src/doc/pages/doc/index.scss
  9. 103
      src/doc/pages/doc/index.ts
  10. 32
      src/doc/pages/doc/index.wxml
  11. BIN
      src/images/icon40.png
  12. BIN
      src/images/icon41.png
  13. BIN
      src/images/icon42.png
  14. BIN
      src/images/icon9-active.png
  15. 12
      src/pages/myCollection/index.json
  16. 209
      src/pages/myCollection/index.scss
  17. 111
      src/pages/myCollection/index.ts
  18. 74
      src/pages/myCollection/index.wxml
  19. 10
      src/pages/myComment/index.json
  20. 155
      src/pages/myComment/index.scss
  21. 103
      src/pages/myComment/index.ts
  22. 45
      src/pages/myComment/index.wxml
  23. 2
      src/pages/myWall/index.scss
  24. 10
      src/pages/userInfo/index.json
  25. 92
      src/pages/userInfo/index.scss
  26. 103
      src/pages/userInfo/index.ts
  27. 60
      src/pages/userInfo/index.wxml
  28. 1
      src/pages/wall/index.wxml
  29. 4
      src/pages/wallDetail/index.json
  30. 68
      src/pages/wallDetail/index.scss
  31. 1
      src/pages/wallDetail/index.ts
  32. 38
      src/pages/wallDetail/index.wxml
  33. 3
      typings/index.d.ts

250
api-doc/PSVT协议模块接口文档.md

@ -0,0 +1,250 @@
# PSVT协议模块接口文档
## 概述
协议模块支持4种协议类型:隐私协议、用户协议、法律声明、健康敏感信息处理与AI服务授权告知书。初始4条数据,无创建接口,仅支持修改更新文案。
所有接口返回格式统一为:
```json
{
"code": 0,
"message": "success",
"data": {}
}
```
协议类型常量值:
| 常量值 | 含义 |
|--------|------|
| 1 | 隐私协议 |
| 2 | 用户协议 |
| 3 | 法律声明 |
| 4 | 健康敏感信息处理与AI服务授权告知书 |
---
## 一、小程序端接口
### 1. 按类型获取协议详情
- **路由**: `?r=psvt/agreement/get-by-type`
- **请求方式**: GET
- **是否需要登录**: 否(白名单路由)
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| type | int | 是 | 协议类型:1-隐私协议,2-用户协议,3-法律声明,4-健康敏感信息处理与AI服务授权告知书 |
#### 响应示例
```json
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"type": 1,
"title": "隐私协议",
"content": "隐私协议内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
}
}
```
#### data字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | int | 协议ID |
| type | int | 协议类型 |
| title | string | 协议标题 |
| content | string | 协议内容(富文本) |
| version | string | 版本号 |
| updateTime | string | 最后更新时间 |
#### 错误码
| code | 说明 |
|------|------|
| 1 | 协议类型参数无效 |
| 1 | 协议不存在 |
---
### 2. 获取协议列表
- **路由**: `?r=psvt/agreement/get-list`
- **请求方式**: GET
- **是否需要登录**: 否(白名单路由)
#### 请求参数
#### 响应示例
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"type": 1,
"title": "隐私协议",
"content": "隐私协议内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
},
{
"id": 2,
"type": 2,
"title": "用户协议",
"content": "用户协议内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
},
{
"id": 3,
"type": 3,
"title": "法律声明",
"content": "法律声明内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
},
{
"id": 4,
"type": 4,
"title": "健康敏感信息处理与AI服务授权告知书",
"content": "健康敏感信息处理与AI服务授权告知书内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
}
]
}
```
#### data字段说明
data为数组类型,每个元素字段同"按类型获取协议详情"接口的data字段。
---
## 二、PC后台管理端接口
### 1. 获取协议列表
- **路由**: `?r=psvtw/agreement/get-list`
- **请求方式**: GET
- **是否需要登录**: 是
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| search | string | 否 | 搜索关键词(按标题模糊搜索) |
| page | int | 否 | 页码,默认1 |
| count | int | 否 | 每页条数,默认10 |
#### 响应示例
```json
{
"code": 0,
"message": "success",
"data": {
"total": 4,
"pages": 1,
"list": [
{
"id": 1,
"type": 1,
"title": "隐私协议",
"content": "隐私协议内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
},
{
"id": 2,
"type": 2,
"title": "用户协议",
"content": "用户协议内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
},
{
"id": 3,
"type": 3,
"title": "法律声明",
"content": "法律声明内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
},
{
"id": 4,
"type": 4,
"title": "健康敏感信息处理与AI服务授权告知书",
"content": "健康敏感信息处理与AI服务授权告知书内容...",
"version": "V1.0",
"updateTime": "2026-07-17 10:00:00"
}
]
}
}
```
#### data字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| total | int | 总记录数 |
| pages | int | 总页数 |
| list | array | 协议列表,每个元素字段同小程序端 |
---
### 2. 更新协议文案
- **路由**: `?r=psvtw/agreement/update`
- **请求方式**: POST
- **是否需要登录**: 是
- **事务**: 是
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | int | 是 | 协议ID |
| title | string | 否 | 协议标题 |
| content | string | 否 | 协议内容(富文本) |
> 注:title和content至少传一个,传入的字段才会更新
#### 响应示例
```json
{
"code": 0,
"message": "更新成功",
"data": {
"id": 1
}
}
```
#### data字段说明
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | int | 更新的协议ID |
#### 错误码
| code | 说明 |
|------|------|
| 1 | 用户未登录 |
| 1 | 协议ID不能为空 |
| 1 | 协议不存在 |
| 1 | 更新协议失败 |

848
api-doc/psvt-patient-api-doc.md

@ -0,0 +1,848 @@
# PSVT患者模块接口文档
## 一、小程序端接口
### 1.1 初始化登录 `GET ?r=psvt/user/init-login`
**说明**: 小程序端通过wx.login获取code,发送到服务端完成登录初始化,返回登录态及用户状态信息。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | wx.login获取的登录凭证 |
| channelId | int | 否 | 扫码渠道ID |
**响应示例**:
```json
{
"code": 0,
"data": {
"loginState": "xxx",
"isLogin": true,
"isReg": true,
"userId": "xxx",
"openId": "xxx",
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| loginState | string | 登录态凭证,后续接口需携带 |
| isLogin | bool | 是否已登录 |
| userId | string | 用户ID |
| openId | string | 小程序OpenId |
| isPhoneRegistered | bool | 是否已手机号注册 |
| isInfoSubmitted | bool | 是否已提交信息 |
**业务逻辑**:
1. 调用微信接口获取openid和session_key
2. 创建/更新PSVT用户记录(t_psvt_user_info)
3. 若有channelId参数,更新ScanChannelId(最终渠道,每次扫码都更新)
4. 首次打开时记录FirstOpenTime
5. 返回用户状态信息(isPhoneRegistered、isInfoSubmitted)
---
### 1.2 手机号授权注册 `POST ?r=psvt/patient/phone-register`
**说明**: 解密微信加密数据获取手机号,完成手机号注册。事务操作。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| encryptedData | string | 是 | 微信加密手机号数据 |
| iv | string | 是 | 微信加密iv |
| channelId | int | 否 | 手机号注册时的渠道ID |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"telephone": "138xxxx1234",
"isPhoneRegistered": true
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| telephone | string | 注册的手机号 |
| isPhoneRegistered | bool | 是否已手机号注册 |
**业务逻辑**:
1. 解密微信加密数据获取手机号
2. 更新t_psvt_user_info的Telephone、IsPhoneRegistered=1、PhoneRegisterTime
3. 若有channelId,记录TelChannelId(首次手机号注册渠道,仅记录首次,不更新)
4. 若有channelId,记录邀约记录(InviteType=2手机号注册)并更新统计表
5. 更新IsFirstOpenPhoneRegistered(首次打开时间和手机号注册时间是同一天时为1)
6. 更新IsPhoneRegisteredNotLost(手机号注册时间和提交信息时间是同一天时为1)
7. 使用事务
---
### 1.3 患者信息提交注册 `POST ?r=psvt/patient/register`
**说明**: 提交年龄段、性别、确诊状态、手术状态等信息完成注册。事务操作。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ageRange | int | 是 | 年龄段:1-18~28岁,2-29~40岁,3-41~50岁,4-50岁以上 |
| gender | int | 是 | 性别:1-男,2-女 |
| provinceId | int | 否 | 省份ID |
| provinceName | string | 否 | 省份名称 |
| cityId | int | 否 | 市ID |
| cityName | string | 否 | 市名称 |
| diagnosisStatus | int | 是 | 确诊状态:1-疑似PSVT正在了解中,2-已确诊PSVT,3-心悸/心跳过快 尚未就诊,4-亲友相关 想了解更多 |
| diagnosisTime | string | 否 | 确诊时间(YYYY-MM格式) |
| surgeryStatus | int | 是 | 手术状态:1-已接收射频消融/相关手术,2-尚未接收手术,3-正在评估 |
| channelId | int | 否 | 提交信息渠道ID |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"userId": "xxx",
"nickName": "abcd12345678",
"avatar": "https://psvt.example.com/avatar/3.png",
"isRegistered": true
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| userId | string | 用户ID |
| nickName | string | 随机生成的昵称(4字母+8数字) |
| avatar | string | 随机分配的头像URL |
| isRegistered | bool | 是否已注册 |
**业务逻辑**:
1. 生成随机昵称(4随机字母+8随机数字)
2. 随机分配头像(10张图片池中随机选1)
3. 创建t_psvt_patient_info记录(患者属性)
4. 更新t_psvt_user_info的PatientId、IsInfoSubmitted=1、InfoSubmitTime、SubmitChannelId
5. 更新IsFirstOpenInfoSubmitted(首次打开时间和信息提交时间是同一天时为1)
6. 更新IsPhoneRegisteredNotLost(手机号注册时间和信息提交时间是同一天时为1)
7. 若有channelId,记录邀约记录(InviteType=3信息提交)并更新统计表
8. 使用事务
---
### 1.4 获取我的信息 `GET ?r=psvt/patient/my-info`
**说明**: 返回当前登录用户的完整信息(用户信息+患者属性),小驼峰格式。
**请求参数**: 无(通过loginState获取userId)
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"userId": "xxx",
"openId": "xxx",
"telephone": "138xxxx1234",
"isPhoneRegistered": true,
"isInfoSubmitted": true,
"isHeartRateTips": true,
"firstOpenTime": "2023-01-01 10:00:00",
"phoneRegisterTime": "2023-01-01 10:05:00",
"infoSubmitTime": "2023-01-01 10:10:00",
"nickName": "abcd12345678",
"avatar": "https://psvt.example.com/avatar/3.png",
"ageRange": 1,
"gender": 1,
"provinceName": "北京",
"cityName": "北京",
"diagnosisStatus": 2,
"diagnosisTime": "2023-06",
"surgeryStatus": 1
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| userId | string | 用户ID |
| openId | string | 小程序OpenId |
| telephone | string | 手机号 |
| isPhoneRegistered | bool | 是否已手机号注册 |
| isInfoSubmitted | bool | 是否已提交信息 |
| isHeartRateTips | bool | 是否开启心率提醒 |
| firstOpenTime | string | 首次打开时间 |
| phoneRegisterTime | string | 手机号注册时间 |
| infoSubmitTime | string | 信息提交时间 |
| nickName | string | 昵称(未提交信息时为null) |
| avatar | string | 头像URL(未提交信息时为null) |
| ageRange | int | 年龄段(未提交信息时为null) |
| gender | int | 性别(未提交信息时为null) |
| provinceName | string | 省份名称(未提交信息时为null) |
| cityName | string | 市名称(未提交信息时为null) |
| diagnosisStatus | int | 确诊状态(未提交信息时为null) |
| diagnosisTime | string | 确诊时间(未提交信息时为null) |
| surgeryStatus | int | 手术状态(未提交信息时为null) |
---
### 1.5 更新我的信息 `POST ?r=psvt/patient/update-my-info`
**说明**: 更新当前登录用户的个人信息。若已确诊PSVT(DiagnosisStatus=2),确诊状态和手术状态不可修改。事务操作。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| avatar | string | 否 | 头像URL |
| nickName | string | 否 | 昵称 |
| telephone | string | 否 | 手机号 |
| ageRange | int | 否 | 年龄段 |
| gender | int | 否 | 性别 |
| provinceId | int | 否 | 省份ID |
| provinceName | string | 否 | 省份名称 |
| cityId | int | 否 | 市ID |
| cityName | string | 否 | 市名称 |
| diagnosisStatus | int | 否 | 确诊状态(已确诊PSVT后不可修改) |
| diagnosisTime | string | 否 | 确诊时间 |
| surgeryStatus | int | 否 | 手术状态(已确诊PSVT后不可修改) |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"userId": "xxx",
"openId": "xxx",
"telephone": "138xxxx1234",
"isPhoneRegistered": true,
"isInfoSubmitted": true,
"isHeartRateTips": true,
"firstOpenTime": "2023-01-01 10:00:00",
"phoneRegisterTime": "2023-01-01 10:05:00",
"infoSubmitTime": "2023-01-01 10:10:00",
"nickName": "newNickname",
"avatar": "https://...",
"ageRange": 2,
"gender": 1,
"provinceName": "上海",
"cityName": "上海",
"diagnosisStatus": 2,
"diagnosisTime": "2023-06",
"surgeryStatus": 1
}
}
```
**响应字段说明**: 同1.4获取我的信息
**业务逻辑**:
1. 若已确诊PSVT(DiagnosisStatus=2),则diagnosisStatus和surgeryStatus不可修改
2. 更新t_psvt_patient_info患者属性
3. 若更新了手机号,同步更新t_psvt_user_info的Telephone
4. 使用事务
---
### 1.6 渠道码扫码记录 `POST ?r=psvt/?r=channel/scan-channel`
**说明**: 记录扫码渠道信息,更新用户的ScanChannelId。事务操作。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| channelId | int | 是 | 渠道码ID |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"recorded": true
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| recorded | bool | 是否记录成功 |
**业务逻辑**:
1. 验证渠道码存在
2. 更新用户ScanChannelId(最终渠道,每次扫码都更新)
3. 记录邀约记录(InviteType=1用户打开)
4. 更新统计表
5. 使用事务
---
### 1.7 获取基础数据列表 `GET ?r=psvt/patient/base-data`
**说明**: 返回性别、年龄段、确诊状态、手术状态等枚举数据,供前端渲染下拉选项。
**请求参数**: 无
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"genderList": [{"1": "男"}, {"2": "女"}],
"ageRangeList": [{"1": "18~28岁"}, {"2": "29~40岁"}, {"3": "41~50岁"}, {"4": "50岁以上"}],
"diagnosisStatusList": [{"1": "疑似PSVT正在了解中"}, {"2": "已确诊PSVT"}, {"3": "心悸/心跳过快 尚未就诊"}, {"4": "亲友相关 想了解更多"}],
"surgeryStatusList": [{"1": "已接收射频消融/相关手术"}, {"2": "尚未接收手术"}, {"3": "正在评估"}]
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| genderList | array | 性别列表,每项为 {value: "label"} |
| ageRangeList | array | 年龄段列表,每项为 {value: "label"} |
| diagnosisStatusList | array | 确诊状态列表,每项为 {value: "label"} |
| surgeryStatusList | array | 手术状态列表,每项为 {value: "label"} |
**genderList值说明**:
| 值 | 说明 |
|---|---|
| 1 | 男 |
| 2 | 女 |
**ageRangeList值说明**:
| 值 | 说明 |
|---|---|
| 1 | 18~28岁 |
| 2 | 29~40岁 |
| 3 | 41~50岁 |
| 4 | 50岁以上 |
**diagnosisStatusList值说明**:
| 值 | 说明 |
|---|---|
| 1 | 疑似PSVT正在了解中 |
| 2 | 已确诊PSVT |
| 3 | 心悸/心跳过快 尚未就诊 |
| 4 | 亲友相关 想了解更多 |
**surgeryStatusList值说明**:
| 值 | 说明 |
|---|---|
| 1 | 已接收射频消融/相关手术 |
| 2 | 尚未接收手术 |
| 3 | 正在评估 |
---
## 二、后台管理端接口
### 2.1 患者列表 `GET ?r=patient/list`
**说明**: 获取PSVT患者列表,含分页、搜索、渠道筛选及汇总统计。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| search | string | 否 | 搜索关键字(手机号/UserId/省市) |
| channelId | int | 否 | 渠道ID筛选 |
| page | int | 否 | 页码,默认1 |
| count | int | 否 | 每页数量,默认10 |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"page": 1,
"pages": 10,
"count": 100,
"summary": {
"totalOpenCount": 1920,
"phoneRegisteredCount": 1900,
"phoneRegisteredRate": "19.9%",
"infoSubmittedCount": 1082,
"infoSubmittedRate": "56.3%",
"diagnosedPsvtCount": 980,
"diagnosedPsvtRate": "90.2%",
"firstOpenPhoneRegisteredCount": 800,
"firstOpenInfoSubmittedCount": 800,
"phoneRegisteredNotLostCount": 700
},
"list": [
{
"userId": "xxx",
"telephone": "138xxxx1234",
"nickName": "abcd12345678",
"avatar": "https://...",
"ageRange": 1,
"gender": 1,
"provinceName": "北京",
"cityName": "北京",
"diagnosisStatus": 2,
"diagnosisTime": "2023-06",
"surgeryStatus": 1,
"firstOpenTime": "2023-01-01 10:00:00",
"phoneRegisterTime": "2023-01-01 10:05:00",
"infoSubmitTime": "2023-01-01 10:10:00",
"isFirstOpenPhoneRegistered": true,
"isFirstOpenInfoSubmitted": true,
"isPhoneRegisteredNotLost": true,
"scanChannelId": 1,
"scanChannelName": "渠道A",
"telChannelId": 2,
"telChannelName": "渠道B",
"submitChannelId": 3,
"submitChannelName": "渠道C"
}
]
}
}
```
**响应字段说明(data层级)**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| page | int | 当前页码 |
| pages | int | 总页数 |
| count | int | 总记录数 |
| summary | object | 汇合统计数据 |
| list | array | 患者列表 |
**summary字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| totalOpenCount | int | 总打开数 |
| phoneRegisteredCount | int | 手机号注册数 |
| phoneRegisteredRate | string | 手机号注册率(百分比) |
| infoSubmittedCount | int | 信息提交数 |
| infoSubmittedRate | string | 信息提交率(百分比) |
| diagnosedPsvtCount | int | 确诊PSVT数 |
| diagnosedPsvtRate | string | 确诊PSVT率(百分比) |
| firstOpenPhoneRegisteredCount | int | 首次打开完成手机号注册数 |
| firstOpenInfoSubmittedCount | int | 首次打开完成信息填写数 |
| phoneRegisteredNotLostCount | int | 手机号注册后未流失数 |
**list行字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| userId | string | 用户ID |
| telephone | string | 手机号 |
| nickName | string | 昵称 |
| avatar | string | 头像URL |
| ageRange | int | 年龄段 |
| gender | int | 性别 |
| provinceName | string | 省份名称 |
| cityName | string | 市名称 |
| diagnosisStatus | int | 确诊状态 |
| diagnosisTime | string | 确诊时间 |
| surgeryStatus | int | 手术状态 |
| firstOpenTime | string | 首次打开时间 |
| phoneRegisterTime | string | 手机号注册时间 |
| infoSubmitTime | string | 信息提交时间 |
| isFirstOpenPhoneRegistered | bool | 首次打开完成手机号注册 |
| isFirstOpenInfoSubmitted | bool | 首次打开完成信息填写 |
| isPhoneRegisteredNotLost | bool | 手机号注册后未流失 |
| scanChannelId | int | 扫码渠道ID |
| scanChannelName | string | 扫码渠道名称 |
| telChannelId | int | 手机号注册渠道ID |
| telChannelName | string | 手机号注册渠道名称 |
| submitChannelId | int | 提交信息渠道ID |
| submitChannelName | string | 提交信息渠道名称 |
---
### 2.2 患者详情 `GET ?r=patient/detail`
**说明**: 根据用户信息表ID获取完整的用户+患者详情数据。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | int | 是 | 用户信息表ID |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"userId": "xxx",
"openId": "xxx",
"telephone": "138xxxx1234",
"isPhoneRegistered": true,
"isInfoSubmitted": true,
"isHeartRateTips": true,
"firstOpenTime": "2023-01-01 10:00:00",
"phoneRegisterTime": "2023-01-01 10:05:00",
"infoSubmitTime": "2023-01-01 10:10:00",
"nickName": "abcd12345678",
"avatar": "https://...",
"ageRange": 1,
"gender": 1,
"provinceName": "北京",
"cityName": "北京",
"diagnosisStatus": 2,
"diagnosisTime": "2023-06",
"surgeryStatus": 1,
"scanChannelId": 1,
"scanChannelName": "渠道A",
"telChannelId": 2,
"telChannelName": "渠道B",
"submitChannelId": 3,
"submitChannelName": "渠道C"
}
}
```
**响应字段说明**: 同1.4获取我的信息,额外包含渠道信息字段(scanChannelId/scanChannelName、telChannelId/telChannelName、submitChannelId/submitChannelName)
---
### 2.3 渠道码列表 `GET ?r=?r=channel/list`
**说明**: 获取所有有效渠道码列表。
**请求参数**: 无
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": [
{
"channelId": 1,
"channelName": "渠道A",
"channelDesc": "渠道A说明",
"miniCodeUrl": "https://xxx/minicode_1.png"
}
]
}
```
**响应字段说明(每行)**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| channelId | int | 渠道ID |
| channelName | string | 渠道名称 |
| channelDesc | string | 渠道码说明 |
| miniCodeUrl | string | 小程序码图片URL |
---
### 2.4 创建渠道码 `POST ?r=channel/create`
**说明**: 创建新的渠道码。事务操作。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| channelName | string | 是 | 渠道名称 |
| channelDesc | string | 否 | 渠道码说明 |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"channelId": 1,
"channelName": "渠道A"
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| channelId | int | 新创建的渠道ID |
| channelName | string | 渠道名称 |
---
### 2.5 更新渠道码 `POST ?r=channel/update`
**说明**: 更新渠道码信息。事务操作。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| channelId | int | 是 | 渠道ID |
| channelName | string | 否 | 渠道名称 |
| channelDesc | string | 否 | 渠道码说明 |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"channelId": 1,
"channelName": "渠道A(已更新)"
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| channelId | int | 渠道ID |
| channelName | string | 更新后的渠道名称 |
---
### 2.6 获取小程序渠道码 `GET ?r=channel/get-minicode`
**说明**: 根据渠道ID获取或生成小程序码图片URL。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| channelId | int | 是 | 渠道ID |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"channelId": 1,
"channelName": "渠道A",
"miniCodeUrl": "https://xxx/minicode_1.png"
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| channelId | int | 渠道ID |
| channelName | string | 渠道名称 |
| miniCodeUrl | string | 小程序码图片URL |
**业务逻辑**:
1. 若渠道已有小程序码URL(MiniCodeUrl),直接返回
2. 若没有,调用微信接口生成小程序码(scene参数包含渠道ID)
3. 保存生成的MiniCodeUrl到渠道记录
---
### 2.7 渠道码统计 `GET ?r=channel/stat`
**说明**: 根据日期范围和渠道ID获取渠道统计数据,从统计表获取。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| inviteDateBegin | string | 否 | 邀约日期开始(YYYY-MM-DD) |
| inviteDateEnd | string | 否 | 邀约日期结束(YYYY-MM-DD) |
| channelIds | string | 否 | 渠道ID筛选,逗号分隔(如"1,2,3") |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": [
{
"statDate": "2023-01-01",
"channelId": 1,
"channelName": "渠道A",
"openPatientCount": 100,
"phoneRegisteredCount": 80,
"infoSubmittedCount": 50,
"diagnosedPsvtCount": 30
}
]
}
```
**响应字段说明(每行)**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| statDate | string | 统计日期 |
| channelId | int | 渠道ID |
| channelName | string | 渠道名称 |
| openPatientCount | int | 邀约打开患者数 |
| phoneRegisteredCount | int | 邀约注册手机号患者数 |
| infoSubmittedCount | int | 邀约提交信息患者数 |
| diagnosedPsvtCount | int | 确诊PSVT患者数 |
---
### 2.8 渠道码原始数据列表 `GET ?r=channel/raw-list`
**说明**: 获取渠道邀约的原始记录列表,支持分页。通过JOIN t_psvt_user_info和t_psvt_patient_info获取用户属性和患者属性,不冗余存储。
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| inviteDateBegin | string | 否 | 邀约日期开始(YYYY-MM-DD) |
| inviteDateEnd | string | 否 | 邀约日期结束(YYYY-MM-DD) |
| channelId | int | 否 | 渠道ID筛选 |
| inviteType | int | 否 | 邀约类型筛选:1-用户打开,2-手机号注册,3-信息提交 |
| page | int | 否 | 页码,默认1 |
| count | int | 否 | 每页数量,默认10 |
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"page": 1,
"pages": 10,
"count": 100,
"list": [
{
"id": 1,
"inviteTime": "2023-01-01 10:00:00",
"channelId": 1,
"channelName": "渠道A",
"inviteType": 1,
"userId": "xxx",
"patientId": 5,
"nickName": "abcd12345678",
"gender": 1,
"ageRange": 1,
"provinceName": "北京",
"cityName": "北京",
"diagnosisStatus": 2,
"diagnosisTime": "2023-06",
"surgeryStatus": 1,
"telephone": "138xxxx1234",
"firstOpenTime": "2023-01-01 10:00:00",
"phoneRegisterTime": "2023-01-01 10:05:00",
"infoSubmitTime": "2023-01-01 10:10:00",
"isFirstOpenPhoneRegistered": true,
"isFirstOpenInfoSubmitted": true,
"isPhoneRegisteredNotLost": true,
"scanChannelId": 1,
"scanChannelName": "渠道A",
"telChannelId": 2,
"telChannelName": "渠道B",
"submitChannelId": 3,
"submitChannelName": "渠道C"
}
]
}
}
```
**响应字段说明(data层级)**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| page | int | 当前页码 |
| pages | int | 总页数 |
| count | int | 总记录数 |
| list | array | 原始数据列表 |
**list行字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 记录ID |
| inviteTime | string | 邀约时间 |
| channelId | int | 渠道ID |
| channelName | string | 渠道名称 |
| inviteType | int | 邀约类型 |
| userId | string | 用户ID |
| patientId | int | 患者ID |
| nickName | string | 昵称 |
| gender | int | 性别 |
| ageRange | int | 年龄段 |
| provinceName | string | 省份名称 |
| cityName | string | 市名称 |
| diagnosisStatus | int | 确诊状态 |
| diagnosisTime | string | 确诊时间 |
| surgeryStatus | int | 手术状态 |
| telephone | string | 手机号 |
| firstOpenTime | string | 首次打开时间 |
| phoneRegisterTime | string | 手机号注册时间 |
| infoSubmitTime | string | 信息提交时间 |
| isFirstOpenPhoneRegistered | bool | 首次打开完成手机号注册 |
| isFirstOpenInfoSubmitted | bool | 首次打开完成信息填写 |
| isPhoneRegisteredNotLost | bool | 手机号注册后未流失 |
| scanChannelId | int | 扫码渠道ID |
| scanChannelName | string | 扫码渠道名称 |
| telChannelId | int | 手机号注册渠道ID |
| telChannelName | string | 手机号注册渠道名称 |
| submitChannelId | int | 提交信息渠道ID |
| submitChannelName | string | 提交信息渠道名称 |
---
### 2.9 获取基础数据列表 `GET ?r=patient/base-data`
**说明**: 返回性别、年龄段、确诊状态、手术状态等枚举数据,供后台管理端渲染下拉选项。
**请求参数**: 无
**响应示例**:
```json
{
"code": 0,
"message": "success",
"data": {
"genderList": [{"1": "男"}, {"2": "女"}],
"ageRangeList": [{"1": "18~28岁"}, {"2": "29~40岁"}, {"3": "41~50岁"}, {"4": "50岁以上"}],
"diagnosisStatusList": [{"1": "疑似PSVT正在了解中"}, {"2": "已确诊PSVT"}, {"3": "心悸/心跳过快 尚未就诊"}, {"4": "亲友相关 想了解更多"}],
"surgeryStatusList": [{"1": "已接收射频消融/相关手术"}, {"2": "尚未接收手术"}, {"3": "正在评估"}]
}
}
```
**响应字段说明**:
| 字段名 | 类型 | 说明 |
|---|---|---|
| genderList | array | 性别列表,每项为 {value: "label"} |
| ageRangeList | array | 年龄段列表,每项为 {value: "label"} |
| diagnosisStatusList | array | 确诊状态列表,每项为 {value: "label"} |
| surgeryStatusList | array | 手术状态列表,每项为 {value: "label"} |
---

2
project.config.json

@ -66,7 +66,7 @@
], ],
"include": [] "include": []
}, },
"appid": "wxfc95e2961d481f58", "appid": "wx903aa29742dad363",
"scripts": { "scripts": {
"beforeCompile": "pnpm run beforeCompile", "beforeCompile": "pnpm run beforeCompile",
"beforePreview": "pnpm run beforeCompile", "beforePreview": "pnpm run beforeCompile",

32
project.private.config.json

@ -23,13 +23,41 @@
"miniprogram": { "miniprogram": {
"list": [ "list": [
{ {
"name": "我的帖子", "name": "隐私协议",
"pathName": "pages/myWall/index", "pathName": "doc/pages/doc/index",
"query": "", "query": "",
"scene": null, "scene": null,
"launchMode": "default" "launchMode": "default"
}, },
{ {
"name": "个人信息",
"pathName": "pages/userInfo/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "我的收藏",
"pathName": "pages/myCollection/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "我的评论",
"pathName": "pages/myComment/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "我的帖子",
"pathName": "pages/myWall/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "我的", "name": "我的",
"pathName": "pages/my/index", "pathName": "pages/my/index",
"query": "", "query": "",

14
src/app.json

@ -21,12 +21,22 @@
"pages/videoList/index", "pages/videoList/index",
"pages/videoDetail/index", "pages/videoDetail/index",
"pages/articleDetail/index", "pages/articleDetail/index",
"pages/myWall/index" "pages/myWall/index",
"pages/myComment/index",
"pages/myCollection/index",
"pages/userInfo/index"
], ],
"subPackages": [ "subPackages": [
{ {
"root": "doc", "root": "doc",
"pages": ["pages/doc1/index", "pages/doc2/index", "pages/doc3/index", "pages/doc4/index", "pages/doc5/index"] "pages": [
"pages/doc/index",
"pages/doc1/index",
"pages/doc2/index",
"pages/doc3/index",
"pages/doc4/index",
"pages/doc5/index"
]
} }
], ],
"tabBar": { "tabBar": {

14
src/app.ts

@ -25,15 +25,15 @@ App<IAppOption>({
globalData: { globalData: {
// dev // dev
// appid:wx903aa29742dad363 // appid:wx903aa29742dad363
// url: 'https://m.wtx.hbraas.com', url: 'https://m.psvt.hbraas.com',
// upFileUrl: 'https://m.wtx.hbraas.com/', upFileUrl: 'https://m.psvt.hbraas.com/',
// imageUrl: 'https://m.wtx.hbraas.com/GeneB/', imageUrl: 'https://m.psvt.hbraas.com/psvt/',
// pro // pro
// appid:wxfc95e2961d481f58 // appid:wxfc95e2961d481f58
url: 'https://m.hbgene.hbsaas.com', // url: 'https://m.hbgene.hbsaas.com',
upFileUrl: 'https://m.hbgene.hbsaas.com/', // upFileUrl: 'https://m.hbgene.hbsaas.com/',
imageUrl: 'https://m.hbgene.hbsaas.com/GeneB/', // imageUrl: 'https://m.hbgene.hbsaas.com/GeneB/',
Timestamp: new Date().getTime(), Timestamp: new Date().getTime(),
@ -71,7 +71,7 @@ App<IAppOption>({
success: (res) => { success: (res) => {
wx.ajax({ wx.ajax({
method: 'GET', method: 'GET',
url: '?r=wtx/user/init-login', url: '?r=psvt/user/init-login',
data: { data: {
code: res.code, code: res.code,
}, },

10
src/doc/pages/doc/index.json

@ -0,0 +1,10 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "隐私协议",
"usingComponents": {
"popup": "/components/popup/index",
"navbar": "/components/navbar/index",
"uploadFile": "/components/uploadFile/index",
"van-popup": "@vant/weapp/popup/index"
}
}

46
src/doc/pages/doc/index.scss

@ -0,0 +1,46 @@
page {
background-color: #f7fafc;
}
.page-title {
font-size: 36rpx;
color: rgba(51, 51, 51, 1);
}
.page {
padding-bottom: 260rpx;
.list {
padding: 40rpx;
.info-card {
padding: 32rpx;
background: rgba(255, 255, 255, 0.7);
border: 1rpx solid #fff;
border-radius: 32rpx;
}
.info-row {
display: flex;
align-items: start;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 1rpx solid #f1f5f9;
&:last-child {
border-bottom: none;
}
.info-label {
font-size: 32rpx;
line-height: 48rpx;
color: #1e293b;
}
.info-arrow {
margin-top: 8rpx;
color: #64748b;
margin-left: 8rpx;
}
}
}
}

103
src/doc/pages/doc/index.ts

@ -0,0 +1,103 @@
const app = getApp<IAppOption>()
Page({
data: {
popupShow: false,
popupType: '',
popupParams: {
position: 'bottom',
} as any,
topicShow: false,
replayShow: true,
fileList: [] as any[],
},
onLoad() {
app.waitLogin({ type: 0 }).then(() => {})
},
handlePopupOk() {
const { popupType } = this.data
if (popupType === 'argument') {
app.mpBehavior({ PageName: 'BTN_PATIENTLOG' })
wx.ajax({
method: 'POST',
url: '?r=wtx/user/agree-guest-privacy',
data: {
WorkerId: app.globalData.scene?.workerId || '',
},
}).then(() => {
this.setData({
popupShow: false,
popupType: '',
popupParams: {},
})
const waitBindDoctorId = app.globalData.waitBindDoctorId
if (waitBindDoctorId) {
this.handleBindDoctor(waitBindDoctorId)
}
})
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
wx.navigateTo({
url: `/patient/pages/login/index`,
})
})
}
},
handlePopupCancel() {
const { popupType } = this.data
if (popupType === 'argument') {
wx.exitMiniProgram()
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
app.globalData.waitBindDoctorId = ''
})
}
},
handleWechatWork() {
wx.navigateTo({
url: '/pages/wechatWork/index',
})
},
handleSetData(e) {
const { fileList } = this.data
this.setData({
fileList: [...fileList, ...e.detail],
})
},
handleDeleteFile(e) {
const { index } = e.detail
const { fileList } = this.data
this.setData({
fileList: fileList.filter((_, i) => i !== index),
})
},
topicClose() {
this.setData({
topicShow: false,
})
},
})
export {}

32
src/doc/pages/doc/index.wxml

@ -0,0 +1,32 @@
<navbar fixed custom-style="background:{{background}}" back>
<view class="page-title" slot="left">
<van-icon name="arrow-left" />
个人信息
</view>
</navbar>
<view
class="page"
style="background: url('/images/bg6.png') no-repeat top center/100% 478rpx;padding-top: {{pageTop}}px;"
>
<view class="list">
<view class="info-card">
<view class="info-row">
<text class="info-label">《隐私政策》</text>
<van-icon name="arrow" class="info-arrow" />
</view>
<view class="info-row">
<text class="info-label">《用户协议》</text>
<van-icon name="arrow" class="info-arrow" />
</view>
<view class="info-row">
<text class="info-label">《法律声明》</text>
<van-icon name="arrow" class="info-arrow" />
</view>
<view class="info-row">
<text class="info-label">《健康敏感信息处理与AI服务授权告知书》</text>
<van-icon name="arrow" class="info-arrow" />
</view>
</view>
</view>
</view>

BIN
src/images/icon40.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

BIN
src/images/icon41.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

BIN
src/images/icon42.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

BIN
src/images/icon9-active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

12
src/pages/myCollection/index.json

@ -0,0 +1,12 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "我的评论",
"usingComponents": {
"popup": "/components/popup/index",
"navbar": "/components/navbar/index",
"uploadFile": "/components/uploadFile/index",
"van-popup": "@vant/weapp/popup/index",
"van-tab": "@vant/weapp/tab/index",
"van-tabs": "@vant/weapp/tabs/index"
}
}

209
src/pages/myCollection/index.scss

@ -0,0 +1,209 @@
page {
background-color: #f7fafc;
}
.page-title {
font-size: 36rpx;
color: rgba(51, 51, 51, 1);
}
.page {
padding-bottom: 260rpx;
position: relative;
.page-header {
padding: 42rpx 30rpx 0;
.search {
display: flex;
align-items: center;
gap: 16rpx;
background-color: #fff;
border-radius: 180rpx;
padding: 0 32rpx;
.icon {
width: 40rpx;
height: 40rpx;
}
.input {
flex: 1;
padding: 18rpx 0;
line-height: 40rpx;
font-size: 28rpx;
color: rgba(30, 41, 59, 1);
}
.place-input {
color: rgba(148, 163, 184, 1);
}
}
}
.tabs {
.van-tabs__line {
width: 32rpx !important;
height: 6rpx;
border-radius: 1998rpx;
background: #2f82a9;
box-sizing: border-box;
}
.tab-active-class {
font-size: 32rpx;
font-weight: bold;
}
.tab-class {
font-size: 28rpx;
padding: 26rpx;
line-height: 44rpx;
}
.wrap-class {
height: 96rpx;
}
.nav-calss {
background-color: transparent;
height: 96rpx;
border: none;
}
}
.body1 {
padding: 24rpx 30rpx;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 48rpx 30rpx;
.card {
display: block;
width: 100%;
overflow: hidden;
.photo {
position: relative;
width: 100%;
height: 442rpx;
border-radius: 32rpx;
overflow: hidden;
.p-img {
display: block;
width: 100%;
height: 100%;
}
.stat {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 0 12rpx;
height: 104rpx;
box-sizing: border-box;
background: linear-gradient(180deg, rgba(2, 2, 2, 0) 0%, rgba(3, 3, 3, 0.11) 20.97%, #090909 100%);
backdrop-filter: opacity(67%);
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8rpx;
.s-item {
padding: 48rpx 12rpx 24rpx;
display: flex;
align-items: center;
gap: 8rpx;
font-size: 28rpx;
color: rgba(255, 255, 255, 1);
.icon {
width: 32rpx;
height: 32rpx;
}
}
}
}
.title {
margin-top: 24rpx;
height: 80rpx;
padding: 0 12rpx;
color: #2a3544;
font-size: 28rpx;
line-height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
}
.body2 {
margin: 32rpx 30rpx 0;
.list-card {
padding: 32rpx;
background-color: #fff;
border-radius: 32rpx;
display: flex;
gap: 32rpx;
.photo-wrap {
position: relative;
flex-shrink: 0;
width: 224rpx;
height: 160rpx;
border-radius: 16rpx;
overflow: hidden;
.photo {
display: block;
width: 100%;
height: 100%;
}
.type1 {
position: absolute;
bottom: 8rpx;
left: 8rpx;
padding: 4rpx 12rpx;
border-radius: 102rpx;
background-color: rgba(0, 0, 0, 0.4);
font-size: 18rpx;
color: #fff;
line-height: 27rpx;
}
.type2 {
position: absolute;
bottom: 8rpx;
left: 8rpx;
padding: 4rpx 12rpx;
border-radius: 102rpx;
background-color: rgba(0, 0, 0, 0.4);
font-size: 18rpx;
color: #fff;
line-height: 27rpx;
display: flex;
align-items: center;
gap: 4rpx;
background: linear-gradient(270deg, #439fc7 0%, #2f82a9 100%);
.icon {
width: 16rpx;
height: 16rpx;
}
}
}
.wrap {
.title {
font-size: 28rpx;
color: rgba(30, 41, 59, 1);
line-height: 40rpx;
font-weight: bold;
height: 80rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.op-wrap {
margin-top: 48rpx;
.op {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
color: rgba(234, 88, 26, 1);
.icon {
width: 28rpx;
height: 28rpx;
}
}
}
}
}
}
}

111
src/pages/myCollection/index.ts

@ -0,0 +1,111 @@
const app = getApp<IAppOption>()
Page({
data: {
popupShow: false,
popupType: '',
popupParams: {
position: 'bottom',
} as any,
topicShow: false,
replayShow: true,
fileList: [] as any[],
tab: 1,
offsetTop: 0,
},
onLoad() {
app.waitLogin({ type: 0 }).then(() => {})
},
onReady() {
const { statusBarHeight } = wx.getSystemInfoSync()
const navBarHeight = 44
this.setData({ offsetTop: statusBarHeight + navBarHeight })
},
handlePopupOk() {
const { popupType } = this.data
if (popupType === 'argument') {
app.mpBehavior({ PageName: 'BTN_PATIENTLOG' })
wx.ajax({
method: 'POST',
url: '?r=wtx/user/agree-guest-privacy',
data: {
WorkerId: app.globalData.scene?.workerId || '',
},
}).then(() => {
this.setData({
popupShow: false,
popupType: '',
popupParams: {},
})
const waitBindDoctorId = app.globalData.waitBindDoctorId
if (waitBindDoctorId) {
this.handleBindDoctor(waitBindDoctorId)
}
})
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
wx.navigateTo({
url: `/patient/pages/login/index`,
})
})
}
},
handlePopupCancel() {
const { popupType } = this.data
if (popupType === 'argument') {
wx.exitMiniProgram()
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
app.globalData.waitBindDoctorId = ''
})
}
},
handleWechatWork() {
wx.navigateTo({
url: '/pages/wechatWork/index',
})
},
handleSetData(e) {
const { fileList } = this.data
this.setData({
fileList: [...fileList, ...e.detail],
})
},
handleDeleteFile(e) {
const { index } = e.detail
const { fileList } = this.data
this.setData({
fileList: fileList.filter((_, i) => i !== index),
})
},
topicClose() {
this.setData({
topicShow: false,
})
},
})
export {}

74
src/pages/myCollection/index.wxml

@ -0,0 +1,74 @@
<navbar fixed custom-style="background:{{background}}" back>
<view class="page-title" slot="left">
<van-icon name="arrow-left" />
我的收藏
</view>
</navbar>
<view class="page" style="background: url('/images/bg6.png') no-repeat top center/100% 478rpx">
<view class="page-header" id="page-header" style="" style="padding-top: {{pageTop + 21}}px;">
<view class="search">
<image class="icon" src="/images/icon14.png"></image>
<input class="input" placeholder-class="place-input" type="text" placeholder="搜索视频" />
</view>
</view>
<van-tabs
class="tabs"
active="{{ tab }}"
sticky
style="--tabs-nav-background-color: {{background}}"
color="rgba(47, 130, 169, 1)"
title-active-color="rgba(47, 130, 169, 1)"
title-inactive-color="rgba(100, 116, 139, 1)"
bind:change="onChange"
tab-active-class="tab-active-class"
tab-class="tab-class"
wrap-class="wrap-class"
nav-class="nav-calss"
offset-top="{{offsetTop}}"
>
<van-tab title="大咖说">
<view class="body1">
<view class="card" wx:for="{{10}}" wx:key="index">
<view class="photo">
<image class="p-img" mode="aspectFill" src="/images/bg1.png"></image>
<view class="stat">
<view class="s-item">
<image class="icon" src="/images/icon8-1.png"></image>
12
</view>
<view class="s-item">
<image class="icon" src="/images/icon9-1.png"></image>
12
</view>
</view>
</view>
<view class="title">PSVT长期管理专家分享:日常忌口、 PSVT长期管理专家分享:日常忌口、</view>
</view>
</view>
</van-tab>
<van-tab title="科普教育">
<view class="body2">
<view class="list-card">
<view class="photo-wrap">
<image class="photo" mode="aspectFill" src="/images/bg1.png"></image>
<!-- <view class="type1">图文</view> -->
<view class="type2">
<image class="icon" src="/images/icon10.png"></image>
视频
</view>
</view>
<view class="wrap">
<view class="title">什么是 PSVT?3 分钟带您了 解阵发性室上性心动过速</view>
<view class="op-wrap">
<view class="op">
<image class="icon" src="/images/icon9-active.png"></image>
取消收藏
</view>
</view>
</view>
</view>
</view>
</van-tab>
</van-tabs>
</view>

10
src/pages/myComment/index.json

@ -0,0 +1,10 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "我的评论",
"usingComponents": {
"popup": "/components/popup/index",
"navbar": "/components/navbar/index",
"uploadFile": "/components/uploadFile/index",
"van-popup": "@vant/weapp/popup/index"
}
}

155
src/pages/myComment/index.scss

@ -0,0 +1,155 @@
page {
background-color: #f7fafc;
}
.page-title {
font-size: 36rpx;
color: rgba(51, 51, 51, 1);
}
.page {
padding-bottom: 260rpx;
.list {
padding: 0 30rpx;
.list-card {
margin-top: 32rpx;
padding: 32rpx;
background: #fff;
border-radius: 32rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.status-tag {
padding: 8rpx 16rpx;
border-radius: 12rpx;
font-size: 28rpx;
line-height: 36rpx;
text-align: center;
&.status1 {
color: #2f82a9;
background: rgba(47, 130, 169, 0.08);
}
&.status2 {
color: rgba(45, 190, 141, 1);
background: rgba(45, 190, 141, 0.08);
}
&.status3 {
color: rgba(186, 26, 26, 1);
background: rgba(186, 26, 26, 0.08);
}
}
.time {
font-size: 24rpx;
line-height: 32rpx;
color: #94a3b8;
}
}
.card-content {
font-size: 32rpx;
line-height: 52rpx;
font-weight: 500;
color: #1e293b;
}
.card-reference {
padding: 24rpx;
background: #f7fafc;
border-radius: 24rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
.reference-tags {
display: flex;
gap: 16rpx;
.tag {
font-size: 24rpx;
line-height: 32rpx;
color: #2f82a9;
}
}
.reference-post {
display: flex;
gap: 24rpx;
.post-thumbnail {
width: 96rpx;
height: 96rpx;
background: #e2e8f0;
border-radius: 16rpx;
}
.post-desc {
width: 458rpx;
font-size: 28rpx;
line-height: 40rpx;
color: #64748b;
}
}
}
.card-overlay {
padding: 16rpx 24rpx;
background: rgba(47, 130, 169, 0.08);
border-radius: 16rpx;
line-height: 32rpx;
.icon {
margin-right: 16rpx;
width: 32rpx;
height: 32rpx;
vertical-align: -4rpx;
}
.overlay-text {
display: inline;
font-size: 28rpx;
color: #2f82a9;
}
}
.rejected {
padding: 16rpx 24rpx;
border-radius: 16rpx;
background: rgba(186, 26, 26, 0.08);
box-sizing: border-box;
font-size: 28rpx;
color: rgba(186, 26, 26, 1);
line-height: 32rpx;
.icon {
display: inline-block;
flex-shrink: 0;
margin-right: 16rpx;
width: 32rpx;
height: 32rpx;
vertical-align: -4rpx;
}
.blod {
display: inline-block;
font-weight: bold;
}
}
.card-footer {
display: flex;
justify-content: flex-end;
.delete-btn {
font-size: 28rpx;
line-height: 40rpx;
color: #94a3b8;
}
}
}
}
}

103
src/pages/myComment/index.ts

@ -0,0 +1,103 @@
const app = getApp<IAppOption>()
Page({
data: {
popupShow: false,
popupType: '',
popupParams: {
position: 'bottom',
} as any,
topicShow: false,
replayShow: true,
fileList: [] as any[],
},
onLoad() {
app.waitLogin({ type: 0 }).then(() => {})
},
handlePopupOk() {
const { popupType } = this.data
if (popupType === 'argument') {
app.mpBehavior({ PageName: 'BTN_PATIENTLOG' })
wx.ajax({
method: 'POST',
url: '?r=wtx/user/agree-guest-privacy',
data: {
WorkerId: app.globalData.scene?.workerId || '',
},
}).then(() => {
this.setData({
popupShow: false,
popupType: '',
popupParams: {},
})
const waitBindDoctorId = app.globalData.waitBindDoctorId
if (waitBindDoctorId) {
this.handleBindDoctor(waitBindDoctorId)
}
})
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
wx.navigateTo({
url: `/patient/pages/login/index`,
})
})
}
},
handlePopupCancel() {
const { popupType } = this.data
if (popupType === 'argument') {
wx.exitMiniProgram()
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
app.globalData.waitBindDoctorId = ''
})
}
},
handleWechatWork() {
wx.navigateTo({
url: '/pages/wechatWork/index',
})
},
handleSetData(e) {
const { fileList } = this.data
this.setData({
fileList: [...fileList, ...e.detail],
})
},
handleDeleteFile(e) {
const { index } = e.detail
const { fileList } = this.data
this.setData({
fileList: fileList.filter((_, i) => i !== index),
})
},
topicClose() {
this.setData({
topicShow: false,
})
},
})
export {}

45
src/pages/myComment/index.wxml

@ -0,0 +1,45 @@
<navbar fixed custom-style="background:{{background}}" back>
<view class="page-title" slot="left">
<van-icon name="arrow-left" />
我的评论
</view>
</navbar>
<view
class="page"
style="background: url('/images/bg6.png') no-repeat top center/100% 478rpx;padding-top: {{pageTop}}px;"
>
<view class="list">
<view class="list-card" bind:tap="handleDetail">
<view class="card-header">
<!-- status1:待审核,status2:已通过,status3:已驳回 -->
<view class="status-tag status1">待审核</view>
<view class="time">2026-01-01 13:00:09</view>
</view>
<view class="card-content">记录发作时间真的很重要,尤其是频率和持续时长</view>
<view class="card-reference">
<view class="reference-tags">
<view class="tag"># 我的心悸经历</view>
<view class="tag"># PSVT急救常识</view>
</view>
<view class="reference-post">
<view class="post-thumbnail"></view>
<view class="post-desc">今天把近期的状态、复诊资料和生活节奏整理了一下,也想记录一下最</view>
</view>
</view>
<view class="card-overlay">
<image class="icon" src="/images/icon40.png"></image>
<view class="overlay-text">评论正在审核中,预计1-2个工作日内完成审核。</view>
</view>
<view class="rejected">
<image class="icon" src="/images/icon39.png"></image>
<view class="blod">驳回原因:</view>
语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当
语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当 语言不当
</view>
<view class="card-footer">
<view class="delete-btn">删除</view>
</view>
</view>
</view>
</view>

2
src/pages/myWall/index.scss

@ -112,7 +112,7 @@ page {
margin-right: 16rpx; margin-right: 16rpx;
width: 32rpx; width: 32rpx;
height: 32rpx; height: 32rpx;
vertical-align: middle; vertical-align: -4rpx;
} }
.blod { .blod {
display: inline-block; display: inline-block;

10
src/pages/userInfo/index.json

@ -0,0 +1,10 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "我的评论",
"usingComponents": {
"popup": "/components/popup/index",
"navbar": "/components/navbar/index",
"uploadFile": "/components/uploadFile/index",
"van-popup": "@vant/weapp/popup/index"
}
}

92
src/pages/userInfo/index.scss

@ -0,0 +1,92 @@
page {
background-color: #f7fafc;
}
.page-title {
font-size: 36rpx;
color: rgba(51, 51, 51, 1);
}
.page {
padding-bottom: 260rpx;
.list {
padding: 40rpx;
.info-card {
padding: 32rpx;
background: rgba(255, 255, 255, 0.7);
border: 1rpx solid #fff;
border-radius: 32rpx;
}
.info-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 1rpx solid #f1f5f9;
&:last-child {
border-bottom: none;
}
.info-label {
font-size: 32rpx;
line-height: 48rpx;
color: #1e293b;
}
.info-value {
flex: 1;
font-size: 32rpx;
line-height: 48rpx;
color: #64748b;
text-align: right;
}
.info-arrow {
color: #64748b;
margin-left: 8rpx;
}
.avatar-wrap {
width: 112rpx;
height: 112rpx;
border-radius: 50%;
background: #d9d9d9;
overflow: hidden;
.avatar {
width: 100%;
height: 100%;
}
}
}
.btn-logout {
margin-top: 32rpx;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 54rpx;
font-size: 32rpx;
line-height: 48rpx;
color: #1e293b;
}
.btn-cancel {
margin-top: 24rpx;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 54rpx;
font-size: 32rpx;
line-height: 48rpx;
color: #1e293b;
}
}
}

103
src/pages/userInfo/index.ts

@ -0,0 +1,103 @@
const app = getApp<IAppOption>()
Page({
data: {
popupShow: false,
popupType: '',
popupParams: {
position: 'bottom',
} as any,
topicShow: false,
replayShow: true,
fileList: [] as any[],
},
onLoad() {
app.waitLogin({ type: 0 }).then(() => {})
},
handlePopupOk() {
const { popupType } = this.data
if (popupType === 'argument') {
app.mpBehavior({ PageName: 'BTN_PATIENTLOG' })
wx.ajax({
method: 'POST',
url: '?r=wtx/user/agree-guest-privacy',
data: {
WorkerId: app.globalData.scene?.workerId || '',
},
}).then(() => {
this.setData({
popupShow: false,
popupType: '',
popupParams: {},
})
const waitBindDoctorId = app.globalData.waitBindDoctorId
if (waitBindDoctorId) {
this.handleBindDoctor(waitBindDoctorId)
}
})
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
wx.navigateTo({
url: `/patient/pages/login/index`,
})
})
}
},
handlePopupCancel() {
const { popupType } = this.data
if (popupType === 'argument') {
wx.exitMiniProgram()
}
if (popupType === 'conformBindDoctorConform') {
this.setData({
popupShow: false,
})
wx.ajax({
method: 'POST',
url: '?r=wtx/account/wait-bind-doctor',
data: {
doctorId: app.globalData.waitBindDoctorId,
},
}).then(() => {
app.globalData.waitBindDoctorId = ''
})
}
},
handleWechatWork() {
wx.navigateTo({
url: '/pages/wechatWork/index',
})
},
handleSetData(e) {
const { fileList } = this.data
this.setData({
fileList: [...fileList, ...e.detail],
})
},
handleDeleteFile(e) {
const { index } = e.detail
const { fileList } = this.data
this.setData({
fileList: fileList.filter((_, i) => i !== index),
})
},
topicClose() {
this.setData({
topicShow: false,
})
},
})
export {}

60
src/pages/userInfo/index.wxml

@ -0,0 +1,60 @@
<navbar fixed custom-style="background:{{background}}" back>
<view class="page-title" slot="left">
<van-icon name="arrow-left" />
个人信息
</view>
</navbar>
<view
class="page"
style="background: url('/images/bg6.png') no-repeat top center/100% 478rpx;padding-top: {{pageTop}}px;"
>
<view class="list">
<view class="info-card">
<view class="info-row">
<text class="info-label">头像</text>
<view class="avatar-wrap">
<image class="avatar" src="" mode="aspectFill" />
</view>
</view>
<view class="info-row">
<text class="info-label">昵称</text>
<text class="info-value">ZSX0092</text>
</view>
<view class="info-row" bind:tap="handlePhone">
<text class="info-label">手机号</text>
<text class="info-value">153****0000</text>
<van-icon name="arrow" class="info-arrow" />
</view>
<view class="info-row" bind:tap="handleAge">
<text class="info-label">年龄段</text>
<text class="info-value">18-35岁</text>
<van-icon name="arrow" class="info-arrow" />
</view>
<view class="info-row" bind:tap="handleGender">
<text class="info-label">性别</text>
<text class="info-value">女</text>
<van-icon name="arrow" class="info-arrow" />
</view>
<view class="info-row" bind:tap="handleRegion">
<text class="info-label">所在地区</text>
<text class="info-value">北京/北京市</text>
<van-icon name="arrow" class="info-arrow" />
</view>
<view class="info-row" bind:tap="handleHealth">
<text class="info-label">我的健康状态</text>
<text class="info-value">已确诊</text>
</view>
<view class="info-row">
<text class="info-label">确诊时间</text>
<text class="info-value">2024-08-01</text>
</view>
<view class="info-row">
<text class="info-label">是否接受过手术治疗</text>
<text class="info-value">正在评估</text>
</view>
</view>
<view class="btn-logout">退出登录</view>
<view class="btn-cancel">注销</view>
</view>
</view>

1
src/pages/wall/index.wxml

@ -78,7 +78,6 @@
</view> </view>
</view> </view>
<view class="fixed-add"> <view class="fixed-add">
<image class="icon" src="/images/icon24.png"></image> <image class="icon" src="/images/icon24.png"></image>
</view> </view>

4
src/pages/wallDetail/index.json

@ -2,6 +2,8 @@
"navigationStyle": "custom", "navigationStyle": "custom",
"usingComponents": { "usingComponents": {
"popup": "/components/popup/index", "popup": "/components/popup/index",
"navbar": "/components/navbar/index" "navbar": "/components/navbar/index",
"van-action-sheet": "@vant/weapp/action-sheet/index",
"van-popup": "@vant/weapp/popup/index"
} }
} }

68
src/pages/wallDetail/index.scss

@ -234,21 +234,35 @@ page {
.comment { .comment {
margin-right: 12rpx; margin-right: 12rpx;
flex: 1; flex: 1;
min-height: 96rpx; height: 96rpx;
border-radius: 24rpx; border-radius: 24rpx;
border: 2rpx solid #f1f5f9; border: 1px solid #f1f5f9;
padding: 0 32rpx;
background: #f9fafb; background: #f9fafb;
box-sizing: border-box; box-sizing: border-box;
.txa { display: flex;
padding: 32rpx; align-items: center;
width: 100%; font-size: 28rpx;
min-height: 100%; color: rgba(148, 163, 184, 1);
font-size: 28rpx; }
color: rgba(30, 41, 59, 1); .auth {
box-sizing: border-box; flex: 1;
display: flex;
align-items: center;
gap: 22rpx;
.icon {
width: 35rpx;
} }
.txa-place { .wrap {
color: rgba(148, 163, 184, 1); .status {
font-size: 28rpx;
color: rgba(71, 85, 105, 1);
}
.content {
margin-top: 8rpx;
font-size: 24rpx;
color: rgba(47, 130, 169, 1);
}
} }
} }
.op-wrap { .op-wrap {
@ -417,3 +431,35 @@ page {
} }
} }
} }
.action-popup {
background-color: #fff;
padding: 16rpx 0 0;
.a-list {
border-bottom: 8rpx solid rgba(247, 250, 252, 1);
.row {
margin: 0 30rpx;
border-bottom: 1px solid rgba(241, 245, 249, 1);
padding: 32rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
font-size: 32rpx;
color: rgba(30, 41, 59, 1);
&:last-of-type {
border: none;
}
.icon {
width: 40rpx;
height: 40rpx;
}
}
}
.a-close{
padding: 32rpx;
font-size: 32rpx;
line-height: 48rpx;
text-align: center;
}
}

1
src/pages/wallDetail/index.ts

@ -10,6 +10,7 @@ Page({
topicShow: false, topicShow: false,
replayShow: false, replayShow: false,
actionShow: false,
}, },
onLoad() { onLoad() {
app.waitLogin({ type: 0 }).then(() => {}) app.waitLogin({ type: 0 }).then(() => {})

38
src/pages/wallDetail/index.wxml

@ -101,9 +101,27 @@
</view> </view>
<view class="page-footer"> <view class="page-footer">
<view class="comment"> <view class="auth" wx:if="{{false}}">
<textarea class="txa" auto-height placeholder-class="txa-place" placeholder="我要评论"></textarea> <image class="icon" mode="widthFix" src="/images/icon37.png"></image>
<view class="wrap">
<view class="status">公开可见</view>
<view class="content">
编辑和设置权限
<van-icon name="arrow" />
</view>
</view>
</view>
<view class="auth" wx:elif="{{false}}">
<image class="icon" mode="widthFix" src="/images/icon22.png"></image>
<view class="wrap">
<view class="status">仅自己可见</view>
<view class="content">
编辑和设置权限
<van-icon name="arrow" />
</view>
</view>
</view> </view>
<view class="comment" wx:else>我要评论</view>
<view class="op-wrap"> <view class="op-wrap">
<view class="op"> <view class="op">
<image class="icon" src="/images/icon9.png"></image> <image class="icon" src="/images/icon9.png"></image>
@ -157,3 +175,19 @@
</view> </view>
</view> </view>
</van-popup> </van-popup>
<van-popup show="{{ actionShow }}" round position="bottom" round bind:close="actionClose">
<view class="action-popup">
<view class="a-list">
<view class="row">
<image class="icon" src="/images/icon41.png"></image>
编辑
</view>
<view class="row">
<image class="icon" src="/images/icon42.png"></image>
删除
</view>
</view>
<view class="a-close">取消</view>
</view>
</van-popup>

3
typings/index.d.ts vendored

@ -36,10 +36,9 @@ interface IAppOption {
waitBindDoctorId: string waitBindDoctorId: string
loginState: string loginState: string
initLoginInfo: Partial<{ initLoginInfo: Partial<{
loginState: string
isLogin: 0 | 1 isLogin: 0 | 1
isReg: 0 | 1 isReg: 0 | 1
loginType: 1 | 2
guestPrivacyAgree: boolean
}> }>
[propName: string]: any [propName: string]: any

Loading…
Cancel
Save