Compare commits

50 Commits
master ... main

Author SHA1 Message Date
李琦
84be1acf65 feat: 患者端挂号预约流程改造与就诊人选择
- 挂号预约:register-info 流程重构、确认步骤/患者选择步骤组件、我的预约(myappiont)页对接
- 接口:register.js 补充挂号相关接口,api.js 调整
- 就诊人:claim-patient-modal 复用弹窗
- 医生信息:doctor-detail/doctor-userinfo 配合排班展示
- 规范:新增 Api-Module-Split.mdc 接口模块拆分规则
2026-08-27 15:33:12 +08:00
李琦
d5cd4f3144 feat: 患者端挂号预约流程改造与就诊人选择
- 挂号预约:register-info 流程重构、确认步骤/患者选择步骤组件、我的预约(myappiont)页对接
- 接口:register.js 补充挂号相关接口,api.js 调整
- 就诊人:claim-patient-modal 复用弹窗
- 医生信息:doctor-detail/doctor-userinfo 配合排班展示
- 规范:新增 Api-Module-Split.mdc 接口模块拆分规则
2026-08-27 15:33:00 +08:00
李琦
4d27397291 feat: 患者端挂号预约流程改造与就诊人选择
- 挂号预约:register-info 流程重构、确认步骤/患者选择步骤组件、我的预约(myappiont)页对接
- 接口:register.js 补充挂号相关接口,api.js 调整
- 就诊人:claim-patient-modal 复用弹窗
- 医生信息:doctor-detail/doctor-userinfo 配合排班展示
- 规范:新增 Api-Module-Split.mdc 接口模块拆分规则
2026-08-18 11:13:05 +08:00
李琦
8dd1f5cca5 feat: 首页按后台配置展示订单提醒,挂号详情支持自行改期
首页金刚区和订单提醒样式走配置下发,消息页改为四入口宫格;挂号详情可改期并展示轨迹,取消态展示与支付取值统一按 xk-api 的 code/result。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 08:53:15 +08:00
李琦
cc252b5d1a feat: 患者端挂号预约流程改造与就诊人选择
- 挂号预约:register-info 流程重构、确认步骤/患者选择步骤组件、我的预约(myappiont)页对接
- 接口:register.js 补充挂号相关接口,api.js 调整
- 就诊人:claim-patient-modal 复用弹窗
- 医生信息:doctor-detail/doctor-userinfo 配合排班展示
- 规范:新增 Api-Module-Split.mdc 接口模块拆分规则
2026-08-14 13:27:57 +08:00
李琦
c681cb916b feat:
1. 手机号格式校验
2026-08-11 18:39:38 +08:00
李琦
206f0aaa50 feat:
1. 医生线下接诊创建就诊人、患者端扫码认领就诊人
2. 生成处方后医生代付码
2026-08-09 14:36:22 +08:00
李琦
53c6f3fcfd feat: 患者信息身份证改成非必填 2026-08-08 18:36:38 +08:00
李琦
d4a5115a80 feat: 患者端api迁移 2026-08-08 11:23:35 +08:00
李琦
74272d61d2 feat: 修复部分功能(健康信息、VIP、医生端健康信息导入) 2026-08-08 10:33:59 +08:00
李琦
874d1e7731 feat: 修复bug 2026-08-08 08:10:40 +08:00
李琦
69c3e6d30e feat: 病历模块优化、AI辅助问诊、接入deepseek 2026-08-06 18:08:51 +08:00
李琦
f9dd9ad3a2 feat: 诊所问题优化 2026-07-30 07:37:29 +08:00
李琦
5629955dd3 feat: 诊所问题优化 2026-07-28 14:22:56 +08:00
李琦
77a690a071 feat(订单页面): 开票 2026-07-28 12:45:56 +08:00
李琦
8b455b1920 feat(home/chat): 列表骨架屏、聊天室己方头像与协议/医生详情优化
- 新增分包组件 ListSkeleton(doctor/message/product/article/category/zone)
- 首页/消息/资讯首次加载改用骨架屏,刷新不再清空旧数据避免闪空
  · 去掉 home getList、消息 onShow 中的数组清空
  · 三页 pages.json 增加 easycom 与 componentPlaceholder 主包异步引用
- 聊天室己方头像改为读取本地 userinfo.avatarurl(登录后微信头像),
  mycollection 上传头像成功后同步写回 storage
- 修复协议抽屉点不开:page-container 改为 v-if + :show;
  协议名单独可点节点,匹配放宽 name/desc 双向 includes
- 医生气泡头像可点击进入 doctor-detail?readonly=1 只读医生详情,
  隐藏预约挂号区
2026-07-21 16:05:09 +08:00
李琦
2d2a8abfd4 feat(home/chat): 列表骨架屏、聊天室己方头像与协议/医生详情优化
- 新增分包组件 ListSkeleton(doctor/message/product/article/category/zone)
- 首页/消息/资讯首次加载改用骨架屏,刷新不再清空旧数据避免闪空
  · 去掉 home getList、消息 onShow 中的数组清空
  · 三页 pages.json 增加 easycom 与 componentPlaceholder 主包异步引用
- 聊天室己方头像改为读取本地 userinfo.avatarurl(登录后微信头像),
  mycollection 上传头像成功后同步写回 storage
- 修复协议抽屉点不开:page-container 改为 v-if + :show;
  协议名单独可点节点,匹配放宽 name/desc 双向 includes
- 医生气泡头像可点击进入 doctor-detail?readonly=1 只读医生详情,
  隐藏预约挂号区
2026-07-19 16:08:04 +08:00
李琦
2c739324d6 fix:
1. 签收功能接口迁移到新的api
2. 选择药品抽屉优化
2026-07-16 07:52:05 +08:00
李琦
cae91485f6 1. 特色方功能迭代 2026-07-13 16:08:29 +08:00
李琦
b66f47dfbb 1. 特色方功能迭代 2026-07-07 08:50:17 +08:00
李琦
fb0df465a1 1. 特色方功能迭代 2026-07-03 16:48:46 +08:00
李琦
cef264c692 1. 特色方价格计算 2026-07-03 12:33:18 +08:00
李琦
fe7d56027b 1. 特色方 2026-07-03 07:54:03 +08:00
李琦
4ff6564ffb 1. 特色方 2026-07-02 14:11:24 +08:00
李琦
3f699fa2c6 1. 修复支付0元弹出报错 2026-06-24 10:28:47 +08:00
李琦
dcf6c37195 1. 处方修复未通过或者审核中依然能看到药品的问题 2026-06-15 15:57:12 +08:00
李琦
a2cb9fd8ba 1. 处方修复未通过或者审核中依然能看到药品的问题 2026-06-15 13:33:17 +08:00
李琦
aee0973a53 1. 处方修复未通过或者审核中依然能看到药品的问题 2026-06-05 14:41:09 +08:00
李琦
5564c92d33 1. 优化订单模块的UI、接口迁移到新的api 2026-06-01 12:36:08 +08:00
李琦
550332d963 1. 修复价格获取的bug,应该获取诊所|药店价格而不是总仓库的 2026-06-01 11:06:52 +08:00
李琦
105f9eafa2 页面UI更新 2026-06-01 09:52:26 +08:00
李琦
63ea420c03 页面UI更新 2026-05-30 16:54:01 +08:00
李琦
6ec1b72063 保留tabbar在本地 2026-05-15 16:47:46 +08:00
李琦
39843877fd 图片资源更新到云端 2026-05-15 16:42:32 +08:00
李琦
6294a3db0c 修复了一些问题 2026-05-15 16:28:11 +08:00
李琦
cd369ddf09 在线复诊选择药品、说明主诉 2026-05-06 16:41:19 +08:00
李琦
1693b0e236 挂号bug修复 2026-04-08 08:33:15 +08:00
李琦
ce5fc2355b 订单和预约购药页面优化 2026-02-07 23:22:28 +08:00
李琦
4f9ba9cf44 转诊方功能优化 2026-02-07 16:17:01 +08:00
李琦
da8ef2019f 转诊方功能优化 2026-02-07 14:17:30 +08:00
李琦
800aabf5ee 转诊方功能优化 2026-02-07 13:02:15 +08:00
李琦
184ff88bd2 转诊方功能优化 2026-02-07 11:12:18 +08:00
李琦
d233a42754 转诊方功能 2026-02-07 09:42:08 +08:00
李琦
a130f428b9 UI优化 2026-02-02 16:10:46 +08:00
李琦
ca03f17f28 在线问诊默认不能选择到店,聊天UI优化 2026-01-26 16:22:16 +08:00
李琦
e57fd125f7 在线问诊默认不能选择到店,聊天UI优化 2026-01-26 15:55:13 +08:00
李琦
696cb2e0fa 首页默认静默登陆 2026-01-15 12:33:46 +08:00
李琦
c4f322edd6 首页默认静默登陆 2026-01-14 20:59:32 +08:00
李琦
7ed22e2d58 首页默认静默登陆、聊天页面 2026-01-14 18:44:20 +08:00
李琦
11ade5d7ae 迁移一部分接口 2026-01-13 13:36:55 +08:00
169 changed files with 22261 additions and 6293 deletions

View File

