@ -1,250 +0,0 @@
@@ -1,250 +0,0 @@
|
||||
# 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=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=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 | 更新协议失败 | |
||||
@ -1,969 +0,0 @@
@@ -1,969 +0,0 @@
|
||||
# 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 | |
||||
| isReg | bool | 是否已信息提交注册 | |
||||
|
||||
**业务逻辑**: |
||||
|
||||
1. 调用微信接口获取openid和session_key |
||||
2. 创建/更新PSVT用户记录(t_psvt_user_info) |
||||
3. 若有channelId参数,更新ScanChannelId(最终渠道,每次扫码都更新) |
||||
4. 首次打开时记录FirstOpenTime |
||||
5. 返回用户状态信息(isPhoneRegistered、isInfoSubmitted) |
||||
|
||||
--- |
||||
|
||||
### 1.2 手机号授权注册 `POST ?r=psvt/login/wx-reg-login` |
||||
|
||||
**说明**: 解密微信加密数据获取手机号,完成手机号注册。事务操作。 |
||||
|
||||
**请求参数**: |
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | |
||||
| ------------- | ------ | ---- | -------------------- | |
||||
| encryptedData | string | 是 | 微信加密手机号数据 | |
||||
| iv | string | 是 | 微信加密iv | |
||||
| channelId | int | 否 | 手机号注册时的渠道ID | |
||||
|
||||
**响应示例**: |
||||
|
||||
```json |
||||
{ |
||||
"code": 0, |
||||
"message": "success", |
||||
"data": { |
||||
"isLogin": true, |
||||
"isReg": true |
||||
} |
||||
} |
||||
``` |
||||
|
||||
**响应字段说明**: |
||||
|
||||
| 字段名 | 类型 | 说明 | |
||||
| ------- | ---- | ------------------ | |
||||
| isLogin | bool | 是否已登录 | |
||||
| isReg | 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.2.1 手机号验证码注册 `POST ?r=psvt/login/reg-login` |
||||
|
||||
**说明**: 解密微信加密数据获取手机号,完成手机号注册。事务操作。 |
||||
|
||||
**请求参数**: |
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | |
||||
| --------- | ------ | ---- | -------------------- | |
||||
| mobile | string | 是 | 手机号 | |
||||
| code | string | 是 | 验证码 | |
||||
| channelId | int | 否 | 手机号注册时的渠道ID | |
||||
|
||||
**响应示例**: |
||||
|
||||
```json |
||||
{ |
||||
"code": 0, |
||||
"message": "success", |
||||
"data": { |
||||
"isLogin": true, |
||||
"isReg": true |
||||
} |
||||
} |
||||
``` |
||||
|
||||
**响应字段说明**: |
||||
|
||||
| 字段名 | 类型 | 说明 | |
||||
| ------- | ---- | ------------------ | |
||||
| isLogin | bool | 是否已登录 | |
||||
| isReg | bool | 是否已信息提交注册 | |
||||
|
||||
### 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=channel/list` |
||||
|
||||
**说明**: 获取有效渠道码列表,支持分页。 |
||||
|
||||
**请求参数**: |
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | |
||||
| ------ | ---- | ---- | ---------------- | |
||||
| page | int | 否 | 页码,默认1 | |
||||
| count | int | 否 | 每页数量,默认10 | |
||||
|
||||
**响应示例**: |
||||
|
||||
```json |
||||
{ |
||||
"code": 0, |
||||
"message": "success", |
||||
"data": { |
||||
"page": 1, |
||||
"pages": 5, |
||||
"count": 50, |
||||
"list": [ |
||||
{ |
||||
"channelId": 1, |
||||
"channelName": "渠道A", |
||||
"channelDesc": "渠道A说明", |
||||
"miniCodeUrl": "https://xxx/minicode_1.png", |
||||
"createDate": "2023-01-01", |
||||
"updateDate": "2023-01-01" |
||||
} |
||||
] |
||||
} |
||||
} |
||||
``` |
||||
|
||||
**响应字段说明(data层级)**: |
||||
|
||||
| 字段名 | 类型 | 说明 | |
||||
| ------ | ----- | -------- | |
||||
| page | int | 当前页码 | |
||||
| pages | int | 总页数 | |
||||
| count | int | 总记录数 | |
||||
| list | array | 渠道列表 | |
||||
|
||||
**list行字段说明**: |
||||
|
||||
| 字段名 | 类型 | 说明 | |
||||
| ----------- | ------ | --------------- | |
||||
| channelId | int | 渠道ID | |
||||
| channelName | string | 渠道名称 | |
||||
| channelDesc | string | 渠道码说明 | |
||||
| miniCodeUrl | string | 小程序码图片URL | |
||||
| createDate | string | 创建日期 | |
||||
| updateDate | string | 更新日期 | |
||||
|
||||
--- |
||||
|
||||
### 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 删除渠道码 `POST ?r=channel/delete` |
||||
|
||||
**说明**: 逻辑删除渠道码,将Status置为100(删除状态)。事务操作。 |
||||
|
||||
**请求参数**: |
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | |
||||
| --------- | ---- | ---- | ------ | |
||||
| channelId | int | 是 | 渠道ID | |
||||
|
||||
**响应示例**: |
||||
|
||||
```json |
||||
{ |
||||
"code": 0, |
||||
"message": "success", |
||||
"data": { |
||||
"channelId": 1 |
||||
} |
||||
} |
||||
``` |
||||
|
||||
**响应字段说明**: |
||||
|
||||
| 字段名 | 类型 | 说明 | |
||||
| --------- | ---- | -------------- | |
||||
| channelId | int | 已删除的渠道ID | |
||||
|
||||
**业务逻辑**: |
||||
|
||||
1. 校验渠道存在 |
||||
2. 将渠道的Status置为100(删除状态),逻辑删除 |
||||
3. 更新UpdateBy和UpdateTime |
||||
4. 使用事务 |
||||
|
||||
--- |
||||
|
||||
### 2.7 获取小程序渠道码 `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.8 渠道码统计 `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", |
||||
"channelDesc": "渠道A说明", |
||||
"openPatientCount": 100, |
||||
"phoneRegisteredCount": 80, |
||||
"infoSubmittedCount": 50, |
||||
"diagnosedPsvtCount": 30 |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
**响应字段说明(每行)**: |
||||
|
||||
| 字段名 | 类型 | 说明 | |
||||
| -------------------- | ------ | -------------------- | |
||||
| statDate | string | 统计日期 | |
||||
| channelId | int | 渠道ID | |
||||
| channelName | string | 渠道名称 | |
||||
| channelDesc | string | 渠道码说明 | |
||||
| openPatientCount | int | 邀约打开患者数 | |
||||
| phoneRegisteredCount | int | 邀约注册手机号患者数 | |
||||
| infoSubmittedCount | int | 邀约提交信息患者数 | |
||||
| diagnosedPsvtCount | int | 确诊PSVT患者数 | |
||||
|
||||
--- |
||||
|
||||
### 2.9 渠道码原始数据列表 `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", |
||||
"inviteDate": "2023-01-01", |
||||
"channelId": 1, |
||||
"channelName": "渠道A", |
||||
"channelDesc": "渠道A说明", |
||||
"inviteType": 1, |
||||
"inviteTypeName": "用户打开", |
||||
"userId": "xxx", |
||||
"patientId": 5, |
||||
"nickName": "abcd12345678", |
||||
"gender": 1, |
||||
"genderName": "男", |
||||
"ageRange": 1, |
||||
"ageRangeName": "18-24岁", |
||||
"provinceName": "北京", |
||||
"cityName": "北京", |
||||
"diagnosisStatus": 2, |
||||
"diagnosisStatusName": "确诊", |
||||
"diagnosisTime": "2023-06", |
||||
"surgeryStatus": 1, |
||||
"surgeryStatusName": "已手术", |
||||
"telephone": "138xxxx1234" |
||||
} |
||||
] |
||||
} |
||||
} |
||||
``` |
||||
|
||||
**响应字段说明(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.10 获取基础数据列表 `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"} | |
||||
|
||||
--- |
||||
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 77 KiB |
@ -1,10 +1,117 @@
@@ -1,10 +1,117 @@
|
||||
const _app = getApp<IAppOption>() |
||||
const app = getApp<IAppOption>() |
||||
|
||||
Page({ |
||||
data: { |
||||
content: '1111', |
||||
id: '', |
||||
detail: {} as any, |
||||
content: '', |
||||
next: null as any, |
||||
}, |
||||
onLoad(options: any) { |
||||
if (options.id) { |
||||
this.setData({ id: options.id }) |
||||
app.waitLogin({ type: 'any' }).then(() => { |
||||
this.getDetail(options.id) |
||||
this.addViewRecord(options.id) |
||||
}) |
||||
} |
||||
}, |
||||
getDetail(id: string) { |
||||
wx.ajax({ |
||||
method: 'GET', |
||||
url: '?r=psvt/pic-text/detail', |
||||
data: { Id: id }, |
||||
}).then((res: any) => { |
||||
this.setData({ |
||||
detail: res || {}, |
||||
content: res.Content || '', |
||||
next: res.Next || null, |
||||
}) |
||||
}) |
||||
}, |
||||
addViewRecord(id: string) { |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/view', |
||||
data: { ContentId: Number(id) }, |
||||
}) |
||||
}, |
||||
handleCompliment() { |
||||
if (!app.checkLoginType(1)) return |
||||
const { detail, id } = this.data |
||||
if (detail.IsCompliment) return |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/compliment', |
||||
data: { ContentId: Number(id) }, |
||||
}).then(() => { |
||||
this.setData({ |
||||
'detail.IsCompliment': 1, |
||||
'detail.ComplimentNum': (detail.ComplimentNum || 0) + 1, |
||||
}) |
||||
}) |
||||
}, |
||||
handleCollect() { |
||||
if (!app.checkLoginType(1)) return |
||||
const { detail, id } = this.data |
||||
if (detail.IsCollect) { |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/cancel-collection', |
||||
data: { ContentId: Number(id) }, |
||||
}).then(() => { |
||||
this.setData({ |
||||
'detail.IsCollect': 0, |
||||
'detail.CollectionPeopleNum': Math.max(0, (detail.CollectionPeopleNum * 1 || 0) - 1), |
||||
}) |
||||
}) |
||||
} else { |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/collection', |
||||
data: { ContentId: Number(id) }, |
||||
}).then(() => { |
||||
this.setData({ |
||||
'detail.IsCollect': 1, |
||||
'detail.CollectionPeopleNum': (detail.CollectionPeopleNum * 1 || 0) + 1, |
||||
}) |
||||
}) |
||||
} |
||||
}, |
||||
handleShare() { |
||||
const { detail, id } = this.data |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/share', |
||||
data: { ContentId: Number(id) }, |
||||
}).then(() => { |
||||
this.setData({ |
||||
'detail.IsShare': 1, |
||||
'detail.ShareNum': (detail.ShareNum * 1 || 0) + 1, |
||||
}) |
||||
}) |
||||
}, |
||||
handleNext() { |
||||
const { next } = this.data |
||||
if (!next) return |
||||
wx.redirectTo({ |
||||
url: `/pages/articleDetail/index?id=${next.Id}`, |
||||
}) |
||||
}, |
||||
handleRecommendTap(e: any) { |
||||
const { id } = e.currentTarget.dataset |
||||
wx.navigateTo({ |
||||
url: `/pages/articleDetail/index?id=${id}`, |
||||
}) |
||||
}, |
||||
onShareAppMessage() { |
||||
const { detail } = this.data |
||||
this.handleShare() |
||||
return { |
||||
title: detail.Title || '图文详情', |
||||
path: `/pages/articleDetail/index?id=${this.data.id}`, |
||||
} |
||||
}, |
||||
onLoad() {}, |
||||
}) |
||||
|
||||
export {} |
||||
|
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
{ |
||||
"navigationStyle": "custom", |
||||
"usingComponents": { |
||||
"navbar": "/components/navbar/index", |
||||
"van-slider": "@vant/weapp/slider/index" |
||||
} |
||||
} |
||||
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
.page-title { |
||||
font-size: 36rpx; |
||||
color: rgba(51, 51, 51, 1); |
||||
padding-right: 100rpx; |
||||
} |
||||
|
||||
.page { |
||||
background-color: #000; |
||||
.swiper { |
||||
width: 100vw; |
||||
height: 100vh; |
||||
.swiper-item { |
||||
position: relative; |
||||
width: 100vw; |
||||
height: 100vh; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
.video { |
||||
width: 100%; |
||||
flex: 1; |
||||
} |
||||
.play { |
||||
position: absolute; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
width: 90rpx; |
||||
height: 104rpx; |
||||
} |
||||
.swiper-item-footer { |
||||
position: relative; |
||||
.info-card { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
transform: translateY(-100%); |
||||
background-color: red; |
||||
width: 100%; |
||||
padding: 24rpx 40rpx 0; |
||||
box-sizing: border-box; |
||||
background: linear-gradient(0deg, #1e293b 0%, rgba(30, 41, 59, 0.11) 74.25%, rgba(30, 41, 59, 0) 100%); |
||||
.rate-select { |
||||
position: absolute; |
||||
top: -24rpx; |
||||
left: 50%; |
||||
transform: translate(-50%, -100%); |
||||
padding: 0 32rpx; |
||||
border-radius: 50rpx; |
||||
background: rgba(30, 41, 59, 0.6); |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
.r-item { |
||||
padding: 20rpx 32rpx; |
||||
font-size: 32rpx; |
||||
white-space: nowrap; |
||||
color: rgba(255, 255, 255, 1); |
||||
&.active { |
||||
color: #ff864a; |
||||
} |
||||
} |
||||
} |
||||
.tag-wrap { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
gap: 24rpx; |
||||
.tag { |
||||
height: 48rpx; |
||||
padding: 4rpx 24rpx; |
||||
line-height: 40rpx; |
||||
border-radius: 24rpx; |
||||
background: #fff; |
||||
box-sizing: border-box; |
||||
font-size: 24rpx; |
||||
color: rgba(30, 41, 59, 1); |
||||
} |
||||
} |
||||
.title { |
||||
margin-top: 24rpx; |
||||
font-size: 36rpx; |
||||
color: rgba(255, 255, 255, 1); |
||||
font-weight: bold; |
||||
} |
||||
.progress { |
||||
margin-top: 32rpx; |
||||
.slider-btn { |
||||
width: 200vw; |
||||
height: 80rpx; |
||||
} |
||||
} |
||||
} |
||||
.container { |
||||
padding: 32rpx 40rpx calc(40rpx + env(safe-area-inset-bottom)) 22rpx; |
||||
width: 100%; |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
background-color: rgba(30, 41, 59, 1); |
||||
.op-wrap { |
||||
display: flex; |
||||
align-items: center; |
||||
.op { |
||||
min-width: 88rpx; |
||||
padding: 0 18rpx; |
||||
text-align: center; |
||||
.icon { |
||||
width: 48rpx; |
||||
height: 48rpx; |
||||
} |
||||
.num { |
||||
margin-top: 8rpx; |
||||
font-size: 28rpx; |
||||
color: rgba(255, 255, 255, 1); |
||||
line-height: 32rpx; |
||||
&.active1 { |
||||
color: rgba(234, 88, 26, 1); |
||||
} |
||||
&.active2 { |
||||
color: rgba(242, 150, 0, 1); |
||||
} |
||||
} |
||||
.share-btn { |
||||
padding: 0; |
||||
margin: 0; |
||||
line-height: normal; |
||||
background: transparent; |
||||
border: none; |
||||
&::after { |
||||
display: none; |
||||
} |
||||
.icon { |
||||
width: 48rpx; |
||||
height: 48rpx; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.speed { |
||||
flex-shrink: 0; |
||||
font-size: 28rpx; |
||||
color: rgba(255, 255, 255, 1); |
||||
padding: 8rpx 16rpx; |
||||
display: flex; |
||||
align-items: center; |
||||
height: 48rpx; |
||||
line-height: 32rpx; |
||||
border-radius: 8rpx; |
||||
border: 1px solid #fff; |
||||
box-sizing: border-box; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,262 @@
@@ -0,0 +1,262 @@
|
||||
const app = getApp<IAppOption>() |
||||
|
||||
const PAGE_SIZE = 10 |
||||
|
||||
function parseContent(content: any) { |
||||
if (typeof content === 'string') { |
||||
try { |
||||
return JSON.parse(content) |
||||
} catch { |
||||
return null |
||||
} |
||||
} |
||||
return content |
||||
} |
||||
|
||||
Page({ |
||||
data: { |
||||
id: '', |
||||
current: 0, |
||||
videoList: [] as Array<any>, |
||||
page: 1, |
||||
pages: 1, |
||||
showRateSelect: false, |
||||
}, |
||||
videoContexts: {} as any, |
||||
onLoad(options: any) { |
||||
if (options.id) { |
||||
this.setData({ id: options.id }) |
||||
} |
||||
|
||||
app.waitLogin({ type: 'any' }).then(() => { |
||||
this.initData() |
||||
}) |
||||
}, |
||||
mapItem(item: any) { |
||||
const content = parseContent(item.Content) |
||||
return { |
||||
...item, |
||||
ImageUrl: item.TitlePicLink, |
||||
SrcUrl: content?.videoUrl || '', |
||||
preload: false, |
||||
progress: 0, |
||||
play: false, |
||||
} |
||||
}, |
||||
initData() { |
||||
const { id } = this.data |
||||
Promise.all([ |
||||
wx.ajax({ method: 'GET', url: '?r=psvt/pic-text/detail', data: { Id: id } }), |
||||
wx.ajax({ |
||||
method: 'GET', |
||||
url: '?r=psvt/pic-text/list-by-content-id', |
||||
data: { page: 1, count: PAGE_SIZE, ContentId: id }, |
||||
}), |
||||
]).then(([detail, listRes]: any) => { |
||||
const detailItem = this.mapItem(detail) |
||||
const list = (listRes.list || []).map((item: any) => this.mapItem(item)) |
||||
this.setData({ |
||||
videoList: [detailItem, ...list], |
||||
page: listRes.page || 1, |
||||
pages: listRes.pages || 1, |
||||
}) |
||||
this.preloadVideos(this.data.current) |
||||
this.playCurrentVideo() |
||||
if (this.data.videoList.length > 0) { |
||||
this.addViewRecord(this.data.videoList[0].Id) |
||||
} |
||||
}) |
||||
}, |
||||
getList(page: number) { |
||||
const { id } = this.data |
||||
wx.ajax({ |
||||
method: 'GET', |
||||
url: '?r=psvt/pic-text/list-by-content-id', |
||||
data: { page, count: PAGE_SIZE, ContentId: id }, |
||||
}).then((res: any) => { |
||||
const list = (res.list || []).map((item: any) => this.mapItem(item)) |
||||
this.setData({ |
||||
videoList: this.data.videoList.concat(list), |
||||
page: res.page || 1, |
||||
pages: res.pages || 1, |
||||
}) |
||||
this.preloadVideos(this.data.current) |
||||
}) |
||||
}, |
||||
preloadVideos(currentIndex: number) { |
||||
const { videoList } = this.data |
||||
const newVideoList = [...videoList] |
||||
newVideoList.forEach((item: any, index: number) => { |
||||
if (Math.abs(index - currentIndex) <= 1) { |
||||
item.preload = true |
||||
} else { |
||||
item.preload = false |
||||
} |
||||
}) |
||||
this.setData({ videoList: newVideoList }) |
||||
}, |
||||
handleChange(e: any) { |
||||
const { current } = e.detail |
||||
const { videoList, pages, page } = this.data |
||||
this.setData({ current, showRateSelect: false }) |
||||
|
||||
this.addViewRecord(videoList[current].Id) |
||||
this.preloadVideos(current) |
||||
this.playCurrentVideo() |
||||
|
||||
if (current >= videoList.length - 2 && page < pages) { |
||||
this.getList(page + 1) |
||||
} |
||||
}, |
||||
playCurrentVideo() { |
||||
const { current, videoList } = this.data |
||||
Object.values(this.videoContexts).forEach((ctx: any) => { |
||||
ctx.pause() |
||||
}) |
||||
const currentVideo = videoList[current] |
||||
if (currentVideo) { |
||||
const videoId = `video-${currentVideo.Id}` |
||||
if (!this.videoContexts[videoId]) { |
||||
this.videoContexts[videoId] = wx.createVideoContext(videoId) |
||||
} |
||||
this.videoContexts[videoId].play() |
||||
} |
||||
}, |
||||
addViewRecord(contentId: string) { |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/view', |
||||
data: { ContentId: Number(contentId) }, |
||||
}) |
||||
}, |
||||
handleTogglePlay(e: any) { |
||||
const { index } = e.currentTarget.dataset |
||||
const { videoList } = this.data |
||||
const video = videoList[index] |
||||
const videoId = `video-${video.Id}` |
||||
if (!this.videoContexts[videoId]) { |
||||
this.videoContexts[videoId] = wx.createVideoContext(videoId) |
||||
} |
||||
if (video.play) { |
||||
this.videoContexts[videoId].pause() |
||||
} else { |
||||
this.videoContexts[videoId].play() |
||||
} |
||||
}, |
||||
handlePlay(e: any) { |
||||
const { index } = e.currentTarget.dataset |
||||
this.setData({ [`videoList[${index}].play`]: true }) |
||||
}, |
||||
handlePause(e: any) { |
||||
const { index } = e.currentTarget.dataset |
||||
this.setData({ [`videoList[${index}].play`]: false }) |
||||
}, |
||||
handleTimeUpdate(e: any) { |
||||
const { index } = e.currentTarget.dataset |
||||
const { videoList } = this.data |
||||
if (videoList[index].drag) return |
||||
const { currentTime, duration } = e.detail |
||||
const progress = (currentTime / duration) * 100 |
||||
this.setData({ |
||||
[`videoList[${index}].progress`]: progress, |
||||
[`videoList[${index}].duration`]: duration, |
||||
}) |
||||
}, |
||||
handleDragStart(e: any) { |
||||
const { index } = e.currentTarget.dataset |
||||
this.setData({ [`videoList[${index}].drag`]: true }) |
||||
}, |
||||
handleDragEnd(e: any) { |
||||
const { index } = e.currentTarget.dataset |
||||
const { videoList } = this.data |
||||
const video = videoList[index] |
||||
const duration = video.duration |
||||
const dragProgress = e.detail |
||||
const currentTime = (dragProgress / 100) * duration |
||||
|
||||
const videoId = `video-${video.Id}` |
||||
if (!this.videoContexts[videoId]) { |
||||
this.videoContexts[videoId] = wx.createVideoContext(videoId) |
||||
} |
||||
this.videoContexts[videoId].seek(currentTime) |
||||
this.setData({ [`videoList[${index}].drag`]: false }) |
||||
}, |
||||
handleCompliment() { |
||||
if (!app.checkLoginType(1)) return |
||||
const { current, videoList } = this.data |
||||
const video = videoList[current] |
||||
if (video.IsCompliment) return |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/compliment', |
||||
data: { ContentId: Number(video.Id) }, |
||||
}).then(() => { |
||||
this.setData({ |
||||
[`videoList[${current}].IsCompliment`]: 1, |
||||
[`videoList[${current}].ComplimentNum`]: (video.ComplimentNum * 1 || 0) + 1, |
||||
}) |
||||
}) |
||||
}, |
||||
handleCollect() { |
||||
if (!app.checkLoginType(1)) return |
||||
const { current, videoList } = this.data |
||||
const video = videoList[current] |
||||
const isCollect = video.IsCollect |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: isCollect ? '?r=psvt/pic-text/cancel-collection' : '?r=psvt/pic-text/collection', |
||||
data: { ContentId: Number(video.Id) }, |
||||
}).then(() => { |
||||
this.setData({ |
||||
[`videoList[${current}].IsCollect`]: isCollect ? 0 : 1, |
||||
[`videoList[${current}].CollectionPeopleNum`]: (video.CollectionPeopleNum * 1 || 0) + (isCollect ? -1 : 1), |
||||
}) |
||||
}) |
||||
}, |
||||
handleShare() { |
||||
const { current, videoList } = this.data |
||||
const video = videoList[current] |
||||
wx.ajax({ |
||||
method: 'POST', |
||||
url: '?r=psvt/pic-text/share', |
||||
data: { ContentId: Number(video.Id) }, |
||||
}).then(() => { |
||||
this.setData({ |
||||
[`videoList[${current}].IsShare`]: 1, |
||||
[`videoList[${current}].ShareNum`]: (video.ShareNum * 1 || 0) + 1, |
||||
}) |
||||
}) |
||||
}, |
||||
handleSpeed(e: any) { |
||||
const { speed } = e.currentTarget.dataset |
||||
const { current, videoList } = this.data |
||||
const video = videoList[current] |
||||
const videoId = `video-${video.Id}` |
||||
if (!this.videoContexts[videoId]) { |
||||
this.videoContexts[videoId] = wx.createVideoContext(videoId) |
||||
} |
||||
this.videoContexts[videoId].playbackRate(Number(speed)) |
||||
this.setData({ |
||||
[`videoList[${current}].playbackRate`]: speed, |
||||
showRateSelect: false, |
||||
}) |
||||
}, |
||||
handleToggleRateSelect() { |
||||
this.setData({ showRateSelect: !this.data.showRateSelect }) |
||||
}, |
||||
onShareAppMessage() { |
||||
const { current, videoList } = this.data |
||||
const video = videoList[current] |
||||
this.handleShare() |
||||
return { |
||||
title: video.Title || '视频详情', |
||||
path: `/pages/articleVideoDetail/index?id=${video.Id}`, |
||||
imageUrl: video.ImageUrl || video.TitlePicLink, |
||||
} |
||||
}, |
||||
handleBack() { |
||||
wx.navigateBack() |
||||
}, |
||||
}) |
||||
|
||||
export {} |
||||
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
<navbar fixed custom-style="background:{{background}}" back> |
||||
<view class="page-title" slot="left" bind:tap="handleBack"> |
||||
<van-icon name="arrow-left" /> |
||||
</view> |
||||
</navbar> |
||||
|
||||
<view class="page"> |
||||
<swiper class="swiper" vertical="{{true}}" current="{{current}}" bindchange="handleChange"> |
||||
<swiper-item class="swiper-item" wx:for="{{videoList}}" wx:key="Id"> |
||||
<video |
||||
id="video-{{item.Id}}" |
||||
class="video" |
||||
src="{{item.preload ? item.SrcUrl : ''}}" |
||||
poster="{{item.ImageUrl}}" |
||||
loop |
||||
title="{{item.Title}}" |
||||
object-fit="contain" |
||||
enable-play-gesture |
||||
enable-progress-gesture |
||||
show-center-play-btn="{{true}}" |
||||
controls="{{false}}" |
||||
show-fullscreen-btn="{{false}}" |
||||
play-btn-position="center" |
||||
bindplay="handlePlay" |
||||
bindpause="handlePause" |
||||
bindtimeupdate="handleTimeUpdate" |
||||
data-index="{{index}}" |
||||
></video> |
||||
<view class="swiper-item-footer"> |
||||
<view class="info-card"> |
||||
<view class="rate-select" wx:if="{{showRateSelect && current===index}}"> |
||||
<view class="r-item {{item.playbackRate==='0.5' && 'active'}}" bind:tap="handleSpeed" data-speed="0.5">0.5倍</view> |
||||
<view class="r-item {{(!item.playbackRate || item.playbackRate==='1') && 'active'}}" bind:tap="handleSpeed" data-speed="1">正常</view> |
||||
<view class="r-item {{item.playbackRate==='1.5' && 'active'}}" bind:tap="handleSpeed" data-speed="1.5">1.5倍</view> |
||||
<view class="r-item {{item.playbackRate==='2' && 'active'}}" bind:tap="handleSpeed" data-speed="2">2.0倍速</view> |
||||
</view> |
||||
<view class="tag-wrap"> |
||||
<view class="tag" wx:for="{{item.CateNames}}" wx:key="index" wx:for-item="cate">{{cate}}</view> |
||||
</view> |
||||
<view class="title">{{item.Title}}</view> |
||||
<view class="progress"> |
||||
<van-slider |
||||
value="{{item.progress}}" |
||||
active-color="#439FC7" |
||||
bar-height="4" |
||||
inactive-color="rgba(255, 255, 255, 1)" |
||||
bind:drag-start="handleDragStart" |
||||
bind:change="handleDragEnd" |
||||
use-button-slot |
||||
data-index="{{index}}" |
||||
> |
||||
<view class="slider-btn" slot="button"></view> |
||||
</van-slider> |
||||
</view> |
||||
</view> |
||||
<view class="container"> |
||||
<view class="op-wrap"> |
||||
<view class="op" bind:tap="handleCompliment"> |
||||
<image class="icon" src="{{item.IsCompliment ? imageUrl + 'icon9-active.png?t=' + Timestamp : imageUrl + 'icon9-1.png?t=' + Timestamp}}"></image> |
||||
<view class="num {{item.IsCompliment ? 'active1' : ''}}">{{item.ComplimentNum || 0}}</view> |
||||
</view> |
||||
<view class="op"> |
||||
<image class="icon" src="{{imageUrl}}icon8-1.png?t={{Timestamp}}"></image> |
||||
<view class="num">{{item.OpenNum || 0}}</view> |
||||
</view> |
||||
<view class="op" bind:tap="handleCollect"> |
||||
<image class="icon" src="{{item.IsCollect ? imageUrl + 'icon28-active.png?t=' + Timestamp : imageUrl + 'icon28-1.png?t=' + Timestamp}}"></image> |
||||
<view class="num {{item.IsCollect ? 'active2' : ''}}">{{item.CollectionPeopleNum || 0}}</view> |
||||
</view> |
||||
<view class="op"> |
||||
<button class="share-btn" open-type="share"> |
||||
<image class="icon" src="{{imageUrl}}icon29-1.png?t={{Timestamp}}"></image> |
||||
</button> |
||||
<view class="num">{{item.ShareNum || 0}}</view> |
||||
</view> |
||||
</view> |
||||
<view class="speed" bind:tap="handleToggleRateSelect">倍速</view> |
||||
</view> |
||||
</view> |
||||
</swiper-item> |
||||
</swiper> |
||||
</view> |
||||
@ -1,4 +1,7 @@
@@ -1,4 +1,7 @@
|
||||
{ |
||||
"navigationStyle": "custom", |
||||
"navigationBarTitleText": "修改手机号", |
||||
"usingComponents": {} |
||||
"usingComponents": { |
||||
"navbar": "/components/navbar/index" |
||||
} |
||||
} |
||||
|
||||
@ -1,12 +1,13 @@
@@ -1,12 +1,13 @@
|
||||
{ |
||||
"navigationStyle": "custom", |
||||
"navigationBarTitleText": "我的评论", |
||||
"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" |
||||
"van-tabs": "@vant/weapp/tabs/index", |
||||
"pagination": "/components/pagination/index" |
||||
} |
||||
} |
||||
|
||||
@ -1,8 +1,11 @@
@@ -1,8 +1,11 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
const _app = getApp<IAppOption>() |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
handleBack() { |
||||
wx.navigateBack() |
||||
}, |
||||
}) |
||||
|
||||
export {} |
||||
|
||||