@@ -0,0 +1,37 @@
---
description: 接口迁移到 xk-api 后必须拆出独立模块文件,禁止再往 api.js 大文件里塞
alwaysApply: true
---
# API 模块拆分规则xk-api 迁移必读)
`request/api/api.js`(约 600+ 行大杂烩)是历史遗留的 Yii 接口聚合文件。
**任何迁移到 xk-api 的接口、以及所有新增的 xk-api 接口,一律不得写进 `api.js`**,必须提取/新建到 `request/api/` 下的独立业务模块文件。
## 必须做
1. **按业务域建独立文件**`request/api/<module>.js`,小驼峰命名(已有先例:`patient.js`、`order.js`、`register.js`、`product.js`、`medicalRecord.js`、`specialPrescription.js`
2. **统一走 http 封装**`import { post, get } from './http'`,第三参传 `3``HttpUrlMap[3] = '/xkApi'`),不要手拼 `/xkApi` 字符串到 `http()` 老封装上
3. **文件头注释标明后端与取值方式**,每个函数注释标 HTTP 方法与路径,例如:
```js
import { post, get } from './http'
/**
* 就诊人相关接口统一走 xk-apitype=3
* 取值unwrapXkApi → code / result / message
*/
/** 就诊人列表 xk-api POST /patient/list */
export async function getPatientListApi(params = {}) {
return await post('/patient/list', params, 3)
}
```
4. **迁移某接口时**Yii → xk-api把它从 `api.js` 挪到对应模块文件,`api.js` 里原函数直接删除(调用方改 import确实来不及改完所有调用方时才允许在 `api.js` 保留一行 re-export 并标 `@deprecated`
5. 页面取值统一 `unwrapXkApi`(见 Api-Response.mdc
## 禁止做
- 禁止在 `api.js` 中新增任何函数(包括 Yii 接口——新 Yii 接口也应放模块文件)
- 禁止新代码再用 `normalizeXkApiResponse` 这类「把 xk-api 响应抹成 errcode/data」的兼容层那是存量代码的过渡产物
- 禁止一个模块文件里混装两个后端的接口而不加注释区分;同一文件确需并存时,每个函数注释必须标明 `// xk-api` 或 `// Yii`

View File

@@ -0,0 +1,49 @@
---
description: Yii 与 xk-api 接口响应取值规范(按后端固定写死,禁止混取)
alwaysApply: true
---
# 接口响应取值Yii vs xk-api
写请求代码时**先确认该接口走哪个后端**,再按对应字段固定取值。
**禁止**运行时猜测(如「有 code 就取 result」**禁止** `result || data` / `code ?? errcode` 混写兼容。
## 形态对照
| | YiioldApi | xk-apiLaravel jok |
|---|---|---|
| 成功码 | `errcode: 0` | `code: 0` |
| 业务数据 | `data` | `result` |
| 提示文案 | `msg` | `message` |
## 强制用法
统一用 `@/utils/api-response.js`,按后端选方法:
```js
// 该接口是 xk-api
import { unwrapXkApi } from '@/utils/api-response.js'
const { ok, payload, message } = unwrapXkApi(res)
// 该接口是 Yii
import { unwrapYiiApi } from '@/utils/api-response.js'
const { ok, payload, message } = unwrapYiiApi(res)
```
也可直接写死(患者端完整响应在 `res.data`
```js
// xk-api
if (res.data.code == 0) { const data = res.data.result }
// Yii
if (res.data.errcode == 0) { const data = res.data.data }
```
API 封装文件注释里标明后端,例如:`// xk-api POST /patient/list`。
## 禁止
- 禁止 `unwrapApi` 一类「自动识别后端再取值」
- 禁止 `res.data.result || res.data.data`
- 禁止把 xk-api 再 `normalize` 成 errcode/data 后按 Yii 读

View File

@@ -0,0 +1,11 @@
---
description: 不懂先问,明确目标后再动手
alwaysApply: true
---
# 先问再做
1. **不懂就问**:需求、业务口径、技术选型、影响范围等有不确定之处,必须先向你确认,禁止凭猜测直接改代码。
2. **一问一答**:每次只提一个最关键的问题,等你回复后再继续;避免一次抛出多个选项把讨论打散。
3. **说明提问原因**:每个问题都要附带「我为什么要问」——例如缺什么信息、有哪些备选理解、猜错会有什么后果。
4. **目标对齐后再动手**:确认对你的目标(要达成什么、不做什么、验收标准)有明确认知后,再开始查代码、写 SQL、改页面。

View File

@@ -0,0 +1,12 @@
---
description:
alwaysApply: true
---
1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则
2. 注意不要生成太多的空行上一部分代码和下一部分代码中间的空行不要大于2行
3. 有封装好的方法、组件需要复用,不要重复造轮子
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面记得使用v-if而不是v-show
5. 数据库的created_at、updated_at、deleted_at统一使用时间戳不要使用字符串并且我在查询器中一级格式化成字符串了无需再次格式化
6. 微信小程序 `.vue` 模板(会编译成 WXML禁止使用 `??``?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.`
7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定

View File

@@ -0,0 +1,15 @@
---
description:
alwaysApply: true
---
1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则
2. 注意不要生成太多的空行上一部分代码和下一部分代码中间的空行不要大于2行
3. 有封装好的方法、组件需要复用,不要重复造轮子
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面记得使用v-if而不是v-show
5. 数据库的created_at、updated_at、deleted_at统一使用时间戳不要使用字符串并且我在查询器中一级格式化成字符串了无需再次格式化
6. 微信小程序 `.vue` 模板(会编译成 WXML禁止使用 `??`、`?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.`
7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定
8. 接口取值:写代码时先确认接口是 xk-api 还是 Yii再固定用 `code/result/message` 或 `errcode/data/msg`(可用 `unwrapXkApi` / `unwrapYiiApi`);禁止运行时猜测、禁止 `result || data`(详见 Api-Response.mdc
9. 卡片强调样式:禁止用左侧竖色条(`border-left` 色条/左侧色块做卡片强调或分类标识统一用「1px 同色系边框 + 同色低透明度外发光box-shadow 微光)」;历史页面暂不强制回改,新增/改动的卡片必须遵守
10. API 模块拆分:迁移到 xk-api 的接口和所有新增接口必须放 `request/api/<module>.js` 独立模块文件,禁止再往 `request/api/api.js` 大文件里新增(详见 Api-Module-Split.mdc

2
.gitignore vendored
View File

@@ -117,3 +117,5 @@ dist
/.hbuilderx/ /.hbuilderx/
/.idea/ /.idea/
/database/ /database/
/utils/utils.js
/.cursor/skills/

View File

@@ -14,7 +14,7 @@
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues"> <option name="myValues">
<value> <value>
<list size="16"> <list size="17">
<item index="0" class="java.lang.String" itemvalue="nobr" /> <item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" /> <item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" /> <item index="2" class="java.lang.String" itemvalue="comment" />
@@ -31,6 +31,7 @@
<item index="13" class="java.lang.String" itemvalue="u-form-item" /> <item index="13" class="java.lang.String" itemvalue="u-form-item" />
<item index="14" class="java.lang.String" itemvalue="u-checkbox-group" /> <item index="14" class="java.lang.String" itemvalue="u-checkbox-group" />
<item index="15" class="java.lang.String" itemvalue="u-checkbox" /> <item index="15" class="java.lang.String" itemvalue="u-checkbox" />
<item index="16" class="java.lang.String" itemvalue="u-parse" />
</list> </list>
</value> </value>
</option> </option>

1
.mimocode/.cron-lock Normal file
View File

@@ -0,0 +1 @@
{"pid":36108,"startedAt":1783929676073}

179
App.vue
View File

@@ -12,6 +12,16 @@
export default { export default {
// //初始化状态-如果登录过,保持登录状态 // //初始化状态-如果登录过,保持登录状态
onLaunch: async function() { onLaunch: async function() {
silentLogin().then((loginResult) => {
if (loginResult.success) {
this.tryHandlePendingSceneActions()
} else {
console.error('静默登录失败:', loginResult.message);
}
}).catch((error) => {
console.error('静默登录异常:', error);
});
const version = 4 // 如果当前版本和后台返回的版本一致,那就是审核中,审核通过后 后台设置加1如果后台的比本地的大 那就是生产版本 const version = 4 // 如果当前版本和后台返回的版本一致,那就是审核中,审核通过后 后台设置加1如果后台的比本地的大 那就是生产版本
const res = await getVersion() const res = await getVersion()
const val = Number(res.data.data.value) const val = Number(res.data.data.value)
@@ -34,54 +44,54 @@
// 存储当前版本,也可以使用globalData // 存储当前版本,也可以使用globalData
uni.setStorageSync('isShow_envVersion', isShow) uni.setStorageSync('isShow_envVersion', isShow)
// 检查是否已登录,如果未登录才检查配置并决定是否静默登录 // // 检查是否已登录,如果未登录才检查配置并决定是否静默登录
const token = uni.getStorageSync('token'); // const token = uni.getStorageSync('token');
const userInfo = uni.getStorageSync('userinfo'); // const userInfo = uni.getStorageSync('userinfo');
if (!token || !userInfo) { // if (!token || !userInfo) {
// 未登录,获取系统配置,判断是否需要静默登录 // 未登录,获取系统配置,判断是否需要静默登录
try { // try {
const configRes = await getSystemConfig({ // const configRes = getSystemConfig({
method: "get", // method: "get",
data: { // data: {
store_id: uni.getStorageSync('store_id') || '11001' // store_id: uni.getStorageSync('store_id') || '11001'
} // }
}); // });
//
// 根据配置决定是否静默登录 // 根据配置决定是否静默登录
// 注意:需要根据实际接口返回格式解析 need_login 字段 // 注意:需要根据实际接口返回格式解析 need_login 字段
const isGuest = isGuestMode(); // const isGuest = isGuestMode();
let needLogin = true; // 默认需要登录页 // let needLogin = false; // 默认需要登录页
//
if (isGuest && (configRes.data.code === 0 || configRes.data.code === '0')) { // if (isGuest && (configRes.data.code === 0 || configRes.data.code === '0')) {
// 新格式:从 result 中获取 // // 新格式:从 result 中获取
const configData = configRes.data.result || {}; // const configData = configRes.data.result || {};
needLogin = configData.need_login !== false; // false 表示静默登录 // needLogin = configData.need_login !== false; // false 表示静默登录
} else if (!isGuest && (configRes.data.errcode === 0 || configRes.data.errcode === '0')) { // } else if (!isGuest && (configRes.data?.errcode === 0 || configRes.data?.errcode === '0')) {
// 旧格式:从 data 中获取 // // 旧格式:从 data 中获取
const configData = configRes.data.data || {}; // const configData = configRes.data.data || {};
needLogin = configData.need_login !== false; // false 表示静默登录 // needLogin = configData.need_login !== false; // false 表示静默登录
} // }
//
// 如果配置为静默登录need_login: false直接执行静默登录 // // 如果配置为静默登录need_login: false直接执行静默登录
if (!needLogin) { // if (!needLogin) {
// 执行静默登录(不显示提示,不跳转) // // 执行静默登录(不显示提示,不跳转)
silentLogin().then((loginResult) => { // silentLogin().then((loginResult) => {
if (loginResult.success) { // if (loginResult.success) {
console.log('静默登录成功'); // console.log('静默登录成功');
} else { // } else {
console.error('静默登录失败:', loginResult.message); // console.error('静默登录失败:', loginResult.message);
} // }
}).catch((error) => { // }).catch((error) => {
console.error('静默登录异常:', error); // console.error('静默登录异常:', error);
}); // });
} // }
// 如果配置为需要登录页need_login: true不执行任何操作等待用户手动登录 // // 如果配置为需要登录页need_login: true不执行任何操作等待用户手动登录
} catch (error) { // } catch (error) {
// 配置接口调用失败,不影响应用启动,仅记录日志 // // 配置接口调用失败,不影响应用启动,仅记录日志
console.error('获取系统配置失败:', error); // console.error('获取系统配置失败:', error);
} // }
} // }
console.log('获取更新机制', uni.canIUse('getUpdateManager')) console.log('获取更新机制', uni.canIUse('getUpdateManager'))
// 获取小程序更新机制兼容 // 获取小程序更新机制兼容
@@ -145,28 +155,32 @@
}, },
onShow: function(e) { onShow: function(e) {
// console.log('App Show')
// this.$store.dispatch('initLogin')
// 解析scene里面的参数 // 解析scene里面的参数
if (e === undefined) if (e === undefined)
return {}; return {};
const scene = decodeURIComponent(e.query.scene) const rawScene = e.query && e.query.scene
? decodeURIComponent(e.query.scene)
: ''
// 这里接收扫码参数 // 这里接收扫码参数
if (scene) { if (rawScene) {
// 就诊人认领cup={user_patient_id}
this.parseClaimPatientScene(rawScene)
// 医生代支付ppay={order_id}
this.parseProxyPayScene(rawScene)
// 判断scene中是否包含sal_id // 判断scene中是否包含sal_id
let isSalespersonId = decodeURIComponent(e.query.scene).includes('sal_id'); let isSalespersonId = rawScene.includes('sal_id');
const scene = decodeURIComponent(e.query.scene).split('STORE_QR_CODE_') const scene = rawScene.split('STORE_QR_CODE_')
if (scene[1]) { if (scene[1]) {
uni.setStorageSync('stores_id', scene[1]) uni.setStorageSync('stores_id', scene[1])
uni.setStorageSync('store_id', scene[1]) uni.setStorageSync('store_id', scene[1])
} }
// uni.setStorageSync('stores_id', scene[1]) const st_id = rawScene.split('&')
const st_id = decodeURIComponent(e.query.scene).split('&') let store_id = rawScene.split('STORE_QR_CODE_')
let store_id = decodeURIComponent(e.query.scene).split('STORE_QR_CODE_') store_id = decodeURIComponent(store_id[1] || '').split('&')
store_id = decodeURIComponent(store_id[1]).split('&') const doctors_id = rawScene.split('=')
const doctors_id = decodeURIComponent(e.query.scene).split('=') if (store_id[0] && store_id[0] !== 'undefined') {
if (store_id[0] !== 'undefined') {
uni.setStorageSync('stores_id', store_id[0]) uni.setStorageSync('stores_id', store_id[0])
uni.setStorageSync('store_id', store_id[0]) uni.setStorageSync('store_id', store_id[0])
} }
@@ -185,11 +199,9 @@
console.log('userId', userId, st_id[0].split('su_id=')[1], res); console.log('userId', userId, st_id[0].split('su_id=')[1], res);
}) })
} }
// console.log(st_id, '4444111111111');
// console.log(scene, '4444');
} }
// const scene = decodeURIComponent(e.query.scene).split('STORE_QR_CODE_') // 登录后触发认领弹窗 / 代付跳转
// console.log(scene, '11111'); this.tryHandlePendingSceneActions()
}, },
onHide: function() { onHide: function() {
// console.log('App Hide') // console.log('App Hide')
@@ -200,6 +212,51 @@
// uni.removeStorageSync('token') // uni.removeStorageSync('token')
}, },
methods: { methods: {
/**
* 解析就诊人认领 scenecup=123 或 up_id=123
*/
parseClaimPatientScene(rawScene) {
const cupMatch = String(rawScene).match(/(?:^|&)cup=(\d+)/)
const upMatch = String(rawScene).match(/(?:^|&)up_id=(\d+)/)
const id = Number((cupMatch && cupMatch[1]) || (upMatch && upMatch[1]) || 0)
if (id > 0) {
uni.setStorageSync('claim_user_patient_id', id)
}
},
/**
* 解析医生代支付 sceneppay=订单ID
* 新扫码时重置「已确认」标记,以便重新弹出确认框
*/
parseProxyPayScene(rawScene) {
const match = String(rawScene).match(/(?:^|&)ppay=(\d+)/)
const orderId = Number((match && match[1]) || 0)
if (orderId > 0) {
uni.setStorageSync('proxy_pay_order_id', orderId)
uni.removeStorageSync('proxy_pay_confirmed')
}
},
/**
* 登录后:弹出认领模态 / 代付确认模态(不再直接跳转代付页)
*/
tryHandlePendingSceneActions() {
const token = uni.getStorageSync('token')
if (!token) return
const claimId = Number(uni.getStorageSync('claim_user_patient_id') || 0)
if (claimId > 0) {
// 通知首页等挂载了认领弹窗的页面打开
setTimeout(() => {
uni.$emit('open-claim-patient-modal')
}, 400)
}
const proxyOrderId = Number(uni.getStorageSync('proxy_pay_order_id') || 0)
const proxyConfirmed = Number(uni.getStorageSync('proxy_pay_confirmed') || 0) === 1
// 有代付扫码且尚未确认:弹确认框;已确认过则不自动跳(避免反复打断)
if (proxyOrderId > 0 && !proxyConfirmed) {
setTimeout(() => {
uni.$emit('open-proxy-pay-confirm-modal')
}, 500)
}
},
initWebSocket() { initWebSocket() {
webSocketManager.connect(); webSocketManager.connect();
@@ -246,7 +303,7 @@
// 从消息中提取发送者信息 // 从消息中提取发送者信息
let senderName = '新消息'; let senderName = '新消息';
let senderAvatar = '/static/xx/ysxx.png'; let senderAvatar = 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png';
// 尝试解析发送者ID获取更多信息 // 尝试解析发送者ID获取更多信息
const senderId = message.sender_user_id || ''; const senderId = message.sender_user_id || '';

View File

@@ -21,7 +21,7 @@ export function isGuestMode() {
export function setGuestMode() { export function setGuestMode() {
uni.setStorageSync(GUEST_MODE_KEY, true); uni.setStorageSync(GUEST_MODE_KEY, true);
// 同时设置store_id为11001 // 同时设置store_id为11001
uni.setStorageSync('store_id', GUEST_STORE_ID); // uni.setStorageSync('store_id', GUEST_STORE_ID);
} }
/** /**

30
common/utils/drug.js Normal file
View File

@@ -0,0 +1,30 @@
export function isWesternRxDrug(drug) {
return drug && drug.type == 2 && (drug.is_otc === 0 || drug.is_otc === '0');
}
export function isHealthFood(drug) {
return drug && (drug.type == 3 || drug.category_type === 'health_food');
}
export function canShowFunction(drug, prescriptionApproved) {
if (isHealthFood(drug)) return false;
if (isWesternRxDrug(drug)) return !!prescriptionApproved;
return true;
}
export function canShowUsage(drug, prescriptionApproved) {
if (isHealthFood(drug)) return false;
if (isWesternRxDrug(drug)) return !!prescriptionApproved;
return drug && drug.type != 3;
}
export function canShowInstruction(drug, prescriptionApproved) {
if (isWesternRxDrug(drug)) return !!prescriptionApproved;
return true;
}
export function isPrescriptionApprovedStatus(data) {
if (!data) return false;
const ps = data.prescription_status ?? data.status;
return data.status === true || ps === 1 || ps === '1' || ps === 3 || ps === '3' || ps === 4 || ps === '4';
}

View File

@@ -2,7 +2,7 @@
<view v-if="visible" class="notification-wrapper" :class="{ 'slide-in': visible }" :style="{ paddingTop: notificationTop + 'px' }"> <view v-if="visible" class="notification-wrapper" :class="{ 'slide-in': visible }" :style="{ paddingTop: notificationTop + 'px' }">
<view class="notification" @click="handleClick"> <view class="notification" @click="handleClick">
<!-- 头像 --> <!-- 头像 -->
<image class="avatar" :src="avatar || '/static/xx/ysxx.png'" mode="aspectFill" /> <image class="avatar" :src="avatar || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png'" mode="aspectFill" />
<!-- 内容区域 --> <!-- 内容区域 -->
<view class="content"> <view class="content">

View File

@@ -0,0 +1,539 @@
<template>
<view class="u-upload" v-if="!disabled">
<view
v-if="showUploadList"
class="u-list-item u-preview-wrap"
v-for="(item, index) in lists"
:key="index"
:style="{
width: $u.addUnit(width),
height: $u.addUnit(height)
}"
>
<view
v-if="deletable"
class="u-delete-icon"
@tap.stop="deleteItem(index)"
:style="{
background: delBgColor
}"
>
<u-icon class="u-icon" :name="delIcon" size="20" :color="delColor"></u-icon>
</view>
<u-line-progress
v-if="showProgress && item.progress > 0 && item.progress != 100 && !item.error"
:show-percent="false"
height="16"
class="u-progress"
:percent="item.progress"
></u-line-progress>
<view @tap.stop="retry(index)" v-if="item.error" class="u-error-btn">点击重试</view>
<image @tap.stop="doPreviewImage(item.url || item.path, index)" class="u-preview-image" v-if="!item.isImage" :src="item.url || item.path" :mode="imageMode"></image>
</view>
<slot name="file" :file="lists"></slot>
<view style="display: inline-block;" @tap="selectFile" v-if="maxCount > lists.length">
<slot name="addBtn"></slot>
<view
v-if="!customBtn"
class="u-list-item u-add-wrap"
hover-class="u-add-wrap__hover"
hover-stay-time="150"
:style="{
width: $u.addUnit(width),
height: $u.addUnit(height)
}"
>
<u-icon name="plus" class="u-add-btn" size="40"></u-icon>
<view class="u-add-tips">{{ uploadText }}</view>
</view>
</view>
</view>
</template>
<script>
import { prepareImagePath } from '@/utils/image-compress.js';
export default {
name: 'c-upload',
props: {
showUploadList: {
type: Boolean,
default: true
},
action: {
type: String,
default: ''
},
maxCount: {
type: [String, Number],
default: 52
},
showProgress: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
imageMode: {
type: String,
default: 'aspectFill'
},
header: {
type: Object,
default() {
return {};
}
},
formData: {
type: Object,
default() {
return {};
}
},
name: {
type: String,
default: 'file'
},
sizeType: {
type: Array,
default() {
return ['original', 'compressed'];
}
},
sourceType: {
type: Array,
default() {
return ['album', 'camera'];
}
},
previewFullImage: {
type: Boolean,
default: true
},
multiple: {
type: Boolean,
default: true
},
deletable: {
type: Boolean,
default: true
},
maxSize: {
type: [String, Number],
default: Number.MAX_VALUE
},
fileList: {
type: Array,
default() {
return [];
}
},
uploadText: {
type: String,
default: '选择图片'
},
autoUpload: {
type: Boolean,
default: true
},
showTips: {
type: Boolean,
default: true
},
customBtn: {
type: Boolean,
default: false
},
width: {
type: [String, Number],
default: 200
},
height: {
type: [String, Number],
default: 200
},
delBgColor: {
type: String,
default: '#fa3534'
},
delColor: {
type: String,
default: '#ffffff'
},
delIcon: {
type: String,
default: 'close'
},
toJson: {
type: Boolean,
default: true
},
beforeUpload: {
type: Function,
default: null
},
beforeRemove: {
type: Function,
default: null
},
limitType: {
type: Array,
default() {
return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'image'];
}
},
index: {
type: [Number, String],
default: ''
}
},
data() {
return {
lists: [],
isInCount: true,
uploading: false
};
},
watch: {
fileList: {
immediate: true,
handler(val) {
val.map(value => {
let tmp = this.lists.some(val => {
return val.url == value.url;
})
!tmp && this.lists.push({ url: value.url, error: false, progress: 100 });
});
}
},
lists(n) {
this.$emit('on-list-change', n, this.index);
}
},
methods: {
clear() {
this.lists = [];
},
reUpload() {
this.uploadFile();
},
selectFile() {
if (this.disabled) return;
const { maxCount, multiple, maxSize, sizeType, lists, sourceType } = this;
const newMaxCount = maxCount - lists.length;
uni.chooseImage({
count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
sourceType: sourceType,
sizeType,
success: async (res) => {
const listOldLength = this.lists.length;
for (let index = 0; index < res.tempFiles.length; index++) {
const val = res.tempFiles[index];
if (!this.checkFileExt(val)) continue;
if (!multiple && index >= 1) continue;
if (val.size > maxSize) {
this.$emit('on-oversize', val, this.lists, this.index);
this.showToast('超出允许的文件大小');
continue;
}
if (maxCount <= lists.length) {
this.$emit('on-exceed', val, this.lists, this.index);
this.showToast('超出最大允许的文件个数');
break;
}
try {
const prepared = await prepareImagePath({
path: val.path,
size: val.size,
});
if (!prepared) continue;
lists.push({
url: prepared.path,
progress: 0,
error: false,
file: {
...val,
path: prepared.path,
size: prepared.size,
},
});
} catch (error) {
this.$emit('on-choose-fail', error);
this.showToast('图片处理失败');
}
}
this.$emit('on-choose-complete', this.lists, this.index);
if (this.autoUpload) this.uploadFile(listOldLength);
},
fail: (error) => {
this.$emit('on-choose-fail', error);
}
});
},
showToast(message, force = false) {
if (this.showTips || force) {
uni.showToast({
title: message,
icon: 'none'
});
}
},
upload() {
this.uploadFile();
},
retry(index) {
this.lists[index].progress = 0;
this.lists[index].error = false;
this.lists[index].response = null;
uni.showLoading({
title: '重新上传'
});
this.uploadFile(index);
},
async uploadFile(index = 0) {
if (this.disabled) return;
if (this.uploading) return;
if (index >= this.lists.length) {
this.$emit('on-uploaded', this.lists, this.index);
return;
}
if (this.lists[index].progress == 100) {
if (this.autoUpload == false) this.uploadFile(index + 1);
return;
}
if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
let beforeResponse = this.beforeUpload.bind(this.$u.$parent.call(this))(index, this.lists);
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
await beforeResponse.then(res => {
}).catch(err => {
return this.uploadFile(index + 1);
})
} else if (beforeResponse === false) {
return this.uploadFile(index + 1);
}
}
if (!this.action) {
this.showToast('请配置上传地址', true);
return;
}
this.lists[index].error = false;
this.uploading = true;
const task = uni.uploadFile({
url: this.action,
filePath: this.lists[index].url,
name: this.name,
formData: this.formData,
header: this.header,
// #ifdef MP-ALIPAY
fileType:'image',
// #endif
success: res => {
let data = this.toJson && this.$u.test.jsonString(res.data) ? JSON.parse(res.data) : res.data;
if (![200, 201, 204].includes(res.statusCode)) {
this.uploadError(index, data);
} else {
this.lists[index].response = data;
this.lists[index].progress = 100;
this.lists[index].error = false;
this.$emit('on-success', data, index, this.lists, this.index);
}
},
fail: e => {
this.uploadError(index, e);
},
complete: res => {
uni.hideLoading();
this.uploading = false;
this.uploadFile(index + 1);
this.$emit('on-change', res, index, this.lists, this.index);
}
});
task.onProgressUpdate(res => {
if (res.progress > 0) {
this.lists[index].progress = res.progress;
this.$emit('on-progress', res, index, this.lists, this.index);
}
});
},
uploadError(index, err) {
this.lists[index].progress = 0;
this.lists[index].error = true;
this.lists[index].response = null;
this.$emit('on-error', err, index, this.lists, this.index);
this.showToast('上传失败,请重试');
},
deleteItem(index) {
uni.showModal({
title: '提示',
content: '您确定要删除此项吗?',
success: async (res) => {
if (res.confirm) {
if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
let beforeResponse = this.beforeRemove.bind(this.$u.$parent.call(this))(index, this.lists);
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
await beforeResponse.then(res => {
this.handlerDeleteItem(index);
}).catch(err => {
this.showToast('已终止移除');
})
} else if (beforeResponse === false) {
this.showToast('已终止移除');
} else {
this.handlerDeleteItem(index);
}
} else {
this.handlerDeleteItem(index);
}
}
}
});
},
handlerDeleteItem(index) {
if (this.lists[index].progress < 100 && this.lists[index].progress > 0) {
typeof this.lists[index].uploadTask != 'undefined' && this.lists[index].uploadTask.abort();
}
this.lists.splice(index, 1);
this.$forceUpdate();
this.$emit('on-remove', index, this.lists, this.index);
this.showToast('移除成功');
},
remove(index) {
if (index >= 0 && index < this.lists.length) {
this.lists.splice(index, 1);
this.$emit('on-list-change', this.lists, this.index);
}
},
doPreviewImage(url, index) {
if (!this.previewFullImage) return;
const images = this.lists.map(item => item.url || item.path);
uni.previewImage({
urls: images,
current: url,
success: () => {
this.$emit('on-preview', url, this.lists, this.index);
},
fail: () => {
uni.showToast({
title: '预览图片失败',
icon: 'none'
});
}
});
},
checkFileExt(file) {
let noArrowExt = false;
let fileExt = '';
const reg = /.+\./;
// #ifdef H5
fileExt = file.name.replace(reg, "").toLowerCase();
// #endif
// #ifndef H5
fileExt = file.path.replace(reg, "").toLowerCase();
// #endif
noArrowExt = this.limitType.some(ext => {
return ext.toLowerCase() === fileExt;
})
if (!noArrowExt) this.showToast(`不允许选择${fileExt}格式的文件`);
return noArrowExt;
}
}
};
</script>
<style lang="scss" scoped>
@import 'uview-ui/libs/css/style.components.scss';
.u-upload {
@include vue-flex;
flex-wrap: wrap;
align-items: center;
}
.u-list-item {
width: 200rpx;
height: 200rpx;
overflow: hidden;
margin: 10rpx;
background: rgb(244, 245, 246);
position: relative;
border-radius: 10rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.u-preview-wrap {
border: 1px solid rgb(235, 236, 238);
}
.u-add-wrap {
flex-direction: column;
color: $u-content-color;
font-size: 26rpx;
}
.u-add-tips {
margin-top: 20rpx;
line-height: 40rpx;
}
.u-add-wrap__hover {
background-color: rgb(235, 236, 238);
}
.u-preview-image {
display: block;
width: 100%;
height: 100%;
border-radius: 10rpx;
}
.u-delete-icon {
position: absolute;
top: 10rpx;
right: 10rpx;
z-index: 10;
background-color: $u-type-error;
border-radius: 100rpx;
width: 44rpx;
height: 44rpx;
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-icon {
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-progress {
position: absolute;
bottom: 10rpx;
left: 8rpx;
right: 8rpx;
z-index: 9;
width: auto;
}
.u-error-btn {
color: #ffffff;
background-color: $u-type-error;
font-size: 20rpx;
padding: 4px 0;
text-align: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 9;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<!-- 居中模态框确认认领非底部抽屉 -->
<u-modal
v-model="show"
title="认领就诊人"
:content="modalContent"
:show-cancel-button="true"
confirm-text="确认认领"
cancel-text="取消"
confirm-color="#0d9488"
:mask-close-able="false"
@confirm="onConfirm"
@cancel="onClose"
></u-modal>
</template>
<script>
import { claimPatientApi } from '@/request/api/patient.js'
import { unwrapXkApi } from '@/utils/api-response.js'
import { registePay } from '@/request/api/register.js'
/**
* 扫码认领医生创建的就诊人(居中 u-modal
* App.vue 写入 claim_user_patient_id 后弹出
*/
export default {
name: 'ClaimPatientModal',
data() {
return {
show: false,
userPatientId: 0,
needPay: false,
price: '0',
registerId: 0,
claiming: false,
}
},
computed: {
modalContent() {
const id = this.userPatientId || '—'
let text = '医生为您创建了就诊人档案就诊人ID' + id + '),确认后绑定到当前微信账号。'
if (this.needPay) {
text += '需支付挂号费:¥' + this.price + '。'
}
return text
},
},
methods: {
/**
* 对外打开:读取 storage 中的认领 ID
*/
openFromStorage() {
const id = Number(uni.getStorageSync('claim_user_patient_id') || 0)
if (id < 1) return
const token = uni.getStorageSync('token')
if (!token) return
this.userPatientId = id
this.needPay = false
this.price = '0'
this.registerId = 0
this.show = true
},
/**
* 取消认领:关闭并清理扫码标记
*/
onClose() {
this.show = false
uni.removeStorageSync('claim_user_patient_id')
},
/**
* 确认认领;若有未付挂号费则拉起支付
*/
async onConfirm() {
if (this.claiming || this.userPatientId < 1) return
this.claiming = true
try {
const res = await claimPatientApi({ user_patient_id: this.userPatientId })
const { ok, payload, message } = unwrapXkApi(res)
if (!ok) {
uni.showToast({ title: message || '认领失败', icon: 'none' })
// 失败重新打开,允许重试
this.show = true
return
}
const needPay = Number(payload && payload.need_pay_register) === 1
const registerId = Number(payload && payload.register_id) || 0
const price = (payload && payload.price) || '0'
uni.removeStorageSync('claim_user_patient_id')
if (needPay && registerId > 0) {
this.needPay = true
this.registerId = registerId
this.price = String(price)
this.show = false
await this.payRegister(registerId)
} else {
uni.showToast({ title: '认领成功', icon: 'success' })
this.show = false
}
} catch (e) {
uni.showToast({ title: (e && e.message) || '认领失败', icon: 'none' })
this.show = true
} finally {
this.claiming = false
}
},
/**
* 认领后支付挂号费xk-api register/pay
*/
async payRegister(registerId) {
try {
const res = await registePay({
method: 'post',
data: { register_id: registerId },
})
// registePay 为 xk-api按 code/result/message 取值
if (res.data && (res.data.code == 0 || res.data.code === '0')) {
const data = res.data.result || {}
if (data.is_paid == 1 || data.is_pay == 1) {
uni.showToast({ title: '认领成功', icon: 'success' })
return
}
uni.requestPayment({
provider: data.appId,
timeStamp: data.timestamp || data.timeStamp,
nonceStr: data.nonceStr,
package: data.package,
signType: data.signType,
paySign: data.paySign,
success: () => {
uni.showToast({ title: '支付成功', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '已认领,挂号费未支付', icon: 'none' })
},
})
} else {
uni.showToast({
title: (res.data && res.data.message) || '获取支付参数失败',
icon: 'none',
})
}
} catch (e) {
uni.showToast({ title: '支付发起失败', icon: 'none' })
}
},
},
}
</script>

View File

@@ -0,0 +1,676 @@
<template>
<u-popup v-model="visible" mode="center" width="85%" border-radius="16" :mask-close-able="false">
<view class="popup-wrap">
<view class="popup-title">图片较大</view>
<view v-if="sizeText" class="size-text">{{ sizeText }}</view>
<view v-if="status === 'compressing'" class="compressing-panel">
<image
v-if="originalPath"
:src="originalPath"
mode="aspectFit"
class="preview-image single"
@click="previewImage(originalPath)"
/>
<text v-if="originalPath" class="preview-hint">点击预览</text>
<view class="compressing-status">
<u-loading mode="circle" />
<text class="progress-text">{{ progressText }}</text>
</view>
</view>
<view v-else-if="status === 'ready'" class="compare-panel">
<view class="preview-column">
<text class="preview-label">原图</text>
<image
:src="originalPath"
mode="aspectFit"
class="preview-image"
@click="previewImage(originalPath)"
/>
<text class="preview-size">{{ formatFileSize(originalSize) }}</text>
<text class="preview-hint">点击预览</text>
</view>
<view class="preview-column">
<text class="preview-label">压缩后</text>
<image
:src="compressedPath"
mode="aspectFit"
class="preview-image"
@click="previewImage(compressedPath)"
/>
<text class="preview-size">{{ formatFileSize(compressedSize) }}</text>
<text class="preview-hint">点击预览</text>
</view>
</view>
<view v-else-if="status === 'gifSkipped'" class="gif-panel">
<image
v-if="originalPath"
:src="originalPath"
mode="aspectFit"
class="preview-image single"
@click="previewImage(originalPath)"
/>
<text v-if="originalPath" class="preview-hint">点击预览</text>
<text class="hint-text">GIF 不支持压缩将上传原图</text>
</view>
<view v-else class="error-panel">
<image
v-if="originalPath"
:src="originalPath"
mode="aspectFit"
class="preview-image single"
@click="previewImage(originalPath)"
/>
<text v-if="originalPath" class="preview-hint">点击预览</text>
<text class="error-text">{{ errorMessage }}</text>
</view>
<view class="footer-actions">
<view class="btn btn-default" @click="finish('cancel')">取消</view>
<view
v-if="status === 'ready' || status === 'error'"
class="btn btn-default"
@click="finish('original')"
>
{{ originalButtonLabel }}
</view>
<view
v-if="status === 'ready'"
class="btn btn-primary"
@click="finish('compressed')"
>
{{ compressedButtonLabel }}
</view>
<view
v-if="status === 'gifSkipped'"
class="btn btn-primary"
@click="finish('original')"
>
{{ originalButtonLabel }}
</view>
</view>
</view>
</u-popup>
</template>
<script>
import {
formatFileSize,
formatSavingsPercent,
isGifPath,
tryCompressImage,
} from '@/utils/image-compress.js';
export default {
name: 'ImageCompressPopup',
data() {
return {
visible: false,
status: 'compressing',
progressText: '正在压缩,请稍候…',
errorMessage: '',
originalPath: '',
compressedPath: '',
originalSize: 0,
compressedSize: 0,
skipped: false,
resolver: null,
};
},
computed: {
sizeText() {
if (!this.originalSize) return '';
const originalText = formatFileSize(this.originalSize);
if (this.status === 'gifSkipped') {
return `原图 ${originalText}`;
}
if (this.compressedSize && this.status === 'ready') {
const savings = formatSavingsPercent(this.originalSize, this.compressedSize);
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}${savings}`;
}
if (this.compressedSize) {
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}`;
}
return `原图 ${originalText}`;
},
originalButtonLabel() {
if (!this.originalSize) return '使用原图';
return `使用原图 (${formatFileSize(this.originalSize)})`;
},
compressedButtonLabel() {
if (!this.compressedSize) return '使用压缩图';
return `使用压缩图 (${formatFileSize(this.compressedSize)})`;
},
},
methods: {
formatFileSize,
previewImage(current) {
if (!this.originalPath) return;
const urls =
this.status === 'ready' && this.compressedPath
? [this.originalPath, this.compressedPath]
: [this.originalPath];
uni.previewImage({
urls,
current,
});
},
open({ path, size }) {
return new Promise((resolve) => {
this.resolver = resolve;
this.originalPath = path;
this.originalSize = size;
this.compressedPath = '';
this.compressedSize = 0;
this.skipped = false;
this.errorMessage = '';
this.progressText = '正在压缩,请稍候…';
this.visible = true;
this.startCompress(path, size);
});
},
async startCompress(path, size) {
if (isGifPath(path)) {
this.skipped = true;
this.status = 'gifSkipped';
return;
}
this.status = 'compressing';
this.progressText = '正在压缩(质量 80%)…';
try {
const compressed = await tryCompressImage(path, [80, 60, 40], (quality) => {
this.progressText = `正在压缩(质量 ${quality}%)…`;
});
this.compressedPath = compressed.path;
this.compressedSize = compressed.size;
this.status = 'ready';
this.progressText = '';
} catch (error) {
this.status = 'error';
this.errorMessage = '图片压缩失败,请使用原图或取消';
this.progressText = '';
}
},
finish(choice) {
const result = { choice, path: null, size: 0, usedCompress: false };
if (choice === 'cancel') {
this.visible = false;
this.resolver && this.resolver(null);
this.resolver = null;
return;
}
if (choice === 'compressed' && this.status === 'ready') {
result.path = this.compressedPath;
result.size = this.compressedSize;
result.usedCompress = true;
} else {
result.path = this.originalPath;
result.size = this.originalSize;
result.usedCompress = false;
}
this.visible = false;
this.resolver && this.resolver(result);
this.resolver = null;
},
},
};
</script>
<style lang="scss" scoped>
.popup-wrap {
padding: 32rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
text-align: center;
margin-bottom: 16rpx;
}
.size-text {
font-size: 26rpx;
color: #666;
text-align: center;
margin-bottom: 24rpx;
}
.preview-image {
width: 100%;
height: 280rpx;
background: #fafafa;
border-radius: 12rpx;
}
.preview-image.single {
width: 100%;
}
.compare-panel {
display: flex;
gap: 16rpx;
}
.preview-column {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.preview-label {
font-size: 26rpx;
font-weight: 500;
margin-bottom: 12rpx;
}
.preview-size {
margin-top: 8rpx;
font-size: 22rpx;
color: #999;
}
.preview-hint {
margin-top: 4rpx;
font-size: 22rpx;
color: #bbb;
}
.compressing-panel,
.gif-panel,
.error-panel {
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
}
.compressing-status {
display: flex;
align-items: center;
gap: 16rpx;
}
.progress-text,
.hint-text {
font-size: 26rpx;
color: #666;
}
.error-text {
font-size: 26rpx;
color: #fa3534;
}
.footer-actions {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 32rpx;
}
.btn {
min-width: 160rpx;
padding: 16rpx 24rpx;
border-radius: 8rpx;
font-size: 26rpx;
text-align: center;
}
.btn-default {
background: #f5f5f5;
color: #333;
}
.btn-primary {
background: #2979ff;
color: #fff;
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<!-- 居中模态框确认代付非底部抽屉 -->
<u-modal
v-model="show"
title="医生代付确认"
:content="modalContent"
:show-cancel-button="true"
confirm-text="确认代付"
cancel-text="取消代付"
confirm-color="#0d9488"
:mask-close-able="false"
@confirm="onConfirm"
@cancel="onCancel"
></u-modal>
</template>
<script>
/**
* 扫码代付前确认模态框
* App.vue 写入 proxy_pay_order_id 后弹出;取消则清理 storage
*/
export default {
name: 'ProxyPayConfirmModal',
data() {
return {
show: false,
orderId: 0,
}
},
computed: {
modalContent() {
const id = this.orderId || '—'
return '医生邀请您代为支付药品订单订单ID' + id + ')。确认后进入支付页;取消则清除本次扫码,下次进入小程序不会再跳转。'
},
},
methods: {
/**
* 清理本次代付扫码会话storage
*/
clearProxyPaySession() {
uni.removeStorageSync('proxy_pay_order_id')
uni.removeStorageSync('proxy_pay_confirmed')
},
/**
* 对外打开:读取 storage 中的代付订单 ID
*/
openFromStorage() {
const id = Number(uni.getStorageSync('proxy_pay_order_id') || 0)
if (id < 1) return
// 已确认过并进过支付页:不再弹窗(避免首页 onShow 反复打断)
if (Number(uni.getStorageSync('proxy_pay_confirmed') || 0) === 1) return
const token = uni.getStorageSync('token')
if (!token) return
this.orderId = id
this.show = true
},
/**
* 取消代付:清理扫码标记,下次进小程序不再跳转
*/
onCancel() {
this.clearProxyPaySession()
this.show = false
uni.showToast({ title: '已取消代付', icon: 'none' })
},
/**
* 确认后进入订单支付页(保留 proxy_pay_order_id 供支付接口使用)
*/
onConfirm() {
const orderId = this.orderId || Number(uni.getStorageSync('proxy_pay_order_id') || 0)
if (orderId < 1) {
this.clearProxyPaySession()
this.show = false
return
}
uni.setStorageSync('proxy_pay_confirmed', 1)
this.show = false
uni.navigateTo({
url: '/subPackages/my/record-pay?order_id=' + orderId + '&proxy_pay=1',
})
},
},
}
</script>

View File

@@ -0,0 +1,181 @@
<template>
<u-popup
v-model="popupVisible"
mode="bottom"
border-radius="24"
:safe-area-inset-bottom="true"
:closeable="true"
@close="onPopupClose"
>
<view class="drawer-inner">
<view class="drawer-header">
<text class="drawer-title">选择待支付挂号</text>
</view>
<view v-if="!list.length" class="drawer-empty">暂无待支付挂号订单</view>
<scroll-view v-else scroll-y class="drawer-scroll">
<view
v-for="item in list"
:key="item.id"
class="drawer-item"
@click="onSelect(item)"
>
<view class="item-main">
<view class="item-row">
<text class="item-label">就诊人</text>
<text class="item-value">{{ displayText(item.patient) }}</text>
</view>
<view class="item-row">
<text class="item-label">医生</text>
<text class="item-value">{{ displayText(item.doctor) }}</text>
</view>
<view class="item-row">
<text class="item-label">诊所</text>
<text class="item-value">{{ displayText(item.store) }}</text>
</view>
<view class="item-row">
<text class="item-label">挂号时间</text>
<text class="item-value">{{ displayText(item.created_at) }}</text>
</view>
</view>
<u-icon name="arrow-right" color="#94A3B8" size="28"></u-icon>
</view>
</scroll-view>
</view>
</u-popup>
</template>
<script>
/**
* 待支付挂号订单选择抽屉
* 首页多条待付款挂号时,供用户选择具体订单前往支付
*/
export default {
name: 'UnpaidRegisterDrawer',
props: {
// v-model 控制抽屉显示隐藏
value: {
type: Boolean,
default: false,
},
// 待支付挂号列表,字段来自 fetchUnpaidRegisterListApi
list: {
type: Array,
default: () => [],
},
},
computed: {
popupVisible: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
},
},
},
methods: {
/**
* 字段为空时显示占位符
* @param {string} val 展示值
*/
displayText(val) {
const text = val != null ? String(val).trim() : '';
return text || '--';
},
/**
* 关闭抽屉
*/
onPopupClose() {
this.$emit('input', false);
},
/**
* 选中某条挂号订单,通知父组件跳转支付
* @param {Object} item 挂号订单
*/
onSelect(item) {
if (!item || item.id == null) return;
this.$emit('select', item.id);
this.$emit('input', false);
},
},
};
</script>
<style lang="scss" scoped>
.drawer-inner {
display: flex;
flex-direction: column;
max-height: 70vh;
box-sizing: border-box;
padding: 24rpx 24rpx 0;
}
.drawer-header {
padding-bottom: 20rpx;
text-align: center;
}
.drawer-title {
font-size: 32rpx;
font-weight: 600;
color: #0f172a;
}
.drawer-empty {
padding: 48rpx 0 64rpx;
text-align: center;
color: #94a3b8;
font-size: 28rpx;
}
.drawer-scroll {
max-height: 60vh;
min-height: 200rpx;
}
.drawer-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 20rpx;
margin-bottom: 16rpx;
background: linear-gradient(90deg, rgba(65, 117, 238, 0.06) 0%, rgba(65, 117, 238, 0.02) 100%);
border: 1rpx solid rgba(65, 117, 238, 0.15);
border-radius: 16rpx;
&:active {
opacity: 0.85;
}
}
.item-main {
flex: 1;
min-width: 0;
margin-right: 16rpx;
}
.item-row {
display: flex;
align-items: flex-start;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
}
.item-label {
flex-shrink: 0;
width: 140rpx;
font-size: 26rpx;
color: #64748b;
}
.item-value {
flex: 1;
font-size: 26rpx;
color: #0f172a;
line-height: 1.5;
word-break: break-all;
}
</style>

View File

@@ -6,12 +6,20 @@ import Vue from 'vue'
// main.js // main.js
import uView from "uview-ui"; import uView from "uview-ui";
import share from '@/common/share.js' import share from '@/common/share.js'
import {setGuestMode} from "@/common/utils/auth";
Vue.mixin(share) Vue.mixin(share)
Vue.use(uView); Vue.use(uView);
//防抖函数 //防抖函数
import common from './common/common.js' import common from './common/common.js'
Vue.prototype.$noMultipleClicks = common.noMultipleClicks; Vue.prototype.$noMultipleClicks = common.noMultipleClicks;
const token = uni.getStorageSync('token');
const userInfo = uni.getStorageSync('userinfo');
if (!token || !userInfo) {
console.log('未登录')
setGuestMode();
}
//时间戳的处理 //时间戳的处理
Vue.filter("formatDate", function(value) { Vue.filter("formatDate", function(value) {
var date = new Date(value * 1000); //时间戳为10位需*1000时间戳为13位的话不需乘1000 var date = new Date(value * 1000); //时间戳为10位需*1000时间戳为13位的话不需乘1000

181
mixins/patientTabList.js Normal file
View File

@@ -0,0 +1,181 @@
/**
* 就诊人 Tab + swiper 列表公共逻辑(处方/挂号/病历复用)
* 页面需实现fetchPatientList(patientId, page) → Promise 返回完整接口 res
*/
import { getPatientListApi } from '@/request/api/patient.js'
import { unwrapXkApi } from '@/utils/api-response.js'
export default {
data() {
return {
patientList: [],
patientTabs: [],
current: 0,
tabChanging: false,
infoList: [],
page: 1,
totalPage: 1,
total: 0,
loadMoreStatus: 'nomore',
isRefreshing: false,
scrollTop: 0,
emptyPatient: false,
}
},
computed: {
currentPatientId() {
const p = this.patientList[this.current]
return p ? Number(p.id) : 0
},
},
methods: {
/**
* 拉取就诊人并加载当前 Tab 列表
*/
async bootstrapPatientTabs() {
try {
const res = await getPatientListApi({})
// getPatientListApi → xk-api
const { ok, payload } = unwrapXkApi(res)
if (!ok) {
this.patientList = []
this.patientTabs = []
this.emptyPatient = true
this.infoList = []
return
}
// xk-api{ list, latest_register_user_patient_id };兼容旧版纯数组
const list = Array.isArray(payload) ? payload : (payload && payload.list) || []
const latestId = Array.isArray(payload)
? 0
: Number((payload && payload.latest_register_user_patient_id) || 0)
this.patientList = list
this.patientTabs = list.map((p) => ({ name: p.name || '就诊人' }))
this.emptyPatient = list.length === 0
if (list.length === 0) {
this.infoList = []
return
}
// 优先最近挂号就诊人,其次默认就诊人,否则第一个
let idx = -1
if (latestId > 0) {
idx = list.findIndex((p) => Number(p.id) === latestId)
}
if (idx < 0) {
idx = list.findIndex((p) => Number(p.is_default) === 1)
}
if (idx < 0) {
idx = 0
}
if (this.current >= list.length) {
this.current = idx
} else if (!this._tabsBootstrapped) {
this.current = idx
}
this._tabsBootstrapped = true
this.reloadCurrentTab()
} catch (e) {
console.error('bootstrapPatientTabs', e)
this.emptyPatient = true
}
},
onTabChange(index) {
if (this.tabChanging) {
return
}
this.tabChanging = true
this.changePatientTab(index)
this.$nextTick(() => {
this.tabChanging = false
})
},
onSwiperChange(e) {
if (this.tabChanging) {
return
}
this.tabChanging = true
this.changePatientTab(e.detail.current)
this.$nextTick(() => {
this.tabChanging = false
})
},
changePatientTab(index) {
const tabIndex = Number(index) || 0
this.current = tabIndex
this.reloadCurrentTab()
},
reloadCurrentTab() {
this.page = 1
this.infoList = []
this.loadMoreStatus = 'loading'
this.fetchCurrentList()
},
/**
* 页面实现return getXxxListApi({...})
*/
fetchPatientList() {
return Promise.reject(new Error('请实现 fetchPatientList'))
},
fetchCurrentList(callback) {
const patientId = this.currentPatientId
if (!patientId) {
this.infoList = []
this.loadMoreStatus = 'nomore'
if (typeof callback === 'function') {
callback()
}
return
}
this.fetchPatientList(patientId, this.page)
.then((res) => {
// fetchPatientList 约定走 xk-api处方/挂号/病历)
const { ok, payload } = unwrapXkApi(res)
if (!ok || !payload) {
this.loadMoreStatus = 'nomore'
return
}
const list = payload.list || []
const pagination = payload.pagination || {}
this.totalPage = Number(pagination.totalPage) || 1
this.total = Number(pagination.total) || 0
if (this.page > 1) {
this.infoList = this.infoList.concat(list)
} else {
this.infoList = list
}
this.loadMoreStatus = this.page >= this.totalPage ? 'nomore' : 'loadmore'
})
.catch((e) => {
console.error('fetchCurrentList', e)
this.loadMoreStatus = 'nomore'
})
.finally(() => {
if (typeof callback === 'function') {
callback()
}
})
},
onRefresherRefresh() {
this.isRefreshing = true
this.page = 1
this.infoList = []
this.fetchCurrentList(() => {
this.isRefreshing = false
})
},
onOrderScrollToLower() {
if (this.page >= this.totalPage) {
this.loadMoreStatus = 'nomore'
return
}
this.page += 1
this.loadMoreStatus = 'loading'
this.fetchCurrentList()
},
goAddPatient() {
uni.navigateTo({
url: '/subPackages/my/myinfo-add',
})
},
},
}

View File

@@ -3,19 +3,32 @@
// 下载安装方式 // 下载安装方式
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" // "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
// npm安装方式 // npm安装方式
// "^u-upload$": "@/components/c-upload/c-upload.vue",
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue", "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
// 消息通知组件 // 消息通知组件
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue" "^MessageNotification": "@/components/MessageNotification/MessageNotification.vue",
"^ClaimPatientModal$": "@/components/claim-patient-modal/claim-patient-modal.vue",
"^ProxyPayConfirmModal$": "@/components/proxy-pay-confirm-modal/proxy-pay-confirm-modal.vue",
"^FollowUpDrugDrawer$": "@/subPackages/doctor/components/FollowUpDrugDrawer.vue",
// 列表骨架屏(分包组件,主包页需配合 componentPlaceholder
"^ListSkeleton$": "@/subPackages/common/components/ListSkeleton.vue",
// 问诊助手壳(仅分包页使用,放分包以减小主包体积)
"^AssistantConsultationShell$": "@/subPackages/common/components/AssistantConsultationShell.vue"
}, },
"pages": [ "pages": [
{ {
"path": "pages/home/home", "path": "pages/home/home",
"style": { "style": {
// "navigationBarTitleText": "custom", // "navigationBarTitleText": "custom",
"navigationStyle": "custom" "navigationStyle": "custom",
"enablePullDownRefresh": true,
// "navigationStyle": "default", // "navigationStyle": "default",
// "navigationBarBackgroundColor": "#298DFF", // "navigationBarBackgroundColor": "#298DFF",
// "navigationBarTextStyle": "white" // "navigationBarTextStyle": "white"
// 主包异步引用分包 ListSkeleton
"componentPlaceholder": {
"list-skeleton": "view"
}
} }
}, },
{ {
@@ -51,7 +64,12 @@
{ {
"path": "pages/cate/index", "path": "pages/cate/index",
"style": { "style": {
"navigationBarTitleText": "健康资讯" "navigationBarTitleText": "健康资讯",
"enablePullDownRefresh": true,
// 主包异步引用分包 ListSkeleton
"componentPlaceholder": {
"list-skeleton": "view"
}
} }
// "needLogin": true // "needLogin": true
}, },
@@ -65,7 +83,12 @@
{ {
"path": "pages/index/index", "path": "pages/index/index",
"style": { "style": {
"navigationBarTitleText": "消息" "navigationBarTitleText": "消息",
"enablePullDownRefresh": true,
// 主包异步引用分包 ListSkeleton
"componentPlaceholder": {
"list-skeleton": "view"
}
} }
// "needLogin": true // "needLogin": true
}, },
@@ -185,6 +208,28 @@
"navigationBarTitleText": "商品详情", "navigationBarTitleText": "商品详情",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
},
{
"path": "follow-up/medication-info",
"style": {
"navigationBarTitleText": "用药信息",
"enablePullDownRefresh": false
}
},
{
"path": "special-prescription/list",
"style": {
"navigationBarTitleText": "特色药方",
"enablePullDownRefresh": false,
"disableScroll": true
}
},
{
"path": "special-prescription/detail",
"style": {
"navigationBarTitleText": "药方详情",
"enablePullDownRefresh": false
}
} }
] ]
}, },
@@ -270,18 +315,32 @@
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
}, },
{
"path": "my/mymedical",
"style": {
"navigationBarTitleText": "我的病历",
"enablePullDownRefresh": false
}
},
{
"path": "my/mymedical-detail",
"style": {
"navigationBarTitleText": "病历详情",
"enablePullDownRefresh": false
}
},
{ {
"path": "my/myrecord", "path": "my/myrecord",
"style": { "style": {
"navigationBarTitleText": "处方记录", "navigationBarTitleText": "我的处方",
"enablePullDownRefresh": true "enablePullDownRefresh": false
} }
}, },
{ {
"path": "my/myappiont", "path": "my/myappiont",
"style": { "style": {
"navigationBarTitleText": "挂号记录", "navigationBarTitleText": "我的挂号",
"enablePullDownRefresh": true "enablePullDownRefresh": false
} }
}, },
{ {
@@ -312,6 +371,20 @@
"navigationBarTitleText": "订单详情", "navigationBarTitleText": "订单详情",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
},
{
"path": "my/my-drug/invoice-apply",
"style": {
"navigationBarTitleText": "申请开票",
"enablePullDownRefresh": true
}
},
{
"path": "my/my-drug/invoice-detail",
"style": {
"navigationBarTitleText": "开票详情",
"enablePullDownRefresh": false
}
} }
] ]
}, },
@@ -466,6 +539,21 @@
"path": "chat/chat", "path": "chat/chat",
"style": { "style": {
"navigationBarTitleText": "在线问诊", "navigationBarTitleText": "在线问诊",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "transfer/transfer-message",
"style": {
"navigationBarTitleText": "转诊消息",
"enablePullDownRefresh": true
}
},
{
"path": "transfer/transfer-consultation",
"style": {
"navigationBarTitleText": "转诊咨询",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
} }

View File

@@ -43,11 +43,11 @@
<!-- 选择坐诊医生 --> <!-- 选择坐诊医生 -->
<view class="doctor_list">选择坐诊医生</view> <view class="doctor_list">选择坐诊医生</view>
<!-- 列表 --> <!-- 列表 -->
<view class="list" v-for="(item,index) in doctorList" :key="item.id" @click="goLookDoctor(item.id)"> <view class="list" v-for="(item,index) in doctorList" :key="item.id">
<!-- 图片部分 --> <!-- 图片部分 -->
<view class="list_info"> <view class="list_info">
<view class="list_left"> <view class="list_left" @click="goLookDoctor(item.id)">
<image :src="item.avatar || '/static/mine/avatar_1.png'" mode="医生头像"></image> <image :src="item.avatar || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode="医生头像"></image>
<!-- 医生 --> <!-- 医生 -->
<view class="list_title"> <view class="list_title">
<view class="titles_name"> <view class="titles_name">
@@ -61,10 +61,10 @@
</view> </view>
</view> </view>
<view class="list_right">{{ typeMap[type] }}</view> <view class="list_right" @click.stop="goRegister(item)">{{ typeMap[type] }}</view>
</view> </view>
<!-- 文字部分 --> <!-- 文字部分 -->
<view class="card"> <view class="card" @click="goLookDoctor(item.id)">
<!-- 擅长 --> <!-- 擅长 -->
<view class="list_going"> <view class="list_going">
擅长{{item.good_at}} 擅长{{item.good_at}}
@@ -129,7 +129,7 @@
import { import {
dDropdown dDropdown
} from '@/components/d-dropdown/d-dropdown' } from '@/components/d-dropdown/d-dropdown'
import { isGuestMode } from '@/common/utils/auth.js' import { isGuestMode, isLoggedIn, saveRedirectInfo, buildUrlWithParams } from '@/common/utils/auth.js'
export default { export default {
components: { components: {
dDropdown dDropdown
@@ -172,11 +172,71 @@
type: "", type: "",
} }
}, },
onLoad(e) { async onLoad(e) {
this.type = e.type this.type = e.type
// console.log(e, 'idd'); // console.log(e, 'idd');
// 当 type === 2 (诊所在线问诊) 时,直接获取委托诊所和医生信息并跳转到挂号页面
if (this.type === '2') {
try {
const storeId = uni.getStorageSync('store_id') || '11001';
const { getClinicOnlineConsultationInfoApi } = await import('@/request/api/product');
const configRes = await getClinicOnlineConsultationInfoApi({ store_id: storeId });
if (configRes.data?.code === 0 || configRes.data?.code === '0') {
const result = configRes.data?.result;
if (result?.can_use && result?.delegate_doctor_id) {
// 直接跳转到就诊人选择页面传递医生ID和挂号类型
uni.redirectTo({
url: `/subPackages/doctor/doctor-userinfo?id=${result.delegate_doctor_id}&r_type=2&delegate_store_id=${result.delegate_store_id || storeId}`
});
return;
} else {
uni.showToast({
title: result?.message || '该门店暂不支持在线问诊功能',
icon: 'none',
duration: 2000
});
// 返回上一页
setTimeout(() => {
uni.navigateBack();
}, 2000);
return;
}
} else {
uni.showToast({
title: configRes.data?.message || '获取在线问诊信息失败',
icon: 'none',
duration: 2000
});
// 返回上一页
setTimeout(() => {
uni.navigateBack();
}, 2000);
return;
}
} catch (error) {
console.error('获取在线问诊信息失败:', error);
uni.showToast({
title: '获取在线问诊信息失败',
icon: 'none',
duration: 2000
});
// 返回上一页
setTimeout(() => {
uni.navigateBack();
}, 2000);
return;
}
}
}, },
methods: { methods: {
resolveRegisterType(pageType) {
const t = String(pageType ?? '0');
if (['0', '1', '2', '3'].includes(t)) return t;
return '0';
},
closeDropdown() { closeDropdown() {
this.$refs.uDropdown.close(); this.$refs.uDropdown.close();
this.getList() this.getList()
@@ -254,6 +314,60 @@
}) })
}, },
async goRegister(item) {
if (!isLoggedIn()) {
const storeId = uni.getStorageSync('store_id') || '11001';
saveRedirectInfo('/subPackages/doctor/doctor-userinfo', {
id: item.id,
r_type: this.resolveRegisterType(this.type),
store_id: storeId,
});
uni.navigateTo({
url: buildUrlWithParams('/pages/login/login', {
redirect: 'doctor-userinfo',
doctor_id: item.id,
store_id: storeId,
})
});
return;
}
const rType = this.resolveRegisterType(this.type);
if (rType === '2') {
try {
const storeId = uni.getStorageSync('store_id') || '11001';
const { getClinicOnlineConsultationInfoApi } = await import('@/request/api/product');
const configRes = await getClinicOnlineConsultationInfoApi({ store_id: storeId });
if (configRes.data?.code === 0 || configRes.data?.code === '0') {
const result = configRes.data?.result;
if (result?.can_use && result?.delegate_doctor_id) {
uni.navigateTo({
url: `/subPackages/doctor/doctor-userinfo?id=${result.delegate_doctor_id}&r_type=2&delegate_store_id=${result.delegate_store_id || storeId}`
});
return;
}
uni.showToast({
title: result?.message || '该门店暂不支持在线问诊功能',
icon: 'none',
});
return;
}
uni.showToast({
title: configRes.data?.message || '获取在线问诊信息失败',
icon: 'none',
});
} catch (error) {
console.error('获取在线问诊信息失败:', error);
uni.showToast({ title: '获取在线问诊信息失败', icon: 'none' });
}
return;
}
uni.navigateTo({
url: `/subPackages/doctor/doctor-userinfo?id=${item.id}&r_type=${rType}`,
});
},
goProduct() { goProduct() {
uni.navigateTo({ uni.navigateTo({
url: '/subPackages/product/product?id=' + id url: '/subPackages/product/product?id=' + id

View File

@@ -47,7 +47,7 @@
<view class="it_info">{{item.small_info}}</view> <view class="it_info">{{item.small_info}}</view>
<view class="it_price"> <view class="it_price">
<text>{{item.drugstore_drug.price}}</text> <text>{{item.drugstore_drug.price}}</text>
<image src="/static/home/gwc.png" mode="" <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/gwc.png" mode=""
@tap.stop="toCartImg(item.drugstore_drug.drug_id)"></image> @tap.stop="toCartImg(item.drugstore_drug.drug_id)"></image>
</view> </view>
</view> </view>

View File

@@ -5,7 +5,8 @@
<view class="search"> <view class="search">
<u-search placeholder="输入文章标题" bg-color="#F5f5f5" color="#DDDDDD" border-color="#F5f5f5" <u-search placeholder="输入文章标题" bg-color="#F5f5f5" color="#DDDDDD" border-color="#F5f5f5"
:show-action="false" maxlength="21" height="66" :clearabled="true" shape="round" v-model="keyword" :show-action="false" maxlength="21" height="66" :clearabled="true" shape="round" v-model="keyword"
@search="toSearch"> @search="toSearch"
@clear="clearSearch">
</u-search> </u-search>
</view> </view>
@@ -13,7 +14,9 @@
<!-- //药品分类左 --> <!-- //药品分类左 -->
<view class="goods"> <view class="goods">
<view class="goods_left" @touchmove.stop> <view class="goods_left" @touchmove.stop>
<scroll-view scroll-y scroll-with-animation class="u-menu-scroll-view" :scroll-top="scrollTop" <!-- 首次加载分类骨架 -->
<ListSkeleton v-if="categoryInitialLoading" type="category" :rows="8" />
<scroll-view v-else scroll-y scroll-with-animation class="u-menu-scroll-view" :scroll-top="scrollTop"
:scroll-into-view="itemId"> :scroll-into-view="itemId">
<view v-for="(item,index) in categoryLists" :key="item.id" class="u-tab-item" <view v-for="(item,index) in categoryLists" :key="item.id" class="u-tab-item"
:class="[num == item.id ? 'u-item-active' : '']" @tap.stop="changeStyle(item.id)" :class="[num == item.id ? 'u-item-active' : '']" @tap.stop="changeStyle(item.id)"
@@ -26,15 +29,18 @@
<view class="goods_right"> <view class="goods_right">
<!-- 药品 --> <!-- 药品 -->
<view class="second"> <view class="second">
<!-- 商品列表 --> <!-- 首次或切分类且当前无文章时展示骨架有旧文章则保留至新结果 -->
<view class="list" v-for="(item,index) in infoList" @click="toDetail(item.id)" :key="item.id"> <ListSkeleton v-if="showArticleSkeleton" type="article" :rows="4" />
<view class="list_"> <template v-else>
<image :src="item.cover||'/static/xx/cp.png'" mode=""></image> <view class="list" v-for="(item,index) in infoList" @click="toDetail(item.id)" :key="item.id">
<view class="list_title"> <view class="list_">
{{item.title}} <image :src="item.cover||'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cp.png'" mode=""></image>
<view class="list_title">
{{item.title}}
</view>
</view> </view>
</view> </view>
</view> </template>
</view> </view>
</view> </view>
</view> </view>
@@ -65,16 +71,36 @@
selectIndex: 0, selectIndex: 0,
arr: [], // 储存距离顶部高度的数组 arr: [], // 储存距离顶部高度的数组
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度 scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
show: false show: false,
// 首次加载骨架
categoryInitialLoading: true,
articleInitialLoading: true,
// 切分类且当前列表为空时拉文章期间展示骨架
articleFetchingEmpty: false
}
},
computed: {
// 是否展示右侧文章骨架:首次加载,或请求中且当前无文章可保留
showArticleSkeleton() {
return this.articleInitialLoading || this.articleFetchingEmpty
} }
}, },
onLoad(op) { onLoad(op) {
}, },
methods: { methods: {
// 搜索 // 搜索:按当前分类 + 标题关键词拉文章(不再误调分类接口)
toSearch() { toSearch() {
this.getList() this.infoList = []
this.articleFetchingEmpty = true
this.getArticleList()
},
// 清空搜索词后重新拉当前分类文章
clearSearch() {
this.keyword = ''
this.infoList = []
this.articleFetchingEmpty = true
this.getArticleList()
}, },
/*切换需要*/ /*切换需要*/
changeStyle(id) { changeStyle(id) {
@@ -86,48 +112,76 @@
url: '/pages/cate/info-detail?id=' + e url: '/pages/cate/info-detail?id=' + e
}) })
}, },
//点击药品一级分类e // 切分类:立即清空文章出骨架
handleCategory1(e, index) { handleCategory1(e, index) {
this.num = e this.num = e
this.cid = e this.cid = e
this.infoList = []
this.articleFetchingEmpty = true
this.getArticleList() this.getArticleList()
}, },
// 获取分类:返回 Promise 供下拉刷新等待
//获取药品分类
getList() { getList() {
categoriesList({ return categoriesList({
method: "post", method: "post",
data: { data: {
store_id: uni.getStorageSync('store_id') || '11001', store_id: uni.getStorageSync('store_id') || '11001',
} }
}).then((res) => { }).then((res) => {
// console.log(res, 'list'); if (res.data.code == 0) {
if (res.data.errcode == 0) { const list = (res.data.result && res.data.result.list) || []
// console.log(res, 'res'); this.categoryLists = list
this.categoryLists = res.data.data.list if (!list.length) {
this.cid = res.data.data.list.map(x => x.id) this.infoList = []
this.num = res.data.data.list[0].id //初始选中 this.articleInitialLoading = false
// this.current = res.data.data[0].subs[0].id //初始选中2 this.articleFetchingEmpty = false
return
}
const firstId = list[0].id
this.num = firstId
this.cid = firstId
return this.getArticleList()
} }
}).finally(() => {
this.categoryInitialLoading = false
}) })
this.getArticleList()
}, },
// 文章列表 // 文章列表:可选 keyword 标题搜索
getArticleList() { getArticleList() {
articleList({ if (!this.infoList || this.infoList.length === 0) {
this.articleFetchingEmpty = true
}
const data = {
store_id: uni.getStorageSync('store_id') || '11001',
cid: this.cid
}
const kw = (this.keyword || '').trim()
if (kw) {
data.keyword = kw
}
return articleList({
method: "get", method: "get",
data: { data
store_id: uni.getStorageSync('store_id') || '11001',
cid: this.cid
}
}).then((res) => { }).then((res) => {
// console.log(res, 'list'); if (res.data.code == 0) {
if (res.data.errcode == 0) { this.infoList = (res.data.result && res.data.result.list) || []
this.infoList = res.data.data.list
// console.log(res, 'list')
} }
}).finally(() => {
this.articleInitialLoading = false
this.articleFetchingEmpty = false
})
},
/**
* 下拉刷新:清空分类/文章并重走开骨架
*/
onPullDownRefresh() {
this.categoryLists = []
this.infoList = []
this.categoryInitialLoading = true
this.articleInitialLoading = true
this.articleFetchingEmpty = false
Promise.resolve(this.getList()).finally(() => {
uni.stopPullDownRefresh()
}) })
} }
}, },
@@ -135,6 +189,7 @@
// this.getList(); // this.getList();
}, },
onShow() { onShow() {
// 不清空 categoryLists / infoList成功后再替换
this.getList(); this.getList();
} }
} }

View File

@@ -44,9 +44,9 @@
} }
}).then((res) => { }).then((res) => {
// console.log(res, 'list'); // console.log(res, 'list');
if (res.data.errcode == 0) { if (res.data.code == 0 && res.data.result) {
this.infoList = res.data.data this.infoList = res.data.result
this.content = res.data.data.content this.content = res.data.result.content
} }
}) })
}, },

File diff suppressed because it is too large Load Diff

View File

@@ -77,12 +77,12 @@
<!-- 底部 --> <!-- 底部 -->
<view class="footer"> <view class="footer">
<view class="box"> <view class="box">
<image src="/static/shop/home.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/shop/home.png" mode=""></image>
<text>首页</text> <text>首页</text>
</view> </view>
<view class="box" @click="toShop"> <view class="box" @click="toShop">
<u-badge type="error" :count="gwcNum" size="mini" :offset="[0,16]"></u-badge> <u-badge type="error" :count="gwcNum" size="mini" :offset="[0,16]"></u-badge>
<image :src="gwcNum==0&&'/static/shop/gwc.png' || gwcNum>0&&'/static/shop/gwc_l.png'" mode=""></image> <image :src="gwcNum==0&&'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/shop/gwc.png' || gwcNum>0&&'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/shop/gwc_l.png'" mode=""></image>
<text :class="gwcNum>0?'active':''">购物车</text> <text :class="gwcNum>0?'active':''">购物车</text>
</view> </view>
<view class="to_btns" @click="toAdd(drugDetailsList.drug_store_relation.drug_id)"> <view class="to_btns" @click="toAdd(drugDetailsList.drug_store_relation.drug_id)">

View File

@@ -1,6 +1,8 @@
<template> <template>
<view class="content safe-area-inset-bottom"> <view class="content safe-area-inset-bottom">
<MessageNotification /> <MessageNotification />
<ClaimPatientModal ref="claimPatientModal" />
<ProxyPayConfirmModal ref="proxyPayConfirmModal" />
<view class="head" @click="gologin"> <view class="head" @click="gologin">
<!-- 门店 --> <!-- 门店 -->
<view class="store"> <view class="store">
@@ -25,14 +27,14 @@
<!-- 图片展示 --> <!-- 图片展示 -->
<view class="section" @click="gologin"> <view class="section" @click="gologin">
<u-swiper :list="list" :img-mode="aspectFill" :height="300" :interval="4000"></u-swiper> <u-swiper :list="list" :img-mode="aspectFill" :height="300" :interval="4000"></u-swiper>
<!-- <image class="banner" mode="aspectFill" src="/static/home/banner.png"></image> --> <!-- <image class="banner" mode="aspectFill" src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png"></image> -->
</view> </view>
<!-- 导航栏 --> <!-- 导航栏 -->
<view class="nav" v-if="isShow_envVersion==false"> <view class="nav" v-if="isShow_envVersion==false">
<view class="nav_service" @click="gologin"> <view class="nav_service" @click="gologin">
<view class="nav_service_img"> <view class="nav_service_img">
<image src="../../static/home/bg1.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg1.png" mode=""></image>
</view> </view>
<view class="nav_service_title"> <view class="nav_service_title">
到店挂号 到店挂号
@@ -41,7 +43,7 @@
<view class="nav_service" @click="gologin"> <view class="nav_service" @click="gologin">
<view class="nav_service_img"> <view class="nav_service_img">
<image src="../../static/home/bg2.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg2.png" mode=""></image>
</view> </view>
<view class="nav_service_title"> <view class="nav_service_title">
预约购药 预约购药
@@ -105,7 +107,7 @@
<!-- 图片部分 --> <!-- 图片部分 -->
<view class="list_infos"> <view class="list_infos">
<view class="list_lefts"> <view class="list_lefts">
<image :src="item.avatar || '/static/mine/avatar_1.png'" mode="医生头像"></image> <image :src="item.avatar || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode="医生头像"></image>
<!-- 医生 --> <!-- 医生 -->
<view class="list_title"> <view class="list_title">
<view class="titles_name"> <view class="titles_name">
@@ -153,9 +155,9 @@
show: true, show: true,
doctorList: [], doctorList: [],
list: [ list: [
// '/static/home/banner.png', // 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png',
// '/static/home/banner.png', // 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png',
// '/static/home/banner.png' // 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png'
], ],
info:[], info:[],
isShow_envVersion: true isShow_envVersion: true
@@ -239,11 +241,37 @@
} }
}) })
}, },
/** 打开扫码认领就诊人弹窗 */
openClaimPatientModal() {
const modal = this.$refs.claimPatientModal
if (modal && modal.openFromStorage) {
modal.openFromStorage()
}
},
/** 打开医生代付确认弹窗 */
openProxyPayConfirmModal() {
const modal = this.$refs.proxyPayConfirmModal
if (modal && modal.openFromStorage) {
modal.openFromStorage()
}
},
}, },
mounted() { mounted() {
this.getShow() this.getShow()
this.getList() this.getList()
} uni.$on('open-claim-patient-modal', this.openClaimPatientModal)
uni.$on('open-proxy-pay-confirm-modal', this.openProxyPayConfirmModal)
this.openClaimPatientModal()
this.openProxyPayConfirmModal()
},
onShow() {
this.openClaimPatientModal()
this.openProxyPayConfirmModal()
},
onUnload() {
uni.$off('open-claim-patient-modal', this.openClaimPatientModal)
uni.$off('open-proxy-pay-confirm-modal', this.openProxyPayConfirmModal)
},
} }
</script> </script>
@@ -467,7 +495,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
background: url('/static/home/bg01.png') no-repeat; background: url('https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg01.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
.nav_service_img { .nav_service_img {
@@ -490,7 +518,7 @@
} }
.nav_service:last-child { .nav_service:last-child {
background: url('/static/home/bg02.png') no-repeat; background: url('https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg02.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
} }
} }

View File

@@ -3,7 +3,7 @@
<MessageNotification /> <MessageNotification />
<!-- 描述 --> <!-- 描述 -->
<view class="card" v-for="item in list" :key="item.id" @click="toInfo(item.id)"> <view class="card" v-for="item in list" :key="item.id" @click="toInfo(item.id)">
<image :src="item.content['avatar']||'/static/mine/avatar_1.png'" mode=""></image> <image :src="item.content['avatar']||'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode=""></image>
<view class="info"> <view class="info">
<view class="infos_title"> <view class="infos_title">
<text class="infos">{{item.content['doctor']}}医生</text> <text class="infos">{{item.content['doctor']}}医生</text>

View File

@@ -12,85 +12,54 @@
</view> </view>
</view> </view>
<!-- 消息列表 --> <!-- 首次加载宫格骨架避免 num 未定义时闪暂无新消息 -->
<view class="list" @click="goSystem"> <view v-if="noticeInitialLoading" class="notice-grid">
<!-- 头像 --> <view v-for="i in 4" :key="i" class="notice-card notice-card--sk">
<view class="avator"> <view class="notice-sk-icon"></view>
<view class="img"> <view class="notice-card__body">
<image src="/static/xx/xt.png" mode=""></image> <view class="notice-sk-line notice-sk-line--title"></view>
</view> <view class="notice-sk-line"></view>
<u-badge size="default" v-if="system.num>=0" :count="system.num" :is-dot="system.num==0?true:false"
type="error" :offset="system.num>0?[-4,-8]:[0,0]"></u-badge>
</view>
<!-- 描述 -->
<view class="info">
<view class="infos_title">
<text class="infos">系统消息</text>
<text class="time">{{ system.time }}</text>
</view>
<view class="preview" v-if="system.num >0">
您有{{ system.num }}条未读信息
</view>
<view class="preview" v-if="system.num==undefined">
暂无新消息
</view> </view>
</view> </view>
</view> </view>
<!-- 四条固定入口一行两列宫格会话列表仍走下方长条 -->
<view class="list" @click="goOrder"> <view v-else class="notice-grid">
<!-- 头像 --> <view
<view class="avator"> v-for="item in noticeEntries"
<view class="img"> :key="item.key"
<image src="/static/xx/cp.png" mode=""></image> class="notice-card"
hover-class="notice-card-hover"
@click="onNoticeTap(item.key)"
>
<view class="notice-card__icon">
<image :src="item.icon" mode="aspectFit"></image>
<u-badge
size="mini"
v-if="item.showBadge"
:count="item.num"
:is-dot="item.isDot"
type="error"
:offset="item.num > 0 ? [-4, -4] : [0, 0]"
></u-badge>
</view> </view>
<u-badge size="default" v-if="order.num>=0" :count="order.num" :is-dot="order.num==0?true:false" <view class="notice-card__body">
type="error" :offset="order.num>0?[-4,-8]:[0,0]"></u-badge> <text class="notice-card__title">{{ item.title }}</text>
</view> <text class="notice-card__preview">{{ item.preview }}</text>
<!-- 描述 --> <text v-if="item.time" class="notice-card__time">{{ item.time }}</text>
<view class="info">
<view class="infos_title">
<text class="infos">产品订单</text>
<text class="time">{{ order.time }}</text>
</view>
<view class="preview" v-if="order.num>0">
您有{{ order.num }}条未读信息
</view>
<view class="preview" v-if="order.num==undefined">
暂无新消息
</view> </view>
</view> </view>
</view> </view>
<view class="list" @click="goDoctor">
<!-- 头像 -->
<view class="avator">
<view class="img">
<image src="/static/xx/ysxx.png" mode=""></image>
</view>
<u-badge size="default" v-if="doctor.num>=0" :count="doctor.num" :is-dot="doctor.num==0?true:false"
type="error" :offset="doctor.num>0?[-4,-8]:[0,0]"></u-badge>
</view>
<!-- 描述 -->
<view class="info">
<view class="infos_title">
<text class="infos">医生消息</text>
<text class="time">{{ doctor.time }}</text>
</view>
<view class="preview" v-if="doctor.num>0">
您有{{ doctor.num }}条新的消息
</view>
<view class="preview" v-if="doctor.num==undefined">
暂无新消息
</view>
</view>
</view>
<!-- 模拟<u-divider>在线问诊</u-divider>效果-->
<!-- 标题 - 在线咨询 --> <!-- 标题 - 在线咨询 -->
<view v-if="checkDev('open-im') && onlineConsultationList.length > 0" class="title"> <view v-if="checkDev('open-im') && onlineConsultationList.length > 0" class="title">
<text>在线咨询</text> <text>在线咨询</text>
</view> </view>
<!-- IM 会话首次骨架有旧数据时直接展示旧列表直至替换 -->
<ListSkeleton
v-if="checkDev('open-im') && chatInitialLoading && onlineConsultationList.length === 0 && onlineReconsultationList.length === 0"
type="message"
:rows="3"
/>
<!-- 在线咨询 - 用户 (type=1) --> <!-- 在线咨询 - 用户 (type=1) -->
<view v-if="checkDev('open-im')" v-for="(item, index) in onlineConsultationList" :key="item.id" class="list" @click="goChat(item, index)"> <view v-if="checkDev('open-im')" v-for="(item, index) in onlineConsultationList" :key="item.id" class="list" @click="goChat(item, index)">
<!-- 头像 --> <!-- 头像 -->
@@ -110,7 +79,6 @@
</view> </view>
<view class="preview"> <view class="preview">
<p>{{ item.last_message }}</p> <p>{{ item.last_message }}</p>
<!-- <p v-if="item.un_read_count>0">您有{{item.un_read_count}}条新的消息</p>-->
</view> </view>
<view class="preview" v-if="item.last_message===''"> <view class="preview" v-if="item.last_message===''">
暂无新消息 暂无新消息
@@ -141,7 +109,6 @@
</view> </view>
<view class="preview"> <view class="preview">
<p>{{ item.last_message }}</p> <p>{{ item.last_message }}</p>
<!-- <p v-if="item.un_read_count>0">您有{{item.un_read_count}}条新的消息</p>-->
</view> </view>
<view class="preview" v-if="item.last_message===''"> <view class="preview" v-if="item.last_message===''">
暂无新消息 暂无新消息
@@ -152,11 +119,15 @@
</template> </template>
<script> <script>
/**
* 患者端消息首页:顶部四入口宫格(系统/订单/医生/转诊)+ 下方 IM 会话列表
*/
import { import {
getseesionRead getseesionRead
} from '../../request/api/api' } from '../../request/api/api'
import {getSystemNoticeIndexApi} from '../../request/api/systemNotice' import {getSystemNoticeIndexApi} from '../../request/api/systemNotice'
import {getChatFriendsListApi} from "@/request/api/im"; import {getChatFriendsListApi} from "@/request/api/im";
import {getTransferPrescriptionListApi} from "@/request/api/transferPrescription";
import {checkDev} from "@/utils/utils"; import {checkDev} from "@/utils/utils";
import MessageNotification from "@/components/MessageNotification/MessageNotification.vue"; import MessageNotification from "@/components/MessageNotification/MessageNotification.vue";
@@ -169,10 +140,30 @@ export default {
order: [], order: [],
system: [], system: [],
doctor: [], doctor: [],
transfer: {
num: 0,
time: ''
},
imChat: [], imChat: [],
onlineConsultationList: [], // 在线咨询列表 (type=1) onlineConsultationList: [], // 在线咨询列表 (type=1)
onlineReconsultationList: [], // 在线复诊列表 (type=3) onlineReconsultationList: [], // 在线复诊列表 (type=3)
type: "" type: "",
// 首次加载骨架:刷新时不再清空数据,故仅首次为 true
noticeInitialLoading: true,
chatInitialLoading: true
}
},
computed: {
/**
* 顶部宫格入口:文案/角标在这里算好,模板禁止方法调用和 ?.
*/
noticeEntries() {
return [
this.buildNoticeEntry('system', '系统消息', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/xt.png', this.system, '条未读信息', '暂无新消息'),
this.buildNoticeEntry('order', '产品订单', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cp.png', this.order, '条未读信息', '暂无新消息'),
this.buildNoticeEntry('doctor', '医生消息', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png', this.doctor, '条新的消息', '暂无新消息'),
this.buildNoticeEntry('transfer', '转诊消息', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png', this.transfer, '条转诊消息待处理', '暂无转诊消息')
]
} }
}, },
onLoad() { onLoad() {
@@ -186,7 +177,35 @@ export default {
}, },
methods: { methods: {
checkDev, checkDev,
/**
* 组装单条宫格入口(未读数未回来时不展示角标,避免闪 0
*/
buildNoticeEntry(key, title, icon, raw, unreadSuffix, emptyText) {
const num = raw && raw.num
const hasNum = num !== undefined && num !== null
const unread = Number(num) || 0
let preview = emptyText
if (hasNum && unread > 0) {
preview = '您有' + unread + unreadSuffix
}
return {
key,
title,
icon,
num: unread,
time: (raw && raw.time) || '',
preview,
showBadge: hasNum,
isDot: unread === 0
}
},
/** 宫格点击:按 key 走原来的四个跳转 */
onNoticeTap(key) {
if (key === 'system') this.goSystem()
else if (key === 'order') this.goOrder()
else if (key === 'doctor') this.goDoctor()
else if (key === 'transfer') this.goTransfer()
},
/** /**
* 处理聊天列表更新事件 * 处理聊天列表更新事件
* 收到新消息时自动刷新列表 * 收到新消息时自动刷新列表
@@ -204,7 +223,6 @@ export default {
type: "all" type: "all"
} }
}).then((res) => { }).then((res) => {
// console.log(res, 'res');
uni.showToast({ uni.showToast({
title: '清除成功', title: '清除成功',
duration: 2000, duration: 2000,
@@ -226,7 +244,6 @@ export default {
type: "system" type: "system"
} }
}).then((res) => { }).then((res) => {
// console.log(res, 'res');
}) })
}, },
// 详情 // 详情
@@ -241,7 +258,6 @@ export default {
type: "order" type: "order"
} }
}).then((res) => { }).then((res) => {
// console.log(res, 'res');
}) })
}, },
goDoctor() { goDoctor() {
@@ -255,24 +271,20 @@ export default {
type: "doctor_news" type: "doctor_news"
} }
}).then((res) => { }).then((res) => {
// console.log(res, 'res'); })
},
// 转诊消息
goTransfer() {
uni.navigateTo({
url: '/subPackages/transfer/transfer-message'
}) })
}, },
goChat(item, index) { goChat(item, index) {
uni.navigateTo({ uni.navigateTo({
url: `/subPackages/chat/chat?room_id=${item.room_id}&nick_name=${item.nick_name}&user_id=${item.id}&avatar=${encodeURIComponent(item.avatar || '')}&room_status=${item.room_status || 0}` url: `/subPackages/chat/chat?room_id=${item.room_id}&nick_name=${item.nick_name}&user_id=${item.id}&avatar=${encodeURIComponent(item.avatar || '')}&room_status=${item.room_status || 0}`
}) })
// getseesionRead({
// method: "post",
// data: {
// store_id: uni.getStorageSync('store_id') || 11001,
// type: "doctor_news"
// }
// }).then((res) => {
// // console.log(res, 'res');
// })
}, },
// // 获取系统/订单/医生消息概览;成功才覆盖,失败保留旧数据
async getInfo() { async getInfo() {
try { try {
const res = await getSystemNoticeIndexApi({ const res = await getSystemNoticeIndexApi({
@@ -285,16 +297,73 @@ export default {
} }
} catch (error) { } catch (error) {
console.error('获取消息概览失败:', error) console.error('获取消息概览失败:', error)
} finally {
this.noticeInitialLoading = false
} }
}, },
// 拉取 IM 好友会话列表,成功后整份替换,失败不写空
getMyChatFriendsList() { getMyChatFriendsList() {
getChatFriendsListApi().then((res) => { return getChatFriendsListApi().then((res) => {
const allChatList = res.data.result || [] const allChatList = res.data.result || []
// 根据type字段区分在线咨询和在线复诊 // 根据type字段区分在线咨询和在线复诊
this.onlineConsultationList = allChatList.filter(item => item.type === 1 || !item.type) // type=1或未设置type的为在线咨询 this.onlineConsultationList = allChatList.filter(item => item.type === 1 || !item.type) // type=1或未设置type的为在线咨询
this.onlineReconsultationList = allChatList.filter(item => item.type === 3) // type=3的为在线复诊 this.onlineReconsultationList = allChatList.filter(item => (item.type === 3 || item.type === 2)) // type=3的为在线复诊
// 保持imChat兼容性包含所有 // 保持imChat兼容性包含所有
this.imChat = allChatList this.imChat = allChatList
}).catch((error) => {
console.error('获取聊天列表失败:', error)
}).finally(() => {
this.chatInitialLoading = false
})
},
// 获取转诊消息列表;失败不重置 transfer避免闪空
getTransferList() {
return getTransferPrescriptionListApi().then((res) => {
if (res.data && res.data.code == 0) {
const transferList = res.data.result || []
// 统计待确认的转诊消息数量
const pendingCount = transferList.filter(item => item.status === 0).length
this.transfer.num = pendingCount
// 获取最新的转诊时间
if (transferList.length > 0) {
const latestTransfer = transferList[0]
if (latestTransfer.transfer_time) {
this.transfer.time = latestTransfer.transfer_time
} else if (latestTransfer.created_at) {
this.transfer.time = latestTransfer.created_at
}
}
}
}).catch((error) => {
console.error('获取转诊消息列表失败:', error)
})
},
/**
* 下拉刷新:重置骨架并并行重拉消息概览 / IM / 转诊
*/
refreshMessagePage() {
this.noticeInitialLoading = true
if (checkDev('open-im')) {
this.chatInitialLoading = true
this.onlineConsultationList = []
this.onlineReconsultationList = []
this.imChat = []
}
const tasks = [
this.getInfo(),
this.getTransferList()
]
if (checkDev('open-im')) {
tasks.push(this.getMyChatFriendsList())
} else {
this.chatInitialLoading = false
}
return Promise.all(tasks)
},
onPullDownRefresh() {
this.refreshMessagePage().finally(() => {
uni.stopPullDownRefresh()
}) })
} }
}, },
@@ -302,13 +371,10 @@ export default {
this.getInfo() this.getInfo()
}, },
onShow() { onShow() {
this.order = [] // 不再清空列表:保留旧 UI接口成功后再覆盖
this.system = []
this.doctor = []
this.onlineConsultationList = []
this.onlineReconsultationList = []
this.getInfo() this.getInfo()
this.getMyChatFriendsList() this.getMyChatFriendsList()
this.getTransferList()
} }
} }
</script> </script>
@@ -351,6 +417,108 @@ export default {
} }
} }
/* 顶部四入口:一行两列,卡片用同色细边框+微光(不用左侧色条) */
.notice-grid {
display: flex;
flex-wrap: wrap;
padding: 0 24rpx 8rpx;
}
.notice-card {
width: 343rpx;
margin: 0 16rpx 16rpx 0;
padding: 24rpx 20rpx;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: flex-start;
background: #fff;
border-radius: 20rpx;
border: 1px solid rgba(83, 109, 254, 0.28);
box-shadow: 0 0 6px rgba(83, 109, 254, 0.16);
}
.notice-card:nth-child(2n) {
margin-right: 0;
}
.notice-card-hover {
opacity: 0.88;
}
.notice-card__icon {
position: relative;
flex-shrink: 0;
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #E6EAFF;
overflow: visible;
image {
width: 72rpx;
height: 72rpx;
}
}
.notice-card__body {
flex: 1;
min-width: 0;
margin-left: 16rpx;
display: flex;
flex-direction: column;
}
.notice-card__title {
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
line-height: 40rpx;
}
.notice-card__preview {
margin-top: 4rpx;
font-size: 22rpx;
color: #64748B;
line-height: 32rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notice-card__time {
margin-top: 4rpx;
font-size: 20rpx;
color: #94A3B8;
line-height: 28rpx;
}
.notice-card--sk {
min-height: 140rpx;
}
.notice-sk-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #EEF2FF;
flex-shrink: 0;
}
.notice-sk-line {
height: 20rpx;
margin-top: 12rpx;
border-radius: 8rpx;
background: #F1F5F9;
width: 100%;
}
.notice-sk-line--title {
height: 28rpx;
width: 140rpx;
flex: none;
}
.list { .list {
width: 750rpx; width: 750rpx;
display: flex; display: flex;

View File

@@ -3,7 +3,7 @@
<MessageNotification /> <MessageNotification />
<!-- 描述 --> <!-- 描述 -->
<view class="card" v-for="item in list" :key="item.id"> <view class="card" v-for="item in list" :key="item.id">
<image src="/static/xx/cpdd.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cpdd.png" mode=""></image>
<view class="info"> <view class="info">
<view class="infos_title"> <view class="infos_title">
<text class="infos">订单</text> <text class="infos">订单</text>

View File

@@ -8,7 +8,7 @@
<view class="qualification-item" v-for="(item, index) in qualifications" :key="index" @click="previewImage(index)"> <view class="qualification-item" v-for="(item, index) in qualifications" :key="index" @click="previewImage(index)">
<view class="qualification-content"> <view class="qualification-content">
<text class="qualification-name">{{ item.name }}</text> <text class="qualification-name">{{ item.name }}</text>
<image :src="item.image || '/static/mine/avatar_1.png'" mode="aspectFit" class="qualification-img"></image> <image :src="item.image || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode="aspectFit" class="qualification-img"></image>
</view> </view>
</view> </view>
</view> </view>

View File

@@ -7,7 +7,7 @@
<view class="title"> <view class="title">
欢迎登录 欢迎登录
</view> </view>
<image src="@/static/empty/login.png" mode="aspectFit"></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/empty/login.png" mode="aspectFit"></image>
<!-- 修改点1移除 disabled 属性和 bg 样式判断使其始终可点击且显示激活颜色 --> <!-- 修改点1移除 disabled 属性和 bg 样式判断使其始终可点击且显示激活颜色 -->
<button class="btn" @click="handleLoginClick">一键登录</button> <button class="btn" @click="handleLoginClick">一键登录</button>

View File

@@ -10,7 +10,7 @@
<view class="info"> <view class="info">
<view class="pwd"> <view class="pwd">
<d-input :maxlength="11" height="96" v-model="form['mobile']" <d-input :maxlength="11" height="96" v-model="form['mobile']"
:prefixIcon="require('../../static/choose/act.png')" borderRadius="96rpx" prefixIconSize="40rpx" prefixIcon="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/choose/act.png" borderRadius="96rpx" prefixIconSize="40rpx"
placeholder="请输入手机号" @blur="check" @input="is_d=false" :placeholder-style="{fontSize:'32rpx'}" placeholder="请输入手机号" @blur="check" @input="is_d=false" :placeholder-style="{fontSize:'32rpx'}"
:custom-style="{fontSize:'32rpx',paddingLeft:'60rpx'}" /> :custom-style="{fontSize:'32rpx',paddingLeft:'60rpx'}" />
</view> </view>
@@ -57,6 +57,7 @@
getCodes, getCodes,
getsLogin getsLogin
} from '../../request/api/api'; } from '../../request/api/api';
import { isPhone } from '@/utils/phone.js';
export default { export default {
data() { data() {
return { return {
@@ -93,7 +94,8 @@
}) })
return return
} }
if (!/^1[345678]\d{9}$/.test(this.form['mobile'])) { // 与后端 checkPhone 统一:含 19 号段
if (!isPhone(this.form['mobile'])) {
// uni.showToast({ // uni.showToast({
// title: '请输入正确的手机号', // title: '请输入正确的手机号',
// duration: 2000, // duration: 2000,
@@ -137,6 +139,14 @@
}) })
}, },
register() { register() {
if (!isPhone(this.form['mobile'])) {
this.$refs.uToast.show({
title: '请输入正确的手机号',
type: 'default',
icon: false
})
return
}
if (this.form['pwd'] == []) { if (this.form['pwd'] == []) {
uni.showToast({ uni.showToast({
title: '请输入验证码', title: '请输入验证码',

View File

@@ -4,7 +4,7 @@
<!-- 头部 --> <!-- 头部 -->
<view class="head"> <view class="head">
<view class="head_img"> <view class="head_img">
<image :src="infoList.user.avatarurl || '/static/mine/mr_tx.png' " mode=""></image> <image :src="infoList.user.avatarurl || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/mr_tx.png' " mode=""></image>
</view> </view>
<view class="head_info"> <view class="head_info">
<view class="head_name"> <view class="head_name">
@@ -31,25 +31,25 @@
</view> </view>
<view class="info"> <view class="info">
<view class="item" @click="toAll(1)" style="position: relative;"> <view class="item" @click="toAll(1)" style="position: relative;">
<image src="/static/mine/dfk.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/dfk.png" mode=""></image>
<text>待付款</text> <text>待付款</text>
<u-badge type="error" :count="infoList.ProductOrder.unpay" :offset="[-2,20]"></u-badge> <u-badge type="error" :count="infoList.ProductOrder.unpay" :offset="[-2,20]"></u-badge>
</view> </view>
<view class="item" @click="toAll(2)" style="position: relative;"> <view class="item" @click="toAll(2)" style="position: relative;">
<image src="/static/mine/dfh.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/dfh.png" mode=""></image>
<text>待发货</text> <text>待发货</text>
<u-badge type="error" :count="infoList.ProductOrder.wait_send" :offset="[-2,20]"></u-badge> <u-badge type="error" :count="infoList.ProductOrder.wait_send" :offset="[-2,20]"></u-badge>
</view> </view>
<view class="item" @click="toAll(3)" style="position: relative;"> <view class="item" @click="toAll(3)" style="position: relative;">
<image src="/static/mine/dsh.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/dsh.png" mode=""></image>
<text>待收货</text> <text>待收货</text>
<u-badge type="error" :count="infoList.ProductOrder.wait_accept" :offset="[-2,20]"></u-badge> <u-badge type="error" :count="infoList.ProductOrder.wait_accept" :offset="[-2,20]"></u-badge>
</view> </view>
<view class="item" @click="toAll(4)"> <view class="item" @click="toAll(4)">
<image src="/static/mine/tk.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/tk.png" mode=""></image>
<text>已完成</text> <text>已完成</text>
</view> </view>
</view> </view>
@@ -63,17 +63,17 @@
<!-- 列表 --> <!-- 列表 -->
<view class="list"> <view class="list">
<!-- <navigator url="../../subPackages/my/myorder" class="infos"> <navigator url="../../subPackages/my/mymedical" class="infos">
<image src="@/static/mine/01.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/01.png" mode=""></image>
<text>问诊记录</text> <text>我的病历</text>
</navigator> --> </navigator>
<navigator url="../../subPackages/my/myrecord" class="infos"> <navigator url="../../subPackages/my/myrecord" class="infos">
<image src="@/static/mine/02.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/02.png" mode=""></image>
<text>处方记录</text> <text>我的处方</text>
</navigator> </navigator>
<navigator url="../../subPackages/my/myappiont" class="infos"> <navigator url="../../subPackages/my/myappiont" class="infos">
<image src="@/static/mine/03.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/03.png" mode=""></image>
<text>挂号记录</text> <text>我的挂号</text>
</navigator> </navigator>
</view> </view>
</view> </view>
@@ -87,15 +87,15 @@
<!-- 列表 --> <!-- 列表 -->
<view class="list"> <view class="list">
<navigator class="infos" url="../../subPackages/my/myinfo" v-if="isShow_envVersion==false"> <navigator class="infos" url="../../subPackages/my/myinfo" v-if="isShow_envVersion==false">
<image src="@/static/mine/jzr.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/jzr.png" mode=""></image>
<text>就诊人管理</text> <text>就诊人管理</text>
</navigator> </navigator>
<navigator url="../../subPackages/setup/area-list" class="infos"> <navigator url="../../subPackages/setup/area-list" class="infos">
<image src="@/static/mine/gy.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/gy.png" mode=""></image>
<text>收货地址</text> <text>收货地址</text>
</navigator> </navigator>
<navigator url="../../subPackages/setup/customer-service" class="infos"> <navigator url="../../subPackages/setup/customer-service" class="infos">
<image src="@/static/mine/kfzx.png" mode=""></image> <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/kfzx.png" mode=""></image>
<text>客服中心</text> <text>客服中心</text>
</navigator> </navigator>
</view> </view>
@@ -106,8 +106,9 @@
<script> <script>
import { import {
getuserInfo getUserInfoApi
} from '../../request/api/api' } from '../../request/api/user'
import { unwrapXkApi } from '@/utils/api-response.js'
export default { export default {
data() { data() {
return { return {
@@ -137,15 +138,13 @@
// 用户信息 // 用户信息
getInfo() { getInfo() {
getuserInfo({ getUserInfoApi({
method: "post", store_id: uni.getStorageSync('store_id') || 11001
data: {
store_id: uni.getStorageSync('store_id') || 11001
}
}).then((res) => { }).then((res) => {
// console.log(res, 'ingo'); // getUserInfoApi → xk-api
if (res.data.errcode == 0) { const { ok, payload } = unwrapXkApi(res)
this.infoList = res.data.data if (ok) {
this.infoList = payload
} }
}) })
}, },

View File

@@ -1,4 +1,37 @@
import http from './request' import http from './request'
import {
getPatientListApi,
savePatientApi,
deletePatientApi,
getPatientGuardianInfoApi,
getPatientRelationApi,
changePatientDefaultApi,
getPatientHealthInfoApi,
} from './patient.js'
/** 将 xk-api 的 code/result 格式转为旧版 errcode/data兼容现有页面 */
function normalizeXkApiResponse(res) {
if (res && res.data && (res.data.code === 0 || res.data.code === '0')) {
res.data.errcode = 0;
res.data.data = res.data.result;
}
return res;
}
/**
* 就诊人列表专用xk-api result 为 { list, latest_register_user_patient_id }
* 旧页面仍读 res.data.data 数组,这里把 list 摊平到 data
*/
function normalizePatientListResponse(res) {
if (res && res.data && (res.data.code === 0 || res.data.code === '0')) {
const result = res.data.result
const list = Array.isArray(result) ? result : ((result && result.list) || [])
res.data.errcode = 0
res.data.data = list
res.data.result = result
}
return res
}
// 登录 授权 其他手机号授权 // 登录 授权 其他手机号授权
export async function getsLogin(params) { export async function getsLogin(params) {
@@ -33,20 +66,20 @@ export async function getAgreem(params) {
// 首页 门店 门店切换 // 首页 门店 门店切换
export async function storeChange(params) { export async function storeChange(params) {
let data = await http('/oldApi/v1/store/store-change', params) let data = await http('/xkApi/store/store-change', params)
return data return normalizeXkApiResponse(data)
} }
// 首页 门店 用户所有门店 // 首页 门店 用户所有门店
export async function storeList(params) { export async function storeList(params) {
let data = await http('/oldApi/v1/store/list', params) let data = await http('/xkApi/store/list', params)
return data return normalizeXkApiResponse(data)
} }
// 首页 门店 门店信息 // 首页 门店 门店信息
export async function storeInfo(params) { export async function storeInfo(params) {
let data = await http('/oldApi/v1/store/info', params) let data = await http('/xkApi/store/info', params)
return data return normalizeXkApiResponse(data)
} }
// 医生列表 // 医生列表
@@ -79,90 +112,62 @@ export async function commentListTop(params) {
return data return data
} }
// 医生 挂号信息 // 挂号域接口registerList/registerCalendar/registe/registeInfo/registePay/registeUnpay/registeCancle
export async function registerList(params) { // 已按 Api-Module-Split 规则迁至 ./register.js调用方请从 '@/request/api/register.js' 导入
let data = await http('/oldApi/v1/register/doc-register-info', params)
return data
}
// 就诊人列表 /** 就诊人列表xk-api POST /patient/list */
export async function userList(params) { export async function userList(params) {
let data = await http('/oldApi/v1/patient/list', params) const data = (params && params.data) || params || {}
return data return normalizePatientListResponse(await getPatientListApi(data))
} }
// 就诊人关系 /** 就诊人关系xk-api GET /patient/relation */
export async function userRelation(params) { export async function userRelation(params) {
let data = await http('/oldApi/v1/patient/relation', params) const data = (params && params.data) || params || {}
return data return normalizeXkApiResponse(await getPatientRelationApi(data))
} }
// 编辑就诊人信息 /** 编辑就诊人头像等(仍走旧接口,业务极少用) */
export async function userInfo(params) { export async function userInfo(params) {
let data = await http('/oldApi/v1/patient/edit-patient', params) let data = await http('/oldApi/v1/patient/edit-patient', params)
return data return data
} }
// 就诊人保存 /** 就诊人保存xk-api POST /patient/save */
export async function userAdd(params) { export async function userAdd(params) {
let data = await http('/oldApi/v1/patient/save', params) const data = (params && params.data) || params || {}
return data return normalizeXkApiResponse(await savePatientApi(data))
} }
// 就诊人详情 /** 就诊人监护人信息xk-api GET /patient/guardian-info */
export async function userGuardianInfo(params) {
const query = (params && params.data) || params || {}
return normalizeXkApiResponse(await getPatientGuardianInfoApi(query))
}
/** 删除就诊人xk-api POST /patient/delete */
export async function userPatientDel(params) {
const data = (params && params.data) || params || {}
return normalizeXkApiResponse(await deletePatientApi(data))
}
/** 就诊人详情(列表已含字段;无独立详情时仍兼容旧 Yii */
export async function userDetail(params) { export async function userDetail(params) {
let data = await http('/oldApi/v1/patient/info', params) let data = await http('/oldApi/v1/patient/info', params)
return data return data
} }
// 就诊人 切换默认诊人 /** 切换默认诊人xk-api POST /patient/change-default */
export async function userPatientChoose(params) { export async function userPatientChoose(params) {
let data = await http('/oldApi/v1/patient/change-patient', params) const data = (params && params.data) || params || {}
return data return normalizeXkApiResponse(await changePatientDefaultApi(data))
} }
/** 健康问诊详情xk-api POST /patient/health-info */
// 就诊人 - 健康信息
export async function userEidtPatient(params) { export async function userEidtPatient(params) {
let data = await http('/oldApi/v1/patient/health-info', params) const data = (params && params.data) || params || {}
return data return normalizeXkApiResponse(await getPatientHealthInfoApi(data))
}
// 挂号
export async function registe(params) {
let data = await http('/oldApi/v1/register/register', params)
return data
}
// 挂号详情
export async function registeInfo(params) {
let data = await http('/oldApi/v1/register/register-detail', params)
return data
}
// 支付挂号
export async function registePay(params) {
let data = await http('/oldApi/v1/register/pay', params)
return data
}
// 取消挂号
export async function registeUnpay(params) {
let data = await http('/oldApi/v1/register/cancel', params)
return data
}
// 挂号退款
export async function registeCancle(params) {
let data = await http('/oldApi/v1/register/refund', params)
return data
}
// 挂号列表
export async function registeList(params) {
let data = await http('/oldApi/v1/register/list', params)
return data
} }
// 设置 服务协议 // 设置 服务协议
@@ -244,16 +249,16 @@ export async function getPrescriptInfo(params) {
return data return data
} }
// 我的 处方支付 // 我的 处方支付xk-api
export async function getPrescriptPay(params) { export async function getPrescriptPay(params) {
let data = await http('/oldApi/v1/product-order/pay', params) let data = await http('/xkApi/product-order/pay', params)
return data return normalizeXkApiResponse(data)
} }
// 我的 处方-产品订单信息 // 我的 处方-产品订单信息xk-api appoint-medicine
export async function getProductInfo(params) { export async function getProductInfo(params) {
let data = await http('/oldApi/v1/product-order/appoint-medicine', params) let data = await http('/xkApi/product-order/appoint-medicine', params)
return data return normalizeXkApiResponse(data)
} }
// 消息 // 消息
@@ -317,10 +322,21 @@ export async function getUseWayPay(params) {
return data return data
} }
// 更新配送方式 http://xiaokang.com/member/v1/product-order/update-delivery /**
* 更新配送方式xk-api单品包邮重算
* 代付时带 proxy_pay=1或与 getPayConfig 一样从 storage 自动附带
*/
export async function getDelivery(params) { export async function getDelivery(params) {
let data = await http('/oldApi/v1/product-order/update-delivery', params) const payload = params && typeof params === 'object' ? { ...params } : { method: 'post', data: {} }
return data const data = payload.data && typeof payload.data === 'object' ? { ...payload.data } : {}
const orderId = Number(data.order_id || 0)
const proxyOrderId = Number(uni.getStorageSync('proxy_pay_order_id') || 0)
if (!data.proxy_pay && proxyOrderId > 0 && orderId === proxyOrderId) {
data.proxy_pay = 1
}
payload.data = data
let res = await http('/xkApi/product-order/update-delivery', payload)
return normalizeXkApiResponse(res)
} }
// 更新代煎服务费用 http://xiaokang.com/member/v1/product-order/update-decoct-service // 更新代煎服务费用 http://xiaokang.com/member/v1/product-order/update-decoct-service
@@ -329,28 +345,51 @@ export async function getUpdateUseWay(params) {
return data return data
} }
//资讯列表 /v1/info/article-list //资讯列表Laravel guest-info列表不含正文
export async function articleList(params) { export async function articleList(params) {
let data = await http('/oldApi/v1/info/article-list', params) let data = await http('/xkApi/guest/guest-info/article-list', params)
return data return data
} }
//分类列表 /v1/info/categories //分类列表
export async function categoriesList(params) { export async function categoriesList(params) {
let data = await http('/oldApi/v1/info/categories', params) let data = await http('/xkApi/guest/guest-info/categories', params)
return data return data
} }
//资讯详情 /v1/info/article-info //资讯详情(含正文)
export async function articleInfo(params) { export async function articleInfo(params) {
let data = await http('/oldApi/v1/info/article-info', params) let data = await http('/xkApi/guest/guest-info/article-info', params)
return data return data
} }
// v1/product-order/list // v1/product-order/list
export async function getOrdlist(params) { export async function getOrdlist(params) {
let data = await http('/oldApi/v1/product-order/list', params) let data = await http('/xkApi/product-order/list', params)
return data return normalizeXkApiResponse(data)
}
/**
* 获取待支付商品订单列表(首页提醒用)
*/
export async function fetchUnpaidProductOrderListApi(params = {}) {
const storeId = params.store_id || uni.getStorageSync('store_id') || '11001';
const res = await getOrdlist({
method: 'post',
data: {
store_id: storeId,
type: 'unpaid',
page: 1,
...params,
},
});
if (res.data?.errcode != 0) {
return [];
}
const list = res.data?.data?.list || [];
return list.filter(
(item) => item.status === 0 && item.is_pay === 0 && item.cancel_status === 0
);
} }
// v1/product-order/list // v1/product-order/list
@@ -418,9 +457,21 @@ export async function PayList(params) {
// return data // return data
// } // }
/**
* 商品订单支付xk-api
* 代付时在 data 中带 proxy_pay=1也可从 storage proxy_pay_order_id 自动附带
*/
export async function getPayConfig(params) { export async function getPayConfig(params) {
let data = await http('/oldApi/v1/product-order/pay', params) const payload = params && typeof params === 'object' ? { ...params } : { method: 'post', data: {} }
return data const data = payload.data && typeof payload.data === 'object' ? { ...payload.data } : {}
const orderId = Number(data.order_id || 0)
const proxyOrderId = Number(uni.getStorageSync('proxy_pay_order_id') || 0)
if (!data.proxy_pay && proxyOrderId > 0 && orderId === proxyOrderId) {
data.proxy_pay = 1
}
payload.data = data
let res = await http('/xkApi/product-order/pay', payload)
return normalizeXkApiResponse(res)
} }
// getOrdlist 获取订单列表 https://zjxk.app.ctkj88.com/member/order/lists // getOrdlist 获取订单列表 https://zjxk.app.ctkj88.com/member/order/lists
// export async function getOrdlist(params) { // export async function getOrdlist(params) {
@@ -439,10 +490,10 @@ export async function getCancle(params) {
// return data // return data
// } // }
// 更换 订单详情 https://app.xiaokang88.com/member/v1/product-order/info // 订单详情xk-api product-order/infodrug-info 页)
export async function getOrdDetail(params) { export async function getOrdDetail(params) {
let data = await http('/oldApi/v1/product-order/info', params) let data = await http('/xkApi/product-order/info', params)
return data return normalizeXkApiResponse(data)
} }
@@ -469,25 +520,18 @@ export async function postRefundBased(params) {
// return data // return data
// } // }
/** 物流详情 → xk-api mobile含多包裹 packages */
export async function getLogistics(params) { export async function getLogistics(params) {
let data = await http('/oldApi/v1/product-order/express', params) let data = await http('/xkApi/product-order/express', params)
return data return normalizeXkApiResponse(data)
} }
//确认收货 https://zjxk.app.ctkj88.com/member/order/confirm // 确认收货(签收)→ xk-api product-order/receive改状态 + 解冻供应商/仓余额)
// https://zjxk.app.ctkj88.com/member/order/confirm
export async function confirmAccept(params) { export async function confirmAccept(params) {
let data = await http('/newApi/order/confirm', params) let data = await http('/xkApi/product-order/receive', params)
return data return normalizeXkApiResponse(data)
} }
//清空购物车
export async function clearAll(params) {
let data = await http('/newApi/cart/clearAll', params)
return data
}
export async function getVersion(params) { export async function getVersion(params) {
let data = await http('/v1/other/xcx-version?store_id=11001', params) let data = await http('/v1/other/xcx-version?store_id=11001', params)
return data return data
@@ -495,7 +539,8 @@ export async function getVersion(params) {
// 获取系统配置信息 // 获取系统配置信息
export async function getSystemConfig(params) { export async function getSystemConfig(params) {
let data = await http('/xkApi/platform/config', params) // let data = await http('/xkApi/platform/config', params)
let data = await http('/xkApi/platform/config', params, 3)
return data return data
} }

View File

@@ -26,3 +26,11 @@ export async function getCartCountsApi(params) {
export async function saveCartApi(params) { export async function saveCartApi(params) {
return await post(`${prefix}/save-cart`, params, 3) return await post(`${prefix}/save-cart`, params, 3)
} }
/**
* 清空购物车(当前用户+门店维度,迁自老 Yii /v1/cart/clear-all xk-api POST /cart/clear-cart
* @param params { store_id: number }
* @returns {Promise<*>}
*/
export async function clearCartApi(params) {
return await post(`${prefix}/clear-cart`, params, 3)
}

33
request/api/invoice.js Normal file
View File

@@ -0,0 +1,33 @@
import { get, post } from './http'
/**
* 将 xk-api 的 code/result 规范为 errcode/data便于页面统一判断
*/
function normalizeXkApiResponse(res) {
if (res && res.data && (res.data.code === 0 || res.data.code === '0')) {
res.data.errcode = 0
res.data.data = res.data.result
}
return res
}
/**
* 申请开票
* @param {object} params order_id, title_type, title_name, tax_no, receive_type, email
*/
export async function applyInvoiceApi(params) {
const data = await post('/invoice/apply', params, 3)
return normalizeXkApiResponse(data)
}
/**
* 开票详情
* @param {object} params id 或 order_id
*/
export async function getInvoiceDetailApi(params) {
const data = await get('/invoice/detail', params, 3)
return normalizeXkApiResponse(data)
}
/** 与 config/xk.php invoice_notice 保持一致的订阅消息模板 ID */
export const INVOICE_NOTICE_TMPL_ID = '2FuQhhO74mABSqGx2cF7vRR2wGekrIMM9kZMvVI6h-Q'

View File

@@ -0,0 +1,19 @@
import { post, get } from './http'
/**
* 我的病历列表xk-api POST /medical-record/list
* 取值unwrapXkApi → code/result/message
* @param {{ user_patient_id: number, page?: number, pageSize?: number }} params
*/
export async function getMedicalRecordListApi(params) {
return await post('/medical-record/list', params, 3)
}
/**
* 我的病历详情xk-api GET /medical-record/detail
* 取值unwrapXkApi → code/result/message
* @param {{ id: number }} params
*/
export async function getMedicalRecordDetailApi(params) {
return await get('/medical-record/detail', params, 3)
}

View File

@@ -1,6 +1,20 @@
import {get, post, uploadFile} from './http' import {get, post, uploadFile} from './http'
const prefix = '/order'; const prefix = '/order';
/**
* 在线复诊:创建就诊信息(挂号前)
*/
export async function createRegisterInfoApi(params) {
return await post(`${prefix}/create-register-info`, params, 3)
}
/**
* 在线复诊:可选西药列表(双端上架)
*/
export async function getWesternDrugsForFollowUpApi(params) {
return await get(`${prefix}/western-drugs-for-follow-up`, params, 3)
}
/** /**
* 获取扫码诊所的商品详情 * 获取扫码诊所的商品详情
* @param params * @param params
@@ -26,6 +40,11 @@ export async function saveRegisterInfoApi(params) {
return await post(`${prefix}/save-register-info`, params, 3) return await post(`${prefix}/save-register-info`, params, 3)
} }
/** 诊所在线复诊:确认是否曾用过所选西药 */
export async function confirmFollowUpDrugUseApi(params) {
return await post(`${prefix}/confirm-follow-up-drug-use`, params, 3)
}
/** /**
* 查询订单的处方状态 * 查询订单的处方状态
* @param params * @param params

46
request/api/patient.js Normal file
View File

@@ -0,0 +1,46 @@
import { post, get } from './http'
/**
* 就诊人相关接口统一走 xk-apitype=3
* 取值unwrapXkApi → code / result / message
*/
/** 就诊人列表 POST /patient/list → { list, latest_register_user_patient_id } */
export async function getPatientListApi(params = {}) {
return await post('/patient/list', params, 3)
}
/** 就诊人保存(新增/编辑POST /patient/save */
export async function savePatientApi(params = {}) {
return await post('/patient/save', params, 3)
}
/** 删除就诊人 POST /patient/delete */
export async function deletePatientApi(params = {}) {
return await post('/patient/delete', params, 3)
}
/** 监护人信息 GET /patient/guardian-info */
export async function getPatientGuardianInfoApi(params = {}) {
return await get('/patient/guardian-info', params, 3)
}
/** 关系枚举 GET /patient/relation */
export async function getPatientRelationApi(params = {}) {
return await get('/patient/relation', params, 3)
}
/** 设为默认就诊人 POST /patient/change-default传 up_id */
export async function changePatientDefaultApi(params = {}) {
return await post('/patient/change-default', params, 3)
}
/** 健康问诊详情 POST /patient/health-info传 user_patient_id */
export async function getPatientHealthInfoApi(params = {}) {
return await post('/patient/health-info', params, 3)
}
/** 扫码认领医生创建的就诊人 POST /patient/claim传 user_patient_id */
export async function claimPatientApi(params = {}) {
return await post('/patient/claim', params, 3)
}

View File

@@ -0,0 +1,10 @@
import {post} from './http'
/**
* 获取处方记录列表
* @param params { up_id: number, store_id: number, page: number }
* @returns {Promise<*>}
*/
export async function getPrescriptionListApi(params) {
return await post('/prescription/list', params, 3)
}

View File

@@ -40,6 +40,20 @@ export async function getHomeTopProductsApi(params) {
export async function getHomeZonesApi(params) { export async function getHomeZonesApi(params) {
return await get('/product/home-zones', params, 3) return await get('/product/home-zones', params, 3)
} }
/**
* 首页展示样式(待办入口排布 + 订单待办样式)
* xk-api GET /product/home-display-config游客态 request.js 会改写到 guest
*/
export async function getHomeDisplayConfigApi() {
return await get('/product/home-display-config', {}, 3)
}
/**
* 首页订单提醒(待支付挂号/商品、已发货、待确认收货)
* xk-api GET /product-order/home-remind取值 unwrapXkApi → code / result / message
*/
export async function getHomeOrderRemindApi() {
return await get('/product-order/home-remind', {}, 3)
}
/** /**
* 更新商品介绍图 * 更新商品介绍图
* @param params * @param params
@@ -51,11 +65,30 @@ export async function updateIntroductionImagesApi(params) {
/** /**
* 检查药店在线复诊配置(委托诊所和医生) * 检查药店在线复诊配置(委托诊所和医生)
* @param params { store_id: number } * @param params { store_id: number, guest?: number } - guest: 0=普通接口, 1=Guest接口
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
export async function checkOnlineConsultationConfigApi(params) { export async function checkOnlineConsultationConfigApi(params) {
return await get('/store/check-online-consultation-config', params, 3) const { guest = 0, ...restParams } = params;
// guest=1 时调用 Guest 接口guest=0 时调用普通接口
const url = guest === 1
? '/guest/guest-platform/check-online-consultation-config'
: '/store/check-online-consultation-config';
return await get(url, restParams, 3)
}
/**
* 获取诊所在线问诊信息(委托诊所和委托医生)
* @param params { store_id: number, guest?: number } - guest: 0=普通接口, 1=Guest接口
* @returns {Promise<*>}
*/
export async function getClinicOnlineConsultationInfoApi(params) {
const { guest = 0, ...restParams } = params;
// guest=1 时调用 Guest 接口guest=0 时调用普通接口
const url = guest === 1
? '/guest/guest-platform/get-clinic-online-consultation-info'
: '/store/get-clinic-online-consultation-info';
return await get(url, restParams, 3)
} }
/** /**

124
request/api/register.js Normal file
View File

@@ -0,0 +1,124 @@
import http from './request'
import {post} from './http'
import { unwrapXkApi } from '@/utils/api-response.js'
/**
* 挂号域接口统一模块(除 registerList 外均为 xk-api
* 取值unwrapXkApi → code / result / message
*
* 说明registe/registeInfo 等函数迁自 api.js保持「http(url, { method, data })」
* 老签名不动,避免改动全部调用方的传参结构;新增接口请用 post(url, params, 3) 新封装。
*/
/**
* 获取挂号记录列表 xk-api POST /register/list
* @param params { patient_id: number, store_id: number, page: number }
* @returns {Promise<*>}
*/
export async function getRegisterListApi(params) {
return await post('/register/list', params, 3)
}
/**
* 获取待支付挂号列表(首页提醒用) xk-api POST /register/list
* @param params
* @returns {Promise<Array>}
*/
export async function fetchUnpaidRegisterListApi(params = {}) {
const storeId = params.store_id || uni.getStorageSync('store_id') || '11001';
const res = await post('/register/list?pageSize=100', {
store_id: storeId,
page: 1,
...params,
}, 3);
// /register/list → xk-api
const { ok, payload } = unwrapXkApi(res)
if (!ok) {
return [];
}
const list = (payload && payload.list) || [];
return list.filter((item) => item.status === 0 && item.is_cancel !== 1);
}
/**
* 医生号源日历 xk-api GET /register/schedule-calendar登录态
* 返回 { register_status, register_price, book_days, days[] }days 含每天出诊/时段/余号
* 读值按 xk-api 形态res.data.code == 0 → res.data.result
*/
export async function registerCalendar(params) {
let data = await http('/xkApi/register/schedule-calendar', params)
return data
}
/**
* 医生号源信息(游客态专用)
* URL 写老 Yii doc-register-info但 request.js 会在 guest 模式下重写到
* xk-api /guest/guest-doctor/doc-register-info响应 code/result 形态result 带 calendar
* @deprecated 登录态请勿使用(会打到老 Yii登录态一律走 registerCalendar
*/
export async function registerList(params) {
let data = await http('/oldApi/v1/register/doc-register-info', params)
return data
}
/**
* 挂号建单 xk-api POST /register/create迁自老 Yii /v1/register/register
* 入参新增 visit_date(Y-m-d) + slot_id0=全天兜底);返回 result{register_id, isHas}
* 读值按 xk-api 形态res.data.code == 0 → res.data.result
*/
export async function registe(params) {
let data = await http('/xkApi/register/create', params)
return data
}
/**
* 挂号详情 xk-api POST /register/detail迁自老 Yii /v1/register/register-detail
* 返回 result 为单对象(老接口是 data.list[0]),附 appointment 预约日期/时段信息
*/
export async function registeInfo(params) {
let data = await http('/xkApi/register/detail', params)
return data
}
/**
* 挂号支付 xk-api POST /register/pay易票联
* 返回原生 xk-api 形态res.data.code == 0 → res.data.result0 元单 result.is_paid=1
* (原 normalize 成 errcode/data 的兼容层已删除,调用方一律按 code/result/message 取值)
*/
export async function registePay(params) {
let data = await http('/xkApi/register/pay', params)
return data
}
/**
* 取消挂号 xk-api POST /register/cancel仅未支付单迁自老 Yii /v1/register/cancel
*/
export async function registeUnpay(params) {
let data = await http('/xkApi/register/cancel', params)
return data
}
/**
* 挂号退款 xk-api POST /register/refund已支付未接诊单迁自老 Yii /v1/register/refund
* 注意:老 Yii refund 同时兼容未付取消xk-api 拆成 cancel/refund 两个接口,页面按 status 分流
*/
export async function registeCancle(params) {
let data = await http('/xkApi/register/refund', params)
return data
}
/**
* 修改预约时间(患者本人改期) xk-api POST /register/reschedule
* 参数 { register_id, visit_date: 'Y-m-d', slot_id };就诊当天后端会拦截(提示联系医生/诊所)
*/
export async function registerReschedule(params = {}) {
return await post('/register/reschedule', params, 3)
}
/**
* 挂号改期记录 xk-api POST /register/reschedule-logs
* 返回 result 数组:谁在什么时候把预约从哪个时段改到哪个时段(详情页溯源展示)
*/
export async function registerRescheduleLogs(params = {}) {
return await post('/register/reschedule-logs', params, 3)
}

View File

@@ -14,12 +14,66 @@ const arr = [
'password' 'password'
] ]
// 敏感数据 // 敏感数据:响应解密后保留明文原字段,脱敏值写入 {field}_tm
const sensitiveData = [ const sensitiveData = [
'id_card', 'id_card',
'idcard' 'idcard',
'guardian_id_card'
] ]
// 仅展示脱敏、编辑需明文的字段
const displayMaskFields = [
'mobile',
'express_mobile',
'patient_mobile',
]
/**
* 脱敏展示值(写入 field_tm
*/
function maskFieldValue(key, plainText) {
if (plainText == null || plainText === '') {
return plainText
}
const str = String(plainText)
switch (key) {
case 'express_name':
case 'express_region':
case 'accept_name':
case 'patient':
return str.slice(0, 1).padEnd(str.length, '*')
case 'mobile':
case 'express_mobile':
case 'patient_mobile':
case 'guardian_mobile':
if (str.length <= 7) {
return str
}
return str.slice(0, 3).padEnd(str.length - 4, '*') + str.slice(-4)
case 'id_card':
case 'idcard':
case 'guardian_id_card':
if (str.length <= 10) {
return str
}
return str.slice(0, 6).padEnd(str.length - 4, '*') + str.slice(-4)
default:
return str
}
}
/** 只读展示:优先 field_tm */
export function displayTm(obj, field) {
if (obj == null) {
return ''
}
const tm = obj[field + '_tm']
if (tm != null && tm !== '') {
return tm
}
return obj[field] ?? ''
}
const isDev = checkDev('dev'); const isDev = checkDev('dev');
// 环境URL配置 // 环境URL配置
@@ -29,14 +83,14 @@ const ENV_URLS = {
main: "https://app.xiaokang88.com/member", main: "https://app.xiaokang88.com/member",
xkApi: "https://api.xiaokang88.com/api/mobile", xkApi: "https://api.xiaokang88.com/api/mobile",
shop: "https://shop.xiaokang88.com/member", shop: "https://shop.xiaokang88.com/member",
im: "https://api.ws.g.xiaokang88.com/api" im: "https://api.ws.g.xiaokang88.com/api",
}, },
// 测试环境 // 测试环境
test: { test: {
main: "https://test.xk.app.nailaoyun.cn/member", main: "https://xk.test.saas.api-yii.nailaoyun.cn/member",
xkApi: "https://test.xk.api.nailaoyun.cn/api/mobile", xkApi: "https://xk.test.saas.api.nailaoyun.cn/api/mobile",
shop: "https://test.xk.shop.nailaoyun.cn/member", shop: "https://test.xk.shop.nailaoyun.cn/member1",
im: "https://api.ws.g.nailaoyun.cn/api" im: "https://xk.ws.nailaoyun.cn/api"
}, },
// 本地开发环境 // 本地开发环境
local: { local: {
@@ -54,7 +108,7 @@ function getEnvUrls() {
return ENV_URLS.local; return ENV_URLS.local;
} }
// 检查是否开启了开发者测试模式 // 检查是否开启了开发者测试模式
const devMode = uni.getStorageSync('dev_mode'); const devMode = checkDev('test');
return devMode ? ENV_URLS.test : ENV_URLS.prod; return devMode ? ENV_URLS.test : ENV_URLS.prod;
} }
@@ -81,12 +135,24 @@ function request(url, params, method = 0) {
} else if (url.indexOf('/xkApi/product/home-zones') !== -1) { } else if (url.indexOf('/xkApi/product/home-zones') !== -1) {
url = url.replace('/xkApi/product/home-zones', '/guest/guest-product/home-zones'); url = url.replace('/xkApi/product/home-zones', '/guest/guest-product/home-zones');
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/product/home-display-config') !== -1) {
url = url.replace('/xkApi/product/home-display-config', '/guest/guest-product/home-display-config');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/platform/config') !== -1) { } else if (url.indexOf('/xkApi/platform/config') !== -1) {
url = url.replace('/xkApi/platform/config', '/guest/guest-platform/config'); url = url.replace('/xkApi/platform/config', '/guest/guest-platform/config');
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/platform/qualifications') !== -1) { } else if (url.indexOf('/xkApi/platform/qualifications') !== -1) {
url = url.replace('/xkApi/platform/qualifications', '/guest/guest-platform/qualifications'); url = url.replace('/xkApi/platform/qualifications', '/guest/guest-platform/qualifications');
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/store/info') !== -1) {
url = url.replace('/xkApi/store/info', '/guest/guest-home/store-info');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/store/list') !== -1) {
url = url.replace('/xkApi/store/list', '/guest/guest-home/store-list');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/special-prescription/') !== -1) {
url = url.replace('/xkApi/special-prescription/', '/guest/guest-special-prescription/');
baseURL = envUrls.xkApi;
} }
} }
@@ -112,12 +178,24 @@ function request(url, params, method = 0) {
} else if (url.indexOf('/oldApi/v1/store/detail') !== -1) { } else if (url.indexOf('/oldApi/v1/store/detail') !== -1) {
url = url.replace('/oldApi/v1/store/detail', '/guest/guest-home/store-detail'); url = url.replace('/oldApi/v1/store/detail', '/guest/guest-home/store-detail');
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
} else if (url.indexOf('/oldApi/v1/store/list') !== -1) {
url = url.replace('/oldApi/v1/store/list', '/guest/guest-home/store-list');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/oldApi/v1/register/doc-register-info') !== -1) { } else if (url.indexOf('/oldApi/v1/register/doc-register-info') !== -1) {
url = url.replace('/oldApi/v1/register/doc-register-info', '/guest/guest-doctor/doc-register-info'); url = url.replace('/oldApi/v1/register/doc-register-info', '/guest/guest-doctor/doc-register-info');
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
} else if (url.indexOf('/oldApi/v1/doctor/office-list') !== -1) { } else if (url.indexOf('/oldApi/v1/doctor/office-list') !== -1) {
url = url.replace('/oldApi/v1/doctor/office-list', '/guest/guest-doctor/office-list'); url = url.replace('/oldApi/v1/doctor/office-list', '/guest/guest-doctor/office-list');
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
} else if (url.indexOf('/oldApi/v1/info/categories') !== -1) {
url = url.replace('/oldApi/v1/info/categories', '/guest/guest-info/categories');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/oldApi/v1/info/article-list') !== -1) {
url = url.replace('/oldApi/v1/info/article-list', '/guest/guest-info/article-list');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/oldApi/v1/info/article-info') !== -1) {
url = url.replace('/oldApi/v1/info/article-info', '/guest/guest-info/article-info');
baseURL = envUrls.xkApi;
} }
} }
@@ -132,6 +210,12 @@ function request(url, params, method = 0) {
} else if (url.indexOf('/v1/register/doc-register-info') !== -1 || url.indexOf('/member/v1/register/doc-register-info') !== -1) { } else if (url.indexOf('/v1/register/doc-register-info') !== -1 || url.indexOf('/member/v1/register/doc-register-info') !== -1) {
url = url.replace(/\/member\/v1\/register\/doc-register-info|\/v1\/register\/doc-register-info/, '/guest/guest-doctor/doc-register-info'); url = url.replace(/\/member\/v1\/register\/doc-register-info|\/v1\/register\/doc-register-info/, '/guest/guest-doctor/doc-register-info');
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
} else if (url.indexOf('/v1/store/info') !== -1 || url.indexOf('/member/v1/store/info') !== -1) {
url = url.replace(/\/member\/v1\/store\/info|\/v1\/store\/info/, '/guest/guest-home/store-info');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/v1/store/list') !== -1 || url.indexOf('/member/v1/store/list') !== -1) {
url = url.replace(/\/member\/v1\/store\/list|\/v1\/store\/list/, '/guest/guest-home/store-list');
baseURL = envUrls.xkApi;
} }
} }
@@ -143,8 +227,9 @@ function request(url, params, method = 0) {
} }
if (url.split('/').indexOf('xkApi') !== -1) { if (url.split('/').indexOf('xkApi') !== -1) {
baseURL = envUrls.xkApi; baseURL = envUrls.xkApi;
// 如果是guest模式且是xkApi请求添加/guest/前缀 // 如果是guest模式且是xkApi请求添加/guest/前缀(但避免重复添加)
if (isGuestRequest) { // 转诊相关接口需要登录不转换为guest路由
if (isGuestRequest && url.indexOf('/guest/') === -1 && url.indexOf('/transfer-prescription') === -1) {
url = url.replace('/xkApi', '/guest/xkApi'); url = url.replace('/xkApi', '/guest/xkApi');
} }
} }
@@ -155,10 +240,29 @@ function request(url, params, method = 0) {
// 加密 // 加密
if (params != null) { if (params != null) {
if (!params.data) {
params.data = {};
}
params.data = getRes(params.data, false); params.data = getRes(params.data, false);
// 如果是guest模式强制设置store_id为11001 const isProductApi = url.indexOf('/product/') !== -1;
if (isProductApi && !params.data.store_id) {
const localStoreId = uni.getStorageSync('store_id');
params.data.store_id = localStoreId || getGuestStoreId();
}
// 如果是guest模式设置store_id优先使用用户传入或本地存储的值
if (isGuestRequest && params.data) { if (isGuestRequest && params.data) {
params.data.store_id = getGuestStoreId(); // 优先使用用户传入的 store_id
if (!params.data.store_id) {
// 如果用户没传,尝试从本地存储获取
const localStoreId = uni.getStorageSync('store_id');
if (localStoreId) {
params.data.store_id = localStoreId;
} else {
// 如果本地存储也没有,使用默认值(保持向后兼容)
params.data.store_id = getGuestStoreId();
}
}
// 如果用户已经传了 store_id就使用用户传入的值不做任何修改
} }
} }
let num = 0; let num = 0;
@@ -224,56 +328,47 @@ function request(url, params, method = 0) {
function getRes(obj, isDecode = true) { function getRes(obj, isDecode = true) {
try { try {
if (obj == null || typeof obj !== 'object') {
return obj
}
for (const key in obj) { for (const key in obj) {
if (key.endsWith('_tm')) {
continue
}
if (Array.isArray(obj[key])) { if (Array.isArray(obj[key])) {
obj[key] = getRes(obj[key]) obj[key] = getRes(obj[key], isDecode)
} else if (typeof obj[key] === 'object') { } else if (obj[key] !== null && typeof obj[key] === 'object') {
obj[key] = getRes(obj[key]) obj[key] = getRes(obj[key], isDecode)
} else { } else if (arr.indexOf(key) !== -1) {
// 判断obj[key]是否在arr中 // 未填写的敏感字段保持空串,禁止加解密(避免空值被解成密文乱码)
if (arr.indexOf(key) !== -1) { if (obj[key] == null || obj[key] === '') {
let aseFile = '' obj[key] = ''
if (isDecode === true) { if (isDecode === true && (sensitiveData.indexOf(key) !== -1 || displayMaskFields.indexOf(key) !== -1)) {
aseFile = customBase64Decode(obj[key]) obj[key + '_tm'] = ''
} else {
aseFile = customBase64Encode(obj[key])
} }
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test( continue
typeof aseFile === 'string' ? aseFile : '' }
) let plainValue = ''
if (isGarbled) { if (isDecode === true) {
aseFile = obj[key] plainValue = customBase64Decode(obj[key])
} else {
plainValue = customBase64Encode(obj[key])
}
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
typeof plainValue === 'string' ? plainValue : ''
)
if (isGarbled) {
plainValue = obj[key]
}
if (isDecode === true) {
const needTm = sensitiveData.indexOf(key) !== -1
|| displayMaskFields.indexOf(key) !== -1
obj[key] = plainValue
if (needTm) {
obj[key + '_tm'] = maskFieldValue(key, plainValue)
} }
// 为了修改,暂时不脱敏 } else {
if (isDecode === true) { obj[key] = plainValue
if (sensitiveData.indexOf(key) !== -1) {
// 数据脱敏,自动匹配姓名、手机号、身份证
switch (key) {
case 'express_name':
case 'express_region':
case 'accept_name':
case 'patient':
// 保留前两个字符,其余用星号代替
aseFile = aseFile.slice(0, 1).padEnd(aseFile.length, '*');
break;
case 'mobile':
case 'express_mobile':
// 保留前三位和后四位,中间用星号代替
aseFile = aseFile.slice(0, 3).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
break;
case 'id_card':
case 'idcard':
// 保留前六位和后四位,中间用星号代替
aseFile = aseFile.slice(0, 6).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
break;
default:
// 默认情况下,全部用星号代替
break;
}
}
}
obj[key] = aseFile
} }
} }
} }
@@ -285,17 +380,22 @@ function getRes(obj, isDecode = true) {
} }
function customBase64Decode(data) { function customBase64Decode(data) {
// 空值直接返回空串,避免 Base64.decode(null/'' ) 失败后回退成密文原串
if (data == null || data === '') {
return ''
}
// return data // return data
try { try {
// 第一次Base64解码 // 第一次Base64解码
const decodedFirst = Base64.decode(data) const decodedFirst = Base64.decode(data)
// 截取从第30个字符开始往后的内容 // 截取从第30个字符开始往后的内容
const subStr = decodedFirst.slice(30) const subStr = decodedFirst.slice(30)
// 第二次Base64解码 // 第二次Base64解码;解出空串说明业务上就是未填写,不要回退成密文
return Base64.decode(subStr) != '' ? Base64.decode(subStr) : data const decodedSecond = Base64.decode(subStr)
return decodedSecond !== '' ? decodedSecond : ''
} catch (error) { } catch (error) {
console.error(`解码Base64字符串【${data}】时发生错误`, error) console.error(`解码Base64字符串【${data}】时发生错误`, error)
return data return ''
} }
} }

View File

@@ -0,0 +1,105 @@
import { get, post } from './http'
export const SPECIAL_PRESCRIPTION_STORAGE_KEY = 'special_prescription_register_context'
/**
* 首页特色方配置
* @param params {{ store_id?: number|string }}
*/
export async function getSpecialPrescriptionHomeApi(params) {
return await get('/special-prescription/home', params, 3)
}
/**
* 特色方列表页配置
* @param params {{ store_id?: number|string }}
*/
export async function getSpecialPrescriptionListPageApi(params) {
return await get('/special-prescription/list-page', params, 3)
}
/**
* 特色方列表
* @param params {{ store_id?: number|string, category_id?: number, name?: string, page?: number, pageSize?: number }}
*/
export async function getSpecialPrescriptionListApi(params) {
return await get('/special-prescription/list', params, 3)
}
/**
* 特色方详情
* @param params {{ id: number|string, store_id?: number|string }}
*/
export async function getSpecialPrescriptionDetailApi(params) {
return await get('/special-prescription/detail', params, 3)
}
/**
* 特色方分类
*/
export async function getSpecialPrescriptionCategoriesApi(params = {}) {
return await get('/special-prescription/categories', params, 3)
}
/**
* 挂号成功后创建选方记录
* @param params {{ register_id, special_prescription_id, dose_count, store_id, doctor_id }}
*/
export async function createSpecialPrescriptionPatientRecordApi(params) {
return await post('/special-prescription/create-patient-record', params, 3)
}
/**
* 保存特色方挂号上下文merge 已有值,避免后续页面未传 sku_id 时覆盖为 0
* @param ctx {{ special_prescription_id, dose_count?, sku_id?, doctor_id? }}
*/
export function saveSpecialPrescriptionRegisterContext(ctx) {
if (!ctx || !ctx.special_prescription_id) return
const prev = getSpecialPrescriptionRegisterContext() || {}
const merged = {
special_prescription_id: ctx.special_prescription_id,
dose_count: ctx.dose_count != null ? ctx.dose_count : (prev.dose_count || 1),
doctor_id: ctx.doctor_id != null ? ctx.doctor_id : (prev.doctor_id || ''),
}
// 未传 sku_id 字段时保留 storage 已有值,防止中间页覆盖为 0
if (Object.prototype.hasOwnProperty.call(ctx, 'sku_id')) {
merged.sku_id = Number(ctx.sku_id) || 0
} else {
merged.sku_id = Number(prev.sku_id) || 0
}
uni.setStorageSync(SPECIAL_PRESCRIPTION_STORAGE_KEY, merged)
}
export function getSpecialPrescriptionRegisterContext() {
return uni.getStorageSync(SPECIAL_PRESCRIPTION_STORAGE_KEY) || null
}
export function clearSpecialPrescriptionRegisterContext() {
uni.removeStorageSync(SPECIAL_PRESCRIPTION_STORAGE_KEY)
}
/**
* 挂号成功后绑定特色方选方记录(幂等,失败不阻断流程)
* @param registerId
*/
export async function tryCreateSpecialPrescriptionPatientRecord(registerId) {
if (!registerId) return
const ctx = getSpecialPrescriptionRegisterContext()
if (!ctx || !ctx.special_prescription_id) return
try {
const res = await createSpecialPrescriptionPatientRecordApi({
register_id: registerId,
special_prescription_id: ctx.special_prescription_id,
dose_count: ctx.dose_count || 1,
sku_id: ctx.sku_id || 0,
store_id: uni.getStorageSync('store_id') || '11001',
doctor_id: ctx.doctor_id,
})
if (res.data?.code === 0 || res.data?.code === '0') {
clearSpecialPrescriptionRegisterContext()
}
} catch (e) {
console.error('tryCreateSpecialPrescriptionPatientRecord', e)
}
}

View File

@@ -0,0 +1,78 @@
import {post, get} from './http'
/**
* 获取转诊消息列表
* @returns {Promise<*>}
*/
export async function getTransferPrescriptionListApi() {
return await post('/transfer-prescription/list', {}, 3)
}
/**
* 获取转诊详情
* @param params { id: number }
* @returns {Promise<*>}
*/
export async function getTransferPrescriptionDetailApi(params) {
return await get('/transfer-prescription/detail', params, 3)
}
/**
* 同意转诊
* @param params { id: number }
* @returns {Promise<*>}
*/
export async function agreeTransferPrescriptionApi(params) {
return await post('/transfer-prescription/agree', params, 3)
}
/**
* 拒绝转诊
* @param params { id: number, reason?: string }
* @returns {Promise<*>}
*/
export async function rejectTransferPrescriptionApi(params) {
return await post('/transfer-prescription/reject', params, 3)
}
/**
* 获取转诊问题列表(用于咨询页面)
* @returns {Promise<*>}
*/
export async function getTransferQuestionsApi() {
return await get('/transfer-prescription/get-questions', {}, 3)
}
/**
* 提交转诊问题答案
* @param params { transfer_id: number, answers: Array<{question_id: number, answer: string}> }
* @returns {Promise<*>}
*/
export async function submitTransferQuestionsApi(params) {
return await post('/transfer-prescription/submit-questions', params, 3)
}
/**
* 获取转诊助理配置(移动端)
* @returns {Promise<*>}
*/
export async function getTransferAssistantConfigApi() {
return await get('/transfer-assistant-config/get-config', {}, 3)
}
/**
* 获取转诊咨询协议信息(固定文案和协议列表)
* @returns {Promise<*>}
*/
export async function getTransferConsultationAgreementInfoApi() {
return await get('/transfer-consultation/get-agreement-info', {}, 3)
}
/**
* 获取协议详情根据协议ID
* @param {number} agreementId 协议ID
* @returns {Promise<*>}
*/
export async function getAgreementDetailApi(agreementId) {
return await get(`/agreement/get-detail?id=${agreementId}`, {}, 3)
}

10
request/api/user.js Normal file
View File

@@ -0,0 +1,10 @@
import {post} from './http'
/**
* 获取用户信息(包含订单统计)
* @param params { store_id: number }
* @returns {Promise<*>}
*/
export async function getUserInfoApi(params) {
return await post('/user/info', params, 3)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

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