Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87f21de8de | ||
|
|
8aafb0114b | ||
|
|
158ffd9e6e | ||
|
|
22e93c5dba | ||
|
|
2a1fe884d7 | ||
|
|
62ffb6fd31 | ||
|
|
97bc48c17c | ||
|
|
1fbc14638a |
41
.cursor/rules/Api-Module-Split.mdc
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
description: 接口迁移到 xk-api 后必须拆出独立模块文件,禁止再往 all.js 大文件里塞
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# API 模块拆分规则(xk-api 迁移必读)
|
||||||
|
|
||||||
|
`api/all.js`(约 1700+ 行)是历史遗留的 Yii 接口聚合文件。
|
||||||
|
**任何迁移到 xk-api 的接口、以及所有新增的 xk-api 接口,一律不得写进 `all.js`**,必须提取/新建到 `api/` 下的独立业务模块文件。
|
||||||
|
|
||||||
|
## 必须做
|
||||||
|
|
||||||
|
1. **按业务域建独立文件**:`api/<module>.js`,小驼峰命名(已有先例:`pharmacistExamine.js`、`reception.js`、`clinicAdmin.js`、`platformAdmin.js`、`chat.js`)
|
||||||
|
2. **xk-api 接口 URL 必须带 `/newApi/` 前缀**(`common/js/common.js` 据此切到 `newApiBase`),Yii 接口用无前缀相对路径
|
||||||
|
3. **文件头注释标明后端与取值方式**,每个函数注释标 HTTP 方法与路径,例如:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* 药师审方相关接口(xk-api,取值用 code / result / message,配合 unwrapXkApi)
|
||||||
|
* 老 Yii service/v1/examine/* 已迁移到 xk-api 的 /pharmacist-examine/*
|
||||||
|
*/
|
||||||
|
import { req } from '@/common/js/index.js';
|
||||||
|
|
||||||
|
/** 审方列表 xk-api GET /pharmacist-examine/list */
|
||||||
|
export function getExamineList(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/pharmacist-examine/list',
|
||||||
|
method: 'GET',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **迁移某接口时**(Yii → xk-api):把它从 `all.js` 挪到对应模块文件,`all.js` 里原函数直接删除(调用方改 import),确实来不及改完所有调用方时才允许在 `all.js` 保留一行 re-export 并标 `@deprecated`
|
||||||
|
5. 页面取值统一 `unwrapXkApi`(见 Api-Response.mdc)
|
||||||
|
|
||||||
|
## 禁止做
|
||||||
|
|
||||||
|
- 禁止在 `all.js` 中新增任何函数(包括 Yii 接口——新 Yii 接口也应放模块文件)
|
||||||
|
- 禁止新代码写 `unwrapClinicRes` 这类「双形态自动兼容」取值;按后端固定取值
|
||||||
|
- 禁止一个模块文件里混装两个后端的接口而不加注释区分;同一文件确需并存时,每个函数注释必须标明 `// xk-api` 或 `// Yii`
|
||||||
32
.cursor/rules/Business-Shared.mdc
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
description: 后台角色(诊所/平台/业务员)必须复用 sub_business_shared,禁止再复制商品订单等业务页
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# 后台角色共享页(禁止重复造轮子)
|
||||||
|
|
||||||
|
诊所管理员 / 平台管理员 / 业务员(含省/市经理)的业务页优先复用 `subPackages/sub_business_shared`,用 `businessMixin` + `resolveBusinessContext` 按 `loginMode` 切 API,不要再复制一套列表/详情。
|
||||||
|
|
||||||
|
## 商品订单
|
||||||
|
|
||||||
|
- **必须**走 `sub_business_shared/order`(平台、业务员)
|
||||||
|
- 诊所管理员继续用历史页 `sub_clinic_admin/order`,**本次不迁、也不要再抄一份**
|
||||||
|
- API:平台 `platform-admin-order/*`,业务员 `salesperson-order/*`(`api/businessApi.js`)
|
||||||
|
- **数据范围跟 PC**:业务员/省/市经理看名下门店全部订单(后端 `getMyStoreIds()` / `store.code = admin.code`),**禁止**前端再按 `salesperson_id` 过滤
|
||||||
|
- **操作跟 PC**:业务员 `canShip/canRefund = false`(`context.js` 的 `bizCap`),详情按钮按能力显隐
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ❌ BAD:再写一套业务员订单页,或按推广员 ID 滤单
|
||||||
|
// ✅ GOOD:工作台入口指向 /subPackages/sub_business_shared/order/index
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可复用清单(先找再写)
|
||||||
|
|
||||||
|
`business-page-layout`、`CollapsibleFilterPanel`、`ListSkeleton`、`InputListPage`、`xk-list-card`、`filterCache`、`pullRefreshMixin`
|
||||||
|
|
||||||
|
## 下拉刷新
|
||||||
|
|
||||||
|
- 自定义导航 + 内部列表:`scroll-view` 的 `refresher-enabled` + `isRefreshing`(对齐仓库/订单),mixin 用 `pullRefreshMixin`
|
||||||
|
- 整页滚动列表:`pages.json` 开 `enablePullDownRefresh` + `onPullDownRefresh` + `uni.stopPullDownRefresh`
|
||||||
|
- **禁止**给聊天页(`sub_online_reception/pages/chat`)加下拉刷新
|
||||||
|
- 开方编辑器、录入/申请表单、登录、协议、静态成功页不加
|
||||||
11
.cursor/rules/Clarify-Before-Act.mdc
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
description: 不懂先问,明确目标后再动手
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# 先问再做
|
||||||
|
|
||||||
|
1. **不懂就问**:需求、业务口径、技术选型、影响范围等有不确定之处,必须先向你确认,禁止凭猜测直接改代码。
|
||||||
|
2. **一问一答**:每次只提一个最关键的问题,等你回复后再继续;避免一次抛出多个选项把讨论打散。
|
||||||
|
3. **说明提问原因**:每个问题都要附带「我为什么要问」——例如缺什么信息、有哪些备选理解、猜错会有什么后果。
|
||||||
|
4. **目标对齐后再动手**:确认对你的目标(要达成什么、不做什么、验收标准)有明确认知后,再开始查代码、写 SQL、改页面。
|
||||||
@@ -12,3 +12,5 @@ alwaysApply: true
|
|||||||
7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定
|
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)
|
8. 接口取值:写代码时先确认接口是 xk-api 还是 Yii,再固定用 `code/result/message` 或 `errcode/data/msg`(可用 `unwrapXkApi` / `unwrapYiiApi`);禁止运行时猜测、禁止 `result || data`(详见 Api-Response.mdc)
|
||||||
9. VIP 功能判断:统一用 `@/utils/vip.js` 的 `hasVipPermission` / `fetchStoreVipPermissions` / `VIP_FEATURE`;模板显隐用 `<vip-gate code="medical_record" :permissions="storeVipPermissions">`;禁止各页手写 `permissions.indexOf` 或重复解析 get-current-store-type
|
9. VIP 功能判断:统一用 `@/utils/vip.js` 的 `hasVipPermission` / `fetchStoreVipPermissions` / `VIP_FEATURE`;模板显隐用 `<vip-gate code="medical_record" :permissions="storeVipPermissions">`;禁止各页手写 `permissions.indexOf` 或重复解析 get-current-store-type
|
||||||
|
10. 卡片强调样式:禁止用左侧竖色条(`border-left` 色条/左侧色块)做卡片强调或分类标识,统一用「1px 同色系边框 + 同色低透明度外发光(box-shadow 微光)」;历史页面暂不强制回改,新增/改动的卡片必须遵守
|
||||||
|
11. API 模块拆分:迁移到 xk-api 的接口和所有新增接口必须放 `api/<module>.js` 独立模块文件,禁止再往 `api/all.js` 大文件里新增(详见 Api-Module-Split.mdc)
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ export function createBusinessApi(prefix, headerFlag) {
|
|||||||
submitWithdrawal(data) {
|
submitWithdrawal(data) {
|
||||||
return bizRequest({ url: `${prefix}-withdrawal/withdrawal`, method: 'POST', data });
|
return bizRequest({ url: `${prefix}-withdrawal/withdrawal`, method: 'POST', data });
|
||||||
},
|
},
|
||||||
|
getWithdrawableOrders(params) {
|
||||||
|
return bizRequest({ url: `${prefix}-withdrawal/withdrawable-orders`, method: 'GET', data: params || {} });
|
||||||
|
},
|
||||||
passWithdrawal(data) {
|
passWithdrawal(data) {
|
||||||
return bizRequest({ url: `${prefix}-withdrawal/pass-application`, method: 'POST', data });
|
return bizRequest({ url: `${prefix}-withdrawal/pass-application`, method: 'POST', data });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ export function getClinicAdminDashboardStats() {
|
|||||||
return clinicRequest({ url: `${PREFIX}-dashboard/stats`, method: 'GET' });
|
return clinicRequest({ url: `${PREFIX}-dashboard/stats`, method: 'GET' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未来7天预约明细(工作台预约卡片点击弹层,本店维度)
|
||||||
|
* xk-api GET /clinic-admin-dashboard/appointment-upcoming-list
|
||||||
|
*/
|
||||||
|
export function getClinicAdminAppointmentUpcomingList(data) {
|
||||||
|
return clinicRequest({ url: `${PREFIX}-dashboard/appointment-upcoming-list`, method: 'GET', data });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作台功能入口下发(后台按角色分配的宫格入口,替代前端硬编码)
|
* 工作台功能入口下发(后台按角色分配的宫格入口,替代前端硬编码)
|
||||||
* xk-api GET /clinic-admin-dashboard/entries
|
* xk-api GET /clinic-admin-dashboard/entries
|
||||||
@@ -102,6 +110,14 @@ export function toggleClinicAdminFavoriteEntry(code) {
|
|||||||
return clinicRequest({ url: `${PREFIX}-dashboard/toggle-favorite-entry`, method: 'POST', data: { code } });
|
return clinicRequest({ url: `${PREFIX}-dashboard/toggle-favorite-entry`, method: 'POST', data: { code } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存我的常用(功能页悬浮编辑 / 首页快捷添加)
|
||||||
|
* xk-api POST /clinic-admin-dashboard/batch-save-favorite-entry
|
||||||
|
*/
|
||||||
|
export function batchSaveClinicAdminFavoriteEntry(codes) {
|
||||||
|
return clinicRequest({ url: `${PREFIX}-dashboard/batch-save-favorite-entry`, method: 'POST', data: { codes } });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
||||||
* xk-api POST /clinic-admin-dashboard/report-entry-usage
|
* xk-api POST /clinic-admin-dashboard/report-entry-usage
|
||||||
@@ -162,6 +178,11 @@ export function submitWithdrawal(data) {
|
|||||||
return clinicRequest({ url: `${PREFIX}-withdrawal/withdrawal`, method: 'POST', data });
|
return clinicRequest({ url: `${PREFIX}-withdrawal/withdrawal`, method: 'POST', data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 可提订单明细 xk-api GET /clinic-admin-withdrawal/withdrawable-orders */
|
||||||
|
export function getWithdrawableOrders(params) {
|
||||||
|
return clinicRequest({ url: `${PREFIX}-withdrawal/withdrawable-orders`, method: 'GET', data: params || {} });
|
||||||
|
}
|
||||||
|
|
||||||
export function getReconciliationList(params) {
|
export function getReconciliationList(params) {
|
||||||
return clinicRequest({ url: `${PREFIX}-reconciliation/list`, method: 'GET', data: params });
|
return clinicRequest({ url: `${PREFIX}-reconciliation/list`, method: 'GET', data: params });
|
||||||
}
|
}
|
||||||
@@ -263,3 +284,8 @@ export function updateWarehousePrice(data) {
|
|||||||
export function getWarehouseProductTypeOptions() {
|
export function getWarehouseProductTypeOptions() {
|
||||||
return clinicRequest({ url: `${PREFIX}-warehouse/product-type-options`, method: 'GET' });
|
return clinicRequest({ url: `${PREFIX}-warehouse/product-type-options`, method: 'GET' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 诊所/药店可见仓库 Tab xk-api GET /clinic-admin-warehouse/warehouse-tabs */
|
||||||
|
export function getWarehouseTabs() {
|
||||||
|
return clinicRequest({ url: `${PREFIX}-warehouse/warehouse-tabs`, method: 'GET' });
|
||||||
|
}
|
||||||
|
|||||||
@@ -114,6 +114,14 @@ export function toggleClinicSalespersonFavoriteEntry(code) {
|
|||||||
return clinicSalespersonRequest({ url: '/newApi/clinic-salesperson-dashboard/toggle-favorite-entry', method: 'POST', data: { code } });
|
return clinicSalespersonRequest({ url: '/newApi/clinic-salesperson-dashboard/toggle-favorite-entry', method: 'POST', data: { code } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存我的常用(功能页悬浮编辑 / 首页快捷添加)
|
||||||
|
* xk-api POST /clinic-salesperson-dashboard/batch-save-favorite-entry
|
||||||
|
*/
|
||||||
|
export function batchSaveClinicSalespersonFavoriteEntry(codes) {
|
||||||
|
return clinicSalespersonRequest({ url: '/newApi/clinic-salesperson-dashboard/batch-save-favorite-entry', method: 'POST', data: { codes } });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
||||||
* xk-api POST /clinic-salesperson-dashboard/report-entry-usage
|
* xk-api POST /clinic-salesperson-dashboard/report-entry-usage
|
||||||
|
|||||||
@@ -89,16 +89,7 @@ export function imageOrderInfo(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*咨询列表 挂号列表
|
// 老 Yii register/list(挂号列表)已迁移到 xk-api doctor-reception-wx/patient-list(见 api/reception.js getPatientList)
|
||||||
order_id int 是 订单id
|
|
||||||
*/
|
|
||||||
export function registerList(data) {
|
|
||||||
return req.request({
|
|
||||||
url: 'register/list',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 咨询列表角标
|
// 咨询列表角标
|
||||||
/**
|
/**
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ export function getPlatformAdminDashboardStats() {
|
|||||||
return platformDashboardRequest({ url: '/newApi/platform-admin-dashboard/stats', method: 'GET' });
|
return platformDashboardRequest({ url: '/newApi/platform-admin-dashboard/stats', method: 'GET' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未来7天预约明细(工作台预约卡片点击弹层,平台全量维度)
|
||||||
|
* xk-api GET /platform-admin-dashboard/appointment-upcoming-list
|
||||||
|
*/
|
||||||
|
export function getPlatformAdminAppointmentUpcomingList(data) {
|
||||||
|
return platformDashboardRequest({ url: '/newApi/platform-admin-dashboard/appointment-upcoming-list', method: 'GET', data });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 平台工作台功能入口下发(后台按角色分配的宫格入口,替代前端硬编码)
|
* 平台工作台功能入口下发(后台按角色分配的宫格入口,替代前端硬编码)
|
||||||
* xk-api GET /platform-admin-dashboard/entries
|
* xk-api GET /platform-admin-dashboard/entries
|
||||||
@@ -52,6 +60,14 @@ export function togglePlatformAdminFavoriteEntry(code) {
|
|||||||
return platformDashboardRequest({ url: '/newApi/platform-admin-dashboard/toggle-favorite-entry', method: 'POST', data: { code } });
|
return platformDashboardRequest({ url: '/newApi/platform-admin-dashboard/toggle-favorite-entry', method: 'POST', data: { code } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存我的常用(功能页悬浮编辑 / 首页快捷添加)
|
||||||
|
* xk-api POST /platform-admin-dashboard/batch-save-favorite-entry
|
||||||
|
*/
|
||||||
|
export function batchSavePlatformAdminFavoriteEntry(codes) {
|
||||||
|
return platformDashboardRequest({ url: '/newApi/platform-admin-dashboard/batch-save-favorite-entry', method: 'POST', data: { codes } });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
||||||
* xk-api POST /platform-admin-dashboard/report-entry-usage
|
* xk-api POST /platform-admin-dashboard/report-entry-usage
|
||||||
|
|||||||
88
api/publicAccountPay.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 公账支付(xk-api,取值 unwrapXkApi → code / result / message)
|
||||||
|
* 医生走 /public-account-pay;诊所管理员走 /clinic-admin-public-account-pay
|
||||||
|
*/
|
||||||
|
import { req } from '@/common/js/index.js'
|
||||||
|
|
||||||
|
function isClinicAdmin() {
|
||||||
|
return uni.getStorageSync('loginMode') === 'clinic_admin'
|
||||||
|
}
|
||||||
|
|
||||||
|
function dailyPrefix() {
|
||||||
|
return isClinicAdmin()
|
||||||
|
? '/newApi/clinic-admin-public-account-pay/'
|
||||||
|
: '/newApi/public-account-pay/'
|
||||||
|
}
|
||||||
|
|
||||||
|
function dailyHeader() {
|
||||||
|
return isClinicAdmin() ? { _clinicAdmin: '1' } : {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 申请公账支付 xk-api POST /public-account-pay/apply */
|
||||||
|
export function applyPublicAccountPayApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/public-account-pay/apply',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 日汇总详情 xk-api GET .../daily-detail */
|
||||||
|
export function getPublicAccountDailyBillInfoApi(id) {
|
||||||
|
return req.request({
|
||||||
|
url: dailyPrefix() + 'daily-detail',
|
||||||
|
method: 'GET',
|
||||||
|
data: { id },
|
||||||
|
header: dailyHeader(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 锁店只读:日账单 + 商品订单 xk-api GET .../daily-lock-orders */
|
||||||
|
export function getPublicAccountDailyLockOrdersApi(data = {}) {
|
||||||
|
return req.request({
|
||||||
|
url: dailyPrefix() + 'daily-lock-orders',
|
||||||
|
method: 'GET',
|
||||||
|
data,
|
||||||
|
header: dailyHeader(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 补出日汇总 xk-api POST .../daily-generate */
|
||||||
|
export function generatePublicAccountDailyBillApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: dailyPrefix() + 'daily-generate',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
header: dailyHeader(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 上传日汇总凭证 xk-api POST .../daily-upload */
|
||||||
|
export function uploadPublicAccountDailyBillApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: dailyPrefix() + 'daily-upload',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
header: dailyHeader(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 催办确认到账 xk-api POST .../daily-urge */
|
||||||
|
export function urgePublicAccountDailyBillApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: dailyPrefix() + 'daily-urge',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
header: dailyHeader(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 锁店现算状态(配色/日汇总状态/联系人)xk-api GET .../lock-status */
|
||||||
|
export function getPublicAccountLockStatusApi() {
|
||||||
|
return req.request({
|
||||||
|
url: dailyPrefix() + 'lock-status',
|
||||||
|
method: 'GET',
|
||||||
|
data: {},
|
||||||
|
header: dailyHeader(),
|
||||||
|
})
|
||||||
|
}
|
||||||
112
api/reception.js
@@ -43,6 +43,42 @@ export function getWorkbenchStats(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 未来7天预约明细(工作台预约卡片点击弹层);xk-api,业务在 result */
|
||||||
|
export function getAppointmentUpcomingListApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/appointment-upcoming-list',
|
||||||
|
method: 'GET',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 医生代患者改期 xk-api POST /doctor-reception-wx/reschedule-appointment(register_id/visit_date/slot_id/remark,写改期日志) */
|
||||||
|
export function rescheduleAppointmentApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/reschedule-appointment',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成患者代付码 xk-api POST /doctor-reception-wx/proxy-pay-qrcode(order_id;返回 base64 小程序码 + 订单号/金额) */
|
||||||
|
export function getProxyPayQrcodeApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/proxy-pay-qrcode',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 医生本人号源日历 xk-api GET /doctor-reception-wx/my-schedule-calendar(改期抽屉选新日期/时段) */
|
||||||
|
export function getMyScheduleCalendarApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/my-schedule-calendar',
|
||||||
|
method: 'GET',
|
||||||
|
data: data || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 获取患者详情(挂号维度的就诊信息,不含处方列表)
|
// 获取患者详情(挂号维度的就诊信息,不含处方列表)
|
||||||
export function getPatientItem(id) {
|
export function getPatientItem(id) {
|
||||||
return req.request({
|
return req.request({
|
||||||
@@ -584,6 +620,32 @@ export function aiGeneratePrescriptionApi(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音转写(multipart:字段名 audio)
|
||||||
|
* @param {{ filePath: string, duration_ms?: number }} data
|
||||||
|
*/
|
||||||
|
export function aiVoiceAsrApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/ai-voice-asr',
|
||||||
|
method: 'POST',
|
||||||
|
type: 'upload',
|
||||||
|
filePath: data.filePath,
|
||||||
|
name: 'audio',
|
||||||
|
formData: {
|
||||||
|
duration_ms: String(data.duration_ms || 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 语音口述落方(只抽药名剂量) */
|
||||||
|
export function aiGenerateVoicePrescriptionApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/ai-generate-voice-prescription',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 本挂号 AI 生成历史(轻量列表) */
|
/** 本挂号 AI 生成历史(轻量列表) */
|
||||||
export function aiListGenerationsApi(data) {
|
export function aiListGenerationsApi(data) {
|
||||||
return req.request({
|
return req.request({
|
||||||
@@ -602,6 +664,15 @@ export function aiGenerationDetailApi(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 标记 AI 生成记录为已读(熄灭未读微光) */
|
||||||
|
export function aiMarkGenerationReadApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/ai-mark-generation-read',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** AI 处方药名模糊对照 */
|
/** AI 处方药名模糊对照 */
|
||||||
export function aiMatchPrescriptionDrugsApi(data) {
|
export function aiMatchPrescriptionDrugsApi(data) {
|
||||||
return req.request({
|
return req.request({
|
||||||
@@ -664,3 +735,44 @@ export function goldenFormulaSaveMatchApi(data) {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医生接诊相关:新建就诊人 / 创建记录 / 选择接诊 / 认领码
|
||||||
|
* 取值:xk-api code / result / message(unwrapXkApi)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 医生新建就诊人并自动挂号 xk-api POST /doctor-reception-wx/create-user-patient */
|
||||||
|
export function createUserPatientApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/create-user-patient',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 医生创建过的全部就诊人 xk-api GET /doctor-reception-wx/doctor-created-user-patient-list */
|
||||||
|
export function getDoctorCreatedUserPatientListApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/doctor-created-user-patient-list',
|
||||||
|
method: 'GET',
|
||||||
|
data: data || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择医生创建的就诊人开始线下接诊 xk-api POST /doctor-reception-wx/start-offline-reception-by-patient */
|
||||||
|
export function startOfflineReceptionByPatientApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/start-offline-reception-by-patient',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 就诊人认领小程序码 xk-api GET /doctor-reception-wx/user-patient-claim-qrcode */
|
||||||
|
export function getUserPatientClaimQrcodeApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/user-patient-claim-qrcode',
|
||||||
|
method: 'GET',
|
||||||
|
data: data || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ function salespersonDashboardRequest(options) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务员工作台首页看板(录入进度 + 名下诊所营业额/处方/挂号)
|
||||||
|
* xk-api GET /salesperson-dashboard/stats?range=today|week|month
|
||||||
|
*/
|
||||||
|
export function getSalespersonDashboardStats(params = {}) {
|
||||||
|
return salespersonDashboardRequest({ url: '/newApi/salesperson-dashboard/stats', method: 'GET', data: params });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务员工作台功能入口下发(后台按角色分配的宫格入口,替代前端硬编码)
|
* 业务员工作台功能入口下发(后台按角色分配的宫格入口,替代前端硬编码)
|
||||||
* xk-api GET /salesperson-dashboard/entries
|
* xk-api GET /salesperson-dashboard/entries
|
||||||
@@ -44,6 +52,14 @@ export function toggleSalespersonFavoriteEntry(code) {
|
|||||||
return salespersonDashboardRequest({ url: '/newApi/salesperson-dashboard/toggle-favorite-entry', method: 'POST', data: { code } });
|
return salespersonDashboardRequest({ url: '/newApi/salesperson-dashboard/toggle-favorite-entry', method: 'POST', data: { code } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存我的常用(功能页悬浮编辑 / 首页快捷添加)
|
||||||
|
* xk-api POST /salesperson-dashboard/batch-save-favorite-entry
|
||||||
|
*/
|
||||||
|
export function batchSaveSalespersonFavoriteEntry(codes) {
|
||||||
|
return salespersonDashboardRequest({ url: '/newApi/salesperson-dashboard/batch-save-favorite-entry', method: 'POST', data: { codes } });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
* 上报功能入口使用(点击跳转时静默调用,调用方无需处理失败)
|
||||||
* xk-api POST /salesperson-dashboard/report-entry-usage
|
* xk-api POST /salesperson-dashboard/report-entry-usage
|
||||||
|
|||||||
16
api/workbench.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* 医生工作台扩展接口(xk-api,取值用 code / result / message,配合 unwrapXkApi)
|
||||||
|
* 看板统计/预约明细等历史接口在 api/reception.js,本文件放工作台新增能力(AI 简报等)
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
req
|
||||||
|
} from '@/common/js/index.js';
|
||||||
|
|
||||||
|
/** AI 今日门诊简报 xk-api GET /doctor-reception-wx/ai-daily-brief(store_id 必传;refresh=1 强制重新生成,后端每天首次生成后按天缓存) */
|
||||||
|
export function getAiDailyBriefApi(data) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/ai-daily-brief',
|
||||||
|
method: 'GET',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import $store from '@/store/index.js';
|
import $store from '@/store/index.js';
|
||||||
import {
|
|
||||||
registerList,
|
|
||||||
} from '@/api/consult.js'
|
|
||||||
class common {
|
class common {
|
||||||
constructor(arg) {
|
constructor(arg) {
|
||||||
this.timer = null
|
this.timer = null
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import errorCode from './errorCode.js';
|
|||||||
import {Base64} from "js-base64";
|
import {Base64} from "js-base64";
|
||||||
import { getDoctorWxEnv } from '@/config/app-env.js';
|
import { getDoctorWxEnv } from '@/config/app-env.js';
|
||||||
import { resolveDoctorWxAuthToken } from '@/utils/doctorWxAuthToken.js';
|
import { resolveDoctorWxAuthToken } from '@/utils/doctorWxAuthToken.js';
|
||||||
|
import { setPublicAccountLock } from '@/utils/publicAccountLock.js'
|
||||||
|
|
||||||
const { legacyApiBase } = getDoctorWxEnv();
|
const { legacyApiBase } = getDoctorWxEnv();
|
||||||
|
|
||||||
@@ -156,6 +157,20 @@ const resInterceptor = (response, conf = {}) => {
|
|||||||
if (res == null || typeof res !== 'object') {
|
if (res == null || typeof res !== 'object') {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
// 4101 锁店:先解密联系人(会写出 phone_txt 再脱敏 phone),再落库给锁店页拨号
|
||||||
|
const bizCode = Number(res.code != null ? res.code : res.errcode)
|
||||||
|
if (bizCode === 4101) {
|
||||||
|
const payload = res.result || {}
|
||||||
|
if (payload.contact_phones) {
|
||||||
|
payload.contact_phones = getRes(payload.contact_phones, true)
|
||||||
|
}
|
||||||
|
setPublicAccountLock(payload)
|
||||||
|
return {
|
||||||
|
wakaryReqToReject: true,
|
||||||
|
msg: res.message || '公账日账单已逾期',
|
||||||
|
res: response
|
||||||
|
}
|
||||||
|
}
|
||||||
if (res.data != null) {
|
if (res.data != null) {
|
||||||
res.data = getRes(res.data)
|
res.data = getRes(res.data)
|
||||||
}
|
}
|
||||||
@@ -163,7 +178,6 @@ const resInterceptor = (response, conf = {}) => {
|
|||||||
res.result = getRes(res.result)
|
res.result = getRes(res.result)
|
||||||
}
|
}
|
||||||
// Laravel notAuth 返回 HTTP 200 + 业务码 401,需与 HTTP 401 走同一套登录失效处理
|
// Laravel notAuth 返回 HTTP 200 + 业务码 401,需与 HTTP 401 走同一套登录失效处理
|
||||||
const bizCode = Number(res.code != null ? res.code : res.errcode)
|
|
||||||
if (bizCode === 401) {
|
if (bizCode === 401) {
|
||||||
return handleAuthExpired(res.msg || res.message || '账号需重新登录', response, conf)
|
return handleAuthExpired(res.msg || res.message || '账号需重新登录', response, conf)
|
||||||
}
|
}
|
||||||
@@ -222,62 +236,68 @@ function _responseLog(res, conf = {}, describe = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解密后写出 {字段}_txt 明文,给锁店拨号等场景用;原字段仍脱敏 */
|
||||||
|
const PHONE_TXT_KEYS = ['phone', 'mobile', 'call_mobile', 'express_mobile', 'patient_mobile', 'accept_tel']
|
||||||
|
|
||||||
function getRes(obj, isDecode = true) {
|
function getRes(obj, isDecode = true) {
|
||||||
if (obj == null || typeof obj !== 'object') {
|
if (obj == null || typeof obj !== 'object') {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
for (let i = 0; i < obj.length; i++) {
|
||||||
|
if (obj[i] != null && typeof obj[i] === 'object') {
|
||||||
|
obj[i] = getRes(obj[i], isDecode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
if (Array.isArray(obj[key])) {
|
if (obj[key] != null && typeof obj[key] === 'object') {
|
||||||
obj[key] = getRes(obj[key])
|
obj[key] = getRes(obj[key], isDecode)
|
||||||
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
} else if (arr.indexOf(key) !== -1) {
|
||||||
obj[key] = getRes(obj[key])
|
if (obj[key] == null || obj[key] === '') continue
|
||||||
} else {
|
let aseFile = ''
|
||||||
// 判断obj[key]是否在arr中
|
if (isDecode === true) {
|
||||||
if (arr.indexOf(key) !== -1) {
|
aseFile = customBase64Decode(obj[key])
|
||||||
if (obj[key] == null || obj[key] === '') continue
|
} else {
|
||||||
let aseFile = ''
|
aseFile = customBase64Encode(obj[key])
|
||||||
if (isDecode === true) {
|
|
||||||
aseFile = customBase64Decode(obj[key])
|
|
||||||
} else {
|
|
||||||
aseFile = customBase64Encode(obj[key])
|
|
||||||
}
|
|
||||||
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
|
|
||||||
typeof aseFile === 'string' ? aseFile : ''
|
|
||||||
)
|
|
||||||
if (isGarbled) {
|
|
||||||
aseFile = obj[key]
|
|
||||||
}
|
|
||||||
if (isDecode === true) {
|
|
||||||
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':
|
|
||||||
case 'phone':
|
|
||||||
// 保留前三位和后四位,中间用星号代替
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
|
||||||
|
typeof aseFile === 'string' ? aseFile : ''
|
||||||
|
)
|
||||||
|
if (isGarbled) {
|
||||||
|
aseFile = obj[key]
|
||||||
|
}
|
||||||
|
// 请求必须加密,不能跳过;解密后先留明文 _txt 再脱敏
|
||||||
|
if (isDecode === true && PHONE_TXT_KEYS.indexOf(key) !== -1) {
|
||||||
|
const plain = String(aseFile || '')
|
||||||
|
if (/^1\d{10}$/.test(plain)) {
|
||||||
|
obj[key + '_txt'] = plain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isDecode === true && 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':
|
||||||
|
case 'phone':
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<view>
|
<view>
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<!-- pageGuard 与 visible 拆开:关抽屉先离场再卸,避免工作台无法滑动 -->
|
||||||
<page-container
|
<page-container
|
||||||
v-if="mode === 'switch'"
|
v-if="mode === 'switch' && pageGuard"
|
||||||
:show="visible"
|
:show="visible"
|
||||||
:overlay="false"
|
:overlay="false"
|
||||||
@leave="handlePageLeave"
|
@leave="handlePageLeave"
|
||||||
|
@afterleave="releasePageGuard"
|
||||||
/>
|
/>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<u-popup
|
<u-popup
|
||||||
@@ -63,9 +65,14 @@ export default {
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearReleaseTimer();
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
|
pageGuard: false,
|
||||||
|
releaseTimer: null,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
selected: null,
|
selected: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -104,10 +111,28 @@ export default {
|
|||||||
return list.find((item) => item.password_matched) || list[0];
|
return list.find((item) => item.password_matched) || list[0];
|
||||||
},
|
},
|
||||||
showPanel() {
|
showPanel() {
|
||||||
|
this.pageGuard = true;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
releasePageGuard() {
|
||||||
|
this.clearReleaseTimer();
|
||||||
|
this.pageGuard = false;
|
||||||
|
},
|
||||||
|
scheduleReleaseGuard() {
|
||||||
|
this.clearReleaseTimer();
|
||||||
|
this.releaseTimer = setTimeout(() => {
|
||||||
|
this.pageGuard = false;
|
||||||
|
this.releaseTimer = null;
|
||||||
|
}, 350);
|
||||||
|
},
|
||||||
|
clearReleaseTimer() {
|
||||||
|
if (this.releaseTimer) {
|
||||||
|
clearTimeout(this.releaseTimer);
|
||||||
|
this.releaseTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
open(accounts = null, options = {}) {
|
open(accounts = null, options = {}) {
|
||||||
this.defaultAccountId = options.defaultAccountId ?? null;
|
this.defaultAccountId = options.defaultAccountId ?? null;
|
||||||
this.defaultAccountType = options.defaultAccountType ?? null;
|
this.defaultAccountType = options.defaultAccountType ?? null;
|
||||||
@@ -134,6 +159,7 @@ export default {
|
|||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
this.scheduleReleaseGuard();
|
||||||
},
|
},
|
||||||
handlePageLeave() {
|
handlePageLeave() {
|
||||||
this.close();
|
this.close();
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
/** 供页面 onShow 调用:刷新微信绑定状态 */
|
/** 供页面 onShow 调用:刷新微信绑定状态 */
|
||||||
refreshWxBind() {
|
refreshWxBind() {
|
||||||
if (this.$refs.wxBindEntry) this.$refs.wxBindEntry.refresh();
|
if (this.$refs.wxBindEntry) return this.$refs.wxBindEntry.refresh();
|
||||||
},
|
},
|
||||||
/** 进入共享个人中心页(sub_business_shared 分包页面可被任意分包 navigateTo) */
|
/** 进入共享个人中心页(sub_business_shared 分包页面可被任意分包 navigateTo) */
|
||||||
goProfile() {
|
goProfile() {
|
||||||
|
|||||||
530
components/ai-brief-card/ai-brief-card.vue
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="featureEnabled">
|
||||||
|
<!-- 工作台只占一行横幅(对齐 PC 公告条),全文进抽屉,避免大卡片占屏且首弹 page-container 锁死页面滑动 -->
|
||||||
|
<view class="ai-brief-banner" @click="onBannerTap">
|
||||||
|
<view class="ai-brief-banner__badge">AI</view>
|
||||||
|
<text class="ai-brief-banner__title">今日简报</text>
|
||||||
|
<text class="ai-brief-banner__text">{{ bannerText }}</text>
|
||||||
|
<u-loading v-if="loading" mode="circle" size="22"></u-loading>
|
||||||
|
<u-icon v-else name="arrow-right" size="22" color="#6acdbb"></u-icon>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 查看全文抽屉:page-container 拦截返回键(规则:抽屉必须 page-container + v-if) -->
|
||||||
|
<!-- pageGuard 与 popupShow 拆开:先 show=false 播完离场再卸节点,避免原生滚动锁残留导致关抽屉后页面无法滑动 -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<page-container
|
||||||
|
v-if="pageGuard"
|
||||||
|
:show="popupShow"
|
||||||
|
:overlay="false"
|
||||||
|
@leave="onPageLeave"
|
||||||
|
@afterleave="releasePageGuard"
|
||||||
|
/>
|
||||||
|
<!-- #endif -->
|
||||||
|
<u-popup :value="popupShow" mode="bottom" border-radius="24" height="70%" :safe-area-inset-bottom="true" :mask-close-able="true" @input="onPopupInput" @close="closePopup">
|
||||||
|
<view class="ai-popup">
|
||||||
|
<view class="ai-popup__header">
|
||||||
|
<view class="ai-popup__title-wrap">
|
||||||
|
<view class="ai-brief-banner__badge">AI</view>
|
||||||
|
<text class="ai-popup__title">{{ popupDateText }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="ai-popup__actions">
|
||||||
|
<text class="ai-popup__refresh" :class="{ 'is-disabled': loading }" @click.stop="onRefreshTap">重新生成</text>
|
||||||
|
<text class="ai-popup__close" @click="closePopup">关闭</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y="true" class="ai-popup__scroll">
|
||||||
|
<view v-if="chips.length" class="ai-brief-card__chips ai-popup__chips">
|
||||||
|
<view v-for="chip in chips" :key="chip.label" class="ai-chip" :class="{ 'ai-chip--hl': chip.highlight }">
|
||||||
|
<text class="ai-chip__value">{{ chip.value }}</text>
|
||||||
|
<text class="ai-chip__label">{{ chip.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="ai-popup__body">
|
||||||
|
<view v-for="(section, idx) in sections" :key="idx" class="ai-popup__section">
|
||||||
|
<text v-if="section.title" class="ai-brief-card__section-tag">{{ section.title }}</text>
|
||||||
|
<text class="ai-popup__section-text">{{ section.text }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="ai-popup__meta">由 AI 生成,仅供参考{{ generatedAtText ? ' · ' + generatedAtText : '' }}</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="ai-popup__footer">
|
||||||
|
<view class="ai-popup__btn" @click="closePopup">知道了</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* AI 今日门诊简报:工作台单行横幅 + 点击打开全文抽屉
|
||||||
|
* 不做每日首弹——page-container 挂在 tab 页上会锁死整页滑动;
|
||||||
|
* 数据仍走 /doctor-reception-wx/ai-daily-brief(每天首次生成、按天缓存),「重新生成」在抽屉内
|
||||||
|
*/
|
||||||
|
import { getAiDailyBriefApi } from '@/api/workbench.js'
|
||||||
|
import { unwrapXkApi } from '@/utils/api-response.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ai-brief-card',
|
||||||
|
props: {
|
||||||
|
/** 当前门店ID:简报按 doctor+store 缓存(排班/挂号统计都是按店口径),切店需重拉 */
|
||||||
|
storeId: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
brief: null,
|
||||||
|
// 系统配置关闭时整卡不渲染,避免关开关后仍占一行
|
||||||
|
featureEnabled: true,
|
||||||
|
loading: false,
|
||||||
|
// 接口本身失败(网络/鉴权),区别于 brief.status=0 的「AI 生成失败」
|
||||||
|
fetchError: false,
|
||||||
|
popupShow: false,
|
||||||
|
// 原生 page-container 挂载开关:关闭时先关 show,afterleave/超时后再卸,避免锁死页面滑动
|
||||||
|
pageGuard: false,
|
||||||
|
releaseTimer: null,
|
||||||
|
// 去重键(storeId_日期):页面 onShow 会反复触发刷新,同店同天已拉过则跳过
|
||||||
|
lastFetchKey: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/** 生成时间展示(缓存行的更新时间) */
|
||||||
|
generatedAtText() {
|
||||||
|
return this.brief && this.brief.generated_at ? this.brief.generated_at : ''
|
||||||
|
},
|
||||||
|
/** 弹层标题:简报日期 */
|
||||||
|
popupDateText() {
|
||||||
|
const date = this.brief && this.brief.brief_date ? this.brief.brief_date : ''
|
||||||
|
return date ? date + ' 今日简报' : '今日简报'
|
||||||
|
},
|
||||||
|
/** 横幅一行摘要:成功展示首段概览,失败/加载给短提示 */
|
||||||
|
bannerText() {
|
||||||
|
if (this.loading) return '正在分析今日门诊…'
|
||||||
|
if (this.fetchError) return '加载失败,点此重试'
|
||||||
|
if (this.brief && this.brief.status !== 1) return '生成失败,点此重试'
|
||||||
|
if (this.sections.length && this.sections[0].text) return this.sections[0].text
|
||||||
|
if (this.chips.length) {
|
||||||
|
const first = this.chips[0]
|
||||||
|
return first.label + ' ' + first.value
|
||||||
|
}
|
||||||
|
return '点击查看今日简报'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 正文分段:后端约定 AI 输出「【今日概览】...【重点提醒】...【时间安排建议】...」,
|
||||||
|
* 按【标题】切段渲染更易读;AI 偶发不按格式时兜底整段展示
|
||||||
|
*/
|
||||||
|
sections() {
|
||||||
|
const content = this.unwrapBriefContent(this.brief && this.brief.content ? this.brief.content : '')
|
||||||
|
if (!content) return []
|
||||||
|
const matches = []
|
||||||
|
const reg = /【([^】]+)】([^【]*)/g
|
||||||
|
let m = reg.exec(content)
|
||||||
|
while (m) {
|
||||||
|
matches.push({ title: m[1] || '', text: (m[2] || '').trim() })
|
||||||
|
m = reg.exec(content)
|
||||||
|
}
|
||||||
|
if (matches.length === 0) return [{ title: '', text: content }]
|
||||||
|
return matches
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 快照统计芯片:读生成时落库的 data_snapshot,保证数字与 AI 文本口径一致
|
||||||
|
* (值班余号/待接诊/今日到期预约/未来7天预约)
|
||||||
|
*/
|
||||||
|
chips() {
|
||||||
|
const data = this.brief && this.brief.data ? this.brief.data : null
|
||||||
|
if (!data) return []
|
||||||
|
const today = data.today || {}
|
||||||
|
const schedule = data.schedule || {}
|
||||||
|
const waitAccept = Number(today.wait_accept) || 0
|
||||||
|
const dueWaiting = Number(today.appointment_due_waiting) || 0
|
||||||
|
// 未到就诊日的预约不当成今日待办芯片,避免角标式催办
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '今日值班',
|
||||||
|
value: schedule.is_work ? (Number(schedule.left) || 0) + '/' + (Number(schedule.total) || 0) + '余号' : '休诊',
|
||||||
|
highlight: false
|
||||||
|
},
|
||||||
|
{ label: '待接诊', value: waitAccept, highlight: waitAccept > 0 },
|
||||||
|
{ label: '今日到期预约', value: dueWaiting, highlight: dueWaiting > 0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 弹层开合同步给工作台:简报弹层在 .content(z-index:1)内,盖不住自定义 tabbar(z-index:998),
|
||||||
|
// 由页面在弹层打开时隐藏 tabbar,避免「知道了」被挡住
|
||||||
|
popupShow(val) {
|
||||||
|
this.$emit('popup-change', !!val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchBrief(false)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Agent/模型偶发把简报包成 JSON 或 ```json```,先拆成【标题】正文再分段
|
||||||
|
*/
|
||||||
|
unwrapBriefContent(raw, depth) {
|
||||||
|
let text = raw ? String(raw).trim() : ''
|
||||||
|
const level = depth || 0
|
||||||
|
if (!text || level > 4) return text
|
||||||
|
const fenceStart = text.indexOf('```')
|
||||||
|
if (fenceStart >= 0) {
|
||||||
|
const after = text.slice(fenceStart).replace(/^```(?:json)?\s*/, '')
|
||||||
|
const end = after.lastIndexOf('```')
|
||||||
|
const inner = end >= 0 ? after.slice(0, end) : after
|
||||||
|
if (inner) text = inner.trim()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const decoded = JSON.parse(text)
|
||||||
|
if (typeof decoded === 'string') return this.unwrapBriefContent(decoded, level + 1)
|
||||||
|
if (!decoded || typeof decoded !== 'object') return text
|
||||||
|
if (Array.isArray(decoded)) {
|
||||||
|
return this.flattenBriefSections(decoded) || text
|
||||||
|
}
|
||||||
|
const wrapFields = ['content', 'text', 'brief', 'summary']
|
||||||
|
for (let i = 0; i < wrapFields.length; i++) {
|
||||||
|
const val = decoded[wrapFields[i]]
|
||||||
|
if (typeof val === 'string' && val.trim()) {
|
||||||
|
return this.unwrapBriefContent(val, level + 1)
|
||||||
|
}
|
||||||
|
if (val && typeof val === 'object') {
|
||||||
|
return this.unwrapBriefContent(JSON.stringify(val), level + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(decoded.sections)) {
|
||||||
|
return this.flattenBriefSections(decoded.sections) || text
|
||||||
|
}
|
||||||
|
const parts = []
|
||||||
|
const skip = { content: 1, text: 1, status: 1, brief_status: 1, brief: 1, summary: 1 }
|
||||||
|
for (const key in decoded) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(decoded, key)) continue
|
||||||
|
const value = decoded[key]
|
||||||
|
if (typeof value !== 'string' && typeof value !== 'number') continue
|
||||||
|
if (skip[key] || !String(key).trim() || !String(value).trim()) continue
|
||||||
|
parts.push('【' + String(key).trim() + '】' + String(value).trim())
|
||||||
|
}
|
||||||
|
return parts.length ? parts.join('') : text
|
||||||
|
} catch (e) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
},
|
||||||
|
flattenBriefSections(list) {
|
||||||
|
const parts = []
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const sec = list[i]
|
||||||
|
if (typeof sec === 'string' && sec.trim()) {
|
||||||
|
parts.push(sec.trim())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const item = sec || {}
|
||||||
|
const title = String(item.title || item.name || item.label || '').trim()
|
||||||
|
const body = String(item.text || item.content || item.body || '').trim()
|
||||||
|
if (title && body) parts.push('【' + title + '】' + body)
|
||||||
|
else if (body) parts.push(body)
|
||||||
|
}
|
||||||
|
return parts.join('')
|
||||||
|
},
|
||||||
|
/** 今天日期字符串(去重键用,与后端 brief_date 同格式) */
|
||||||
|
todayStr() {
|
||||||
|
const d = new Date()
|
||||||
|
const pad = (n) => (n < 10 ? '0' + n : '' + n)
|
||||||
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 拉取简报;refresh=true 强制后端重新生成(用户点「重新生成」按钮)
|
||||||
|
* 页面 onShow / 切店都会调用本方法,靠 lastFetchKey 去重避免重复请求;
|
||||||
|
* 首次生成可能耗时十几秒(走 AI),期间维持加载态
|
||||||
|
*/
|
||||||
|
async fetchBrief(refresh) {
|
||||||
|
if (!this.storeId || this.loading) return
|
||||||
|
const fetchKey = String(this.storeId) + '_' + this.todayStr()
|
||||||
|
if (!refresh && this.brief && this.lastFetchKey === fetchKey) return
|
||||||
|
this.loading = true
|
||||||
|
this.fetchError = false
|
||||||
|
try {
|
||||||
|
const params = { store_id: this.storeId }
|
||||||
|
if (refresh) params.refresh = 1
|
||||||
|
const res = await getAiDailyBriefApi(params)
|
||||||
|
const { ok, payload } = unwrapXkApi(res)
|
||||||
|
if (ok && payload) {
|
||||||
|
// 系统配置关闭:不展示横幅、不记去重键,避免关开关后仍占位
|
||||||
|
if (payload.enabled === false) {
|
||||||
|
this.featureEnabled = false
|
||||||
|
this.brief = null
|
||||||
|
this.lastFetchKey = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.featureEnabled = true
|
||||||
|
this.brief = payload
|
||||||
|
this.lastFetchKey = fetchKey
|
||||||
|
} else {
|
||||||
|
this.fetchError = true
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.fetchError = true
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 横幅点击:成功打开抽屉,失败则重试;加载中不响应以免打断生成 */
|
||||||
|
onBannerTap() {
|
||||||
|
if (this.loading) return
|
||||||
|
if (this.fetchError) {
|
||||||
|
this.onRetryTap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.brief && this.brief.status !== 1) {
|
||||||
|
this.onRefreshTap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.brief) this.openPopup()
|
||||||
|
},
|
||||||
|
/** 重新生成(防抖:加载中不响应) */
|
||||||
|
onRefreshTap() {
|
||||||
|
if (this.loading) return
|
||||||
|
this.fetchBrief(true)
|
||||||
|
},
|
||||||
|
/** 网络失败重试(读缓存即可,不强制重生成) */
|
||||||
|
onRetryTap() {
|
||||||
|
if (this.loading) return
|
||||||
|
this.fetchBrief(false)
|
||||||
|
},
|
||||||
|
/** 点横幅打开抽屉:先挂 page-container,下一帧再 show,保证原生层能接到进入动画 */
|
||||||
|
openPopup() {
|
||||||
|
this.pageGuard = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.popupShow = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closePopup() {
|
||||||
|
this.popupShow = false
|
||||||
|
this.scheduleReleaseGuard()
|
||||||
|
},
|
||||||
|
/** u-popup 双向同步(蒙层点击关闭等场景) */
|
||||||
|
onPopupInput(val) {
|
||||||
|
this.popupShow = !!val
|
||||||
|
if (!val) this.scheduleReleaseGuard()
|
||||||
|
},
|
||||||
|
/** page-container 返回键:关抽屉,不退出工作台 */
|
||||||
|
onPageLeave() {
|
||||||
|
this.popupShow = false
|
||||||
|
this.scheduleReleaseGuard()
|
||||||
|
},
|
||||||
|
/** 离场动画结束立刻卸原生容器(比超时更干净) */
|
||||||
|
releasePageGuard() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
this.pageGuard = false
|
||||||
|
},
|
||||||
|
/** 点按钮关闭时 afterleave 可能不触发,350ms 兜底卸掉,避免滚动锁残留 */
|
||||||
|
scheduleReleaseGuard() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
this.releaseTimer = setTimeout(() => {
|
||||||
|
this.pageGuard = false
|
||||||
|
this.releaseTimer = null
|
||||||
|
}, 350)
|
||||||
|
},
|
||||||
|
clearReleaseTimer() {
|
||||||
|
if (this.releaseTimer) {
|
||||||
|
clearTimeout(this.releaseTimer)
|
||||||
|
this.releaseTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 单行横幅:1px 同色边框 + 微光,不占工作台首屏 */
|
||||||
|
.ai-brief-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(106, 205, 187, 0.3);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
box-shadow: 0 0 6px rgba(106, 205, 187, 0.18);
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
&__badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 2rpx 12rpx;
|
||||||
|
background: linear-gradient(135deg, #6acdbb, #4db6a3);
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #888;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-brief-card {
|
||||||
|
&__chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section-tag {
|
||||||
|
align-self: flex-start;
|
||||||
|
padding: 2rpx 16rpx;
|
||||||
|
background: rgba(106, 205, 187, 0.12);
|
||||||
|
border-radius: 999rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #4db6a3;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 快照统计芯片 */
|
||||||
|
.ai-chip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8rpx 18rpx;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
|
||||||
|
&--hl {
|
||||||
|
border-color: rgba(251, 140, 0, 0.45);
|
||||||
|
background: rgba(251, 140, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 650;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #909399;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全文弹层 */
|
||||||
|
.ai-popup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 28rpx 32rpx 20rpx;
|
||||||
|
border-bottom: 1px solid #f2f3f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__refresh {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
padding: 8rpx 12rpx;
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #909399;
|
||||||
|
padding: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__scroll {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
min-height: 400rpx;
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__chips {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
padding: 16rpx 0 32rpx;
|
||||||
|
border-top: 1px solid #f2f3f5;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #b0b3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
padding: 16rpx 32rpx;
|
||||||
|
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn {
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
text-align: center;
|
||||||
|
background: linear-gradient(135deg, #6acdbb, #4db6a3);
|
||||||
|
border-radius: 999rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,910 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 页面级返回拦截:防止用户按返回键直接退出页面(规则要求抽屉必须配 page-container + v-if) -->
|
||||||
|
<!-- pageGuard 与 show 拆开:先 show=false 播完离场再卸节点,避免关抽屉后页面无法滑动 -->
|
||||||
|
<!-- 两级抽屉:原生容器一次 leave 后失效,关掉子抽屉后需重挂载才能拦截下一次返回 -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<page-container
|
||||||
|
v-if="pageGuard"
|
||||||
|
:show="show"
|
||||||
|
:overlay="false"
|
||||||
|
@leave="onPageLeave"
|
||||||
|
@afterleave="releasePageGuard"
|
||||||
|
/>
|
||||||
|
<!-- #endif -->
|
||||||
|
<u-popup
|
||||||
|
:value="show"
|
||||||
|
mode="bottom"
|
||||||
|
border-radius="24"
|
||||||
|
height="75%"
|
||||||
|
:safe-area-inset-bottom="true"
|
||||||
|
:mask-close-able="true"
|
||||||
|
@input="onPopupInput"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<view class="appt-drawer">
|
||||||
|
<view class="appt-drawer__header">
|
||||||
|
<text class="appt-drawer__title">未来7天预约明细</text>
|
||||||
|
<text class="appt-drawer__close" @click="close">关闭</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 日期筛选:全部 + 7 天横向滚动 pill -->
|
||||||
|
<scroll-view scroll-x="true" class="appt-drawer__filters">
|
||||||
|
<view class="appt-drawer__filters-inner">
|
||||||
|
<view
|
||||||
|
class="appt-pill"
|
||||||
|
:class="{ 'appt-pill--active': filterDate === '' }"
|
||||||
|
@click="switchFilter('')"
|
||||||
|
>全部7天</view>
|
||||||
|
<view
|
||||||
|
v-for="day in dayList"
|
||||||
|
:key="day.date"
|
||||||
|
class="appt-pill"
|
||||||
|
:class="{ 'appt-pill--active': filterDate === day.date }"
|
||||||
|
@click="switchFilter(day.date)"
|
||||||
|
>{{ day.label }}({{ day.count }})</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 明细列表:滚动到底自动加载下一页 -->
|
||||||
|
<scroll-view scroll-y="true" class="appt-drawer__list" @scrolltolower="loadMore">
|
||||||
|
<view v-if="loading && list.length === 0" class="appt-drawer__loading">
|
||||||
|
<u-loading mode="circle" size="36"></u-loading>
|
||||||
|
</view>
|
||||||
|
<block v-else>
|
||||||
|
<view v-for="item in list" :key="item.register_id" class="appt-row">
|
||||||
|
<view class="appt-row__date">
|
||||||
|
<text class="appt-row__day">{{ item.visit_date_short }}</text>
|
||||||
|
<text class="appt-row__week">{{ item.week }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="appt-row__main">
|
||||||
|
<view class="appt-row__line1">
|
||||||
|
<text class="appt-row__patient">{{ item.patient_name || '未知就诊人' }}</text>
|
||||||
|
<text class="appt-row__slot">{{ item.slot_label }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="appt-row__line2">
|
||||||
|
<text v-if="showDoctor && item.doctor_name" class="appt-row__meta">医生:{{ item.doctor_name }}</text>
|
||||||
|
<text v-if="showStore && item.store_name" class="appt-row__meta">{{ item.store_name }}</text>
|
||||||
|
<!-- 脱敏手机号展示(拦截器已解码+脱敏),拨号用未脱敏的 call_mobile -->
|
||||||
|
<text v-if="item.patient_mobile" class="appt-row__meta">{{ item.patient_mobile }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 行操作:电话沟通 + 医生代改期(仅医生端、已支付待接诊单可改) -->
|
||||||
|
<view class="appt-row__actions" v-if="item.call_mobile || (canReschedule && item.can_reschedule)">
|
||||||
|
<view v-if="item.call_mobile" class="appt-action" @click="callPatient(item)">
|
||||||
|
<u-icon name="phone" size="24" color="#6acdbb"></u-icon>
|
||||||
|
<text class="appt-action__text">电话</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="canReschedule && item.can_reschedule" class="appt-action" @click="openReschedule(item)">
|
||||||
|
<u-icon name="calendar" size="24" color="#6acdbb"></u-icon>
|
||||||
|
<text class="appt-action__text">改期</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="appt-row__status" :class="item.status_class">{{ item.status_text }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="!loading && list.length === 0" class="appt-drawer__empty">
|
||||||
|
<u-icon name="calendar" size="60" color="#C4C7CC"></u-icon>
|
||||||
|
<text class="appt-drawer__empty-text">暂无预约</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="list.length > 0" class="appt-drawer__footer">
|
||||||
|
<u-loading v-if="loading" mode="circle" size="28"></u-loading>
|
||||||
|
<text v-else-if="finished" class="appt-drawer__footer-text">没有更多了</text>
|
||||||
|
<text v-else class="appt-drawer__footer-text" @click="loadMore">加载更多</text>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
|
||||||
|
<!-- 医生代改期子抽屉:日历日期 + 时段选择(数据来自 my-schedule-calendar),z-index 需高于明细抽屉 -->
|
||||||
|
<u-popup
|
||||||
|
v-if="role === 'doctor'"
|
||||||
|
:value="rescheduleShow"
|
||||||
|
mode="bottom"
|
||||||
|
border-radius="24"
|
||||||
|
height="72%"
|
||||||
|
:safe-area-inset-bottom="true"
|
||||||
|
:mask-close-able="true"
|
||||||
|
:z-index="10090"
|
||||||
|
@input="onReschedulePopupInput"
|
||||||
|
@close="closeReschedule"
|
||||||
|
>
|
||||||
|
<view class="resch">
|
||||||
|
<view class="appt-drawer__header">
|
||||||
|
<text class="appt-drawer__title">修改预约时间</text>
|
||||||
|
<text class="appt-drawer__close" @click="closeReschedule">关闭</text>
|
||||||
|
</view>
|
||||||
|
<view class="resch__current" v-if="rescheduleTarget">
|
||||||
|
{{ rescheduleTarget.patient_name || '就诊人' }} · 当前:{{ rescheduleTarget.visit_date }} {{ rescheduleTarget.slot_label }}
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y="true" class="resch__body">
|
||||||
|
<view v-if="calendarLoading" class="appt-drawer__loading">
|
||||||
|
<u-loading mode="circle" size="36"></u-loading>
|
||||||
|
</view>
|
||||||
|
<block v-else>
|
||||||
|
<view v-if="calendarDays.length > 0">
|
||||||
|
<scroll-view scroll-x="true" class="resch__days" :show-scrollbar="false">
|
||||||
|
<view class="resch__days-inner">
|
||||||
|
<view
|
||||||
|
v-for="(day, idx) in calendarDays"
|
||||||
|
:key="day.date"
|
||||||
|
class="resch-day"
|
||||||
|
:class="{ 'resch-day--active': idx === selectedDayIndex, 'resch-day--disabled': !day.is_work || day.is_full }"
|
||||||
|
@click="selectDay(idx)"
|
||||||
|
>
|
||||||
|
<view class="resch-day__week">{{ day.week }}</view>
|
||||||
|
<view class="resch-day__date">{{ day.date_text }}</view>
|
||||||
|
<view class="resch-day__state" :class="{ 'resch-day__state--rest': !day.is_work }">{{ day.state_text }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="resch__slots" v-if="selectedDay && selectedDay.is_work && selectedDay.slots.length > 1">
|
||||||
|
<view
|
||||||
|
v-for="slot in selectedDay.slots"
|
||||||
|
:key="slot.slot_id"
|
||||||
|
class="resch-slot"
|
||||||
|
:class="{ 'resch-slot--active': slot.slot_id === selectedSlotId, 'resch-slot--disabled': slot.is_full || slot.is_expired }"
|
||||||
|
@click="selectSlot(slot)"
|
||||||
|
>
|
||||||
|
<view class="resch-slot__name">{{ slot.slot_name }}</view>
|
||||||
|
<view class="resch-slot__time">{{ slot.start_time }}-{{ slot.end_time }}</view>
|
||||||
|
<view class="resch-slot__state">{{ slot.state_text }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="resch__tip" v-if="selectedDay && !selectedDay.is_work">该日休诊,请选择其他日期</view>
|
||||||
|
</view>
|
||||||
|
<view class="resch__tip" v-else>暂无可约号源</view>
|
||||||
|
<view class="resch__remark">
|
||||||
|
<input
|
||||||
|
class="resch__remark-input"
|
||||||
|
v-model="rescheduleRemark"
|
||||||
|
placeholder="备注(选填,如:电话已与患者确认)"
|
||||||
|
maxlength="100"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="resch__footer">
|
||||||
|
<view class="resch__selected" v-if="rescheduleSlotLabel">改期至:{{ rescheduleSlotLabel }}</view>
|
||||||
|
<view
|
||||||
|
class="resch__btn"
|
||||||
|
:class="{ 'resch__btn--disabled': !canConfirmReschedule || rescheduleSubmitting }"
|
||||||
|
@click="confirmReschedule"
|
||||||
|
>{{ rescheduleSubmitting ? '提交中...' : '确认修改' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 未来7天预约明细抽屉(医生 / 诊所管理员 / 平台管理员三端工作台共用)
|
||||||
|
*
|
||||||
|
* 为什么按 role 内部选接口而不是传函数 prop:小程序端组件传函数不稳妥,
|
||||||
|
* 且三端接口鉴权头不同(医生 JWT / _clinicAdmin / _platformAdmin),收敛在组件内按 role 分发最直观。
|
||||||
|
* 数据分页由后端 appointment-upcoming-list 提供(page/page_size/visit_date),滚动到底自动翻页。
|
||||||
|
*/
|
||||||
|
import { getAppointmentUpcomingListApi, rescheduleAppointmentApi, getMyScheduleCalendarApi } from '@/api/reception.js'
|
||||||
|
import { getClinicAdminAppointmentUpcomingList } from '@/api/clinicAdmin.js'
|
||||||
|
import { getPlatformAdminAppointmentUpcomingList } from '@/api/platformAdmin.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AppointmentUpcomingDrawer',
|
||||||
|
props: {
|
||||||
|
/** v-model 显隐 */
|
||||||
|
value: { type: Boolean, default: false },
|
||||||
|
/** 端角色:doctor 医生(隐藏医生列) / clinic 诊所管理员(隐藏门店列) / platform 平台管理员(全显示) */
|
||||||
|
role: { type: String, default: 'doctor' },
|
||||||
|
/** 7 天分布(工作台统计接口返回的 days),用于筛选 pill 展示数量 */
|
||||||
|
days: { type: Array, default: () => [] },
|
||||||
|
/** 打开时默认选中的日期(点某天卡格进入时预筛选),空=全部7天 */
|
||||||
|
defaultDate: { type: String, default: '' },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
filterDate: '',
|
||||||
|
list: [],
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
// 请求序号:快速切日期/连点加载更多时,据此丢弃晚到的过期响应,防止旧数据覆盖新列表
|
||||||
|
reqSeq: 0,
|
||||||
|
// page-container 挂载开关:关闭时先关 show,afterleave/超时后再卸
|
||||||
|
pageGuard: false,
|
||||||
|
releaseTimer: null,
|
||||||
|
// ---- 医生代改期子抽屉状态(仅 role=doctor 使用) ----
|
||||||
|
rescheduleShow: false,
|
||||||
|
rescheduleTarget: null,
|
||||||
|
rescheduleRemark: '',
|
||||||
|
rescheduleSubmitting: false,
|
||||||
|
calendarLoading: false,
|
||||||
|
// 医生本人号源日历(my-schedule-calendar),展示文案在 applyCalendar 预计算
|
||||||
|
calendarDays: [],
|
||||||
|
selectedDayIndex: 0,
|
||||||
|
selectedSlotId: -1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() { return this.value },
|
||||||
|
set(v) { this.$emit('input', v) },
|
||||||
|
},
|
||||||
|
/** 筛选 pill:父组件偶发传 null,渲染层不能对 undefined 做 v-for */
|
||||||
|
dayList() {
|
||||||
|
return Array.isArray(this.days) ? this.days : []
|
||||||
|
},
|
||||||
|
/** 是否加载完(用于底部「没有更多」提示与翻页阻断) */
|
||||||
|
finished() {
|
||||||
|
return this.list.length >= this.total
|
||||||
|
},
|
||||||
|
/** 医生端看自己的号不需要医生列 */
|
||||||
|
showDoctor() {
|
||||||
|
return this.role !== 'doctor'
|
||||||
|
},
|
||||||
|
/** 诊所管理员端固定本店不需要门店列 */
|
||||||
|
showStore() {
|
||||||
|
return this.role !== 'clinic'
|
||||||
|
},
|
||||||
|
/** 仅医生端可代患者改期(管理员端明细只读;医生不受「就诊当天不可改」限制) */
|
||||||
|
canReschedule() {
|
||||||
|
return this.role === 'doctor'
|
||||||
|
},
|
||||||
|
/** 当前选中的日期对象 */
|
||||||
|
selectedDay() {
|
||||||
|
return this.calendarDays[this.selectedDayIndex] || null
|
||||||
|
},
|
||||||
|
/** 当前选中的时段对象 */
|
||||||
|
selectedSlot() {
|
||||||
|
if (!this.selectedDay || this.selectedSlotId < 0) return null
|
||||||
|
const slots = this.selectedDay.slots || []
|
||||||
|
for (let i = 0; i < slots.length; i++) {
|
||||||
|
if (slots[i].slot_id === this.selectedSlotId) return slots[i]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
/** 改期目标文案(底部确认条) */
|
||||||
|
rescheduleSlotLabel() {
|
||||||
|
if (!this.selectedDay || !this.selectedSlot) return ''
|
||||||
|
let label = this.selectedDay.date + ' ' + (this.selectedSlot.slot_name || '')
|
||||||
|
const s = this.selectedSlot
|
||||||
|
if (s.start_time && s.end_time && !(s.start_time === '00:00' && s.end_time === '23:59')) {
|
||||||
|
label += ' ' + s.start_time + '-' + s.end_time
|
||||||
|
}
|
||||||
|
return label
|
||||||
|
},
|
||||||
|
/** 是否可提交:选中了出诊日的可约时段 */
|
||||||
|
canConfirmReschedule() {
|
||||||
|
return !!(this.selectedDay && this.selectedDay.is_work && this.selectedSlot
|
||||||
|
&& !this.selectedSlot.is_full && !this.selectedSlot.is_expired)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
/** 打开时按入口预筛选并拉第一页(每次打开都取最新数据) */
|
||||||
|
value(v) {
|
||||||
|
if (v) {
|
||||||
|
this.pageGuard = true
|
||||||
|
this.filterDate = this.defaultDate || ''
|
||||||
|
this.resetAndLoad()
|
||||||
|
} else {
|
||||||
|
this.scheduleReleaseGuard()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
releasePageGuard() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
this.pageGuard = false
|
||||||
|
},
|
||||||
|
scheduleReleaseGuard() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
this.releaseTimer = setTimeout(() => {
|
||||||
|
this.pageGuard = false
|
||||||
|
this.releaseTimer = null
|
||||||
|
}, 350)
|
||||||
|
},
|
||||||
|
clearReleaseTimer() {
|
||||||
|
if (this.releaseTimer) {
|
||||||
|
clearTimeout(this.releaseTimer)
|
||||||
|
this.releaseTimer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
onPopupInput(v) {
|
||||||
|
this.show = v
|
||||||
|
},
|
||||||
|
onPageLeave() {
|
||||||
|
// 返回手势优先关改期子抽屉,主抽屉保留(微信每页只允许一个 page-container,收口在此分发)
|
||||||
|
if (this.rescheduleShow) {
|
||||||
|
this.rescheduleShow = false
|
||||||
|
// 原生容器一次 leave 后即失效,重挂载后才能继续拦截「关主抽屉」的下一次返回
|
||||||
|
this.pageGuard = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.pageGuard = true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.close()
|
||||||
|
},
|
||||||
|
/** 切换日期筛选:回到第一页重查 */
|
||||||
|
switchFilter(date) {
|
||||||
|
if (this.filterDate === date) return
|
||||||
|
this.filterDate = date
|
||||||
|
this.resetAndLoad()
|
||||||
|
},
|
||||||
|
resetAndLoad() {
|
||||||
|
this.page = 1
|
||||||
|
this.list = []
|
||||||
|
this.total = 0
|
||||||
|
this.fetchList(1)
|
||||||
|
},
|
||||||
|
/** 滚动到底/点击加载更多:请求下一页(页码在成功回包后才提交,失败自然停留原页不跳页) */
|
||||||
|
loadMore() {
|
||||||
|
if (this.loading || this.finished) return
|
||||||
|
this.fetchList(this.page + 1)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 按端角色调用对应明细接口并统一取值
|
||||||
|
* 医生端接口返回原始 body(code/result),诊所/平台端封装返回 { ok, data }
|
||||||
|
*
|
||||||
|
* 竞态与分页防护:
|
||||||
|
* 1. 每次请求领取自增序号,回包时序号已过期(期间又发了新请求)直接丢弃,防旧响应覆盖新列表
|
||||||
|
* 2. 页码显式入参、只在成功回包后 this.page = page,失败/丢弃都不动页码,无需回滚逻辑
|
||||||
|
* @param {number} page 目标页码
|
||||||
|
*/
|
||||||
|
fetchList(page = 1) {
|
||||||
|
const seq = ++this.reqSeq
|
||||||
|
this.loading = true
|
||||||
|
const params = {
|
||||||
|
page,
|
||||||
|
page_size: this.pageSize,
|
||||||
|
visit_date: this.filterDate,
|
||||||
|
}
|
||||||
|
let task = null
|
||||||
|
if (this.role === 'clinic') {
|
||||||
|
task = getClinicAdminAppointmentUpcomingList(params).then((wrap) => {
|
||||||
|
return wrap && wrap.ok ? wrap.data : null
|
||||||
|
})
|
||||||
|
} else if (this.role === 'platform') {
|
||||||
|
task = getPlatformAdminAppointmentUpcomingList(params).then((wrap) => {
|
||||||
|
return wrap && wrap.ok ? wrap.data : null
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 医生端:xk-api 原始 body,code=0 时业务在 result
|
||||||
|
task = getAppointmentUpcomingListApi(params).then((res) => {
|
||||||
|
return res && res.code === 0 ? res.result : null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return task.then((payload) => {
|
||||||
|
if (seq !== this.reqSeq || !payload) return
|
||||||
|
const items = Array.isArray(payload.items) ? payload.items : []
|
||||||
|
const rows = items.map((item) => this.decorateItem(item))
|
||||||
|
// 成功才提交页码:list 拼接按目标页判断,第一页替换、后续页追加
|
||||||
|
this.page = page
|
||||||
|
this.list = page === 1 ? rows : this.list.concat(rows)
|
||||||
|
this.total = payload.total || 0
|
||||||
|
}).catch(() => {
|
||||||
|
// 静默失败:保留已有列表与页码,用户可再次触发加载
|
||||||
|
}).finally(() => {
|
||||||
|
// 仅最新请求负责收尾 loading,过期请求不动状态(新请求可能仍在途)
|
||||||
|
if (seq === this.reqSeq) {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 行数据装饰:把状态样式类、短日期、可改期标记先算进数据(模板禁止 :class 调函数)
|
||||||
|
*/
|
||||||
|
decorateItem(item) {
|
||||||
|
let statusClass = 'is-normal'
|
||||||
|
if (item.is_pay !== 1) {
|
||||||
|
statusClass = 'is-wait-pay'
|
||||||
|
} else if (item.status === 1) {
|
||||||
|
statusClass = 'is-wait-accept'
|
||||||
|
} else if (item.status === 2) {
|
||||||
|
statusClass = 'is-accepting'
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
status_class: statusClass,
|
||||||
|
// 列表里日期缩短为 MM-DD,完整年份对当周预约无增量信息
|
||||||
|
visit_date_short: (item.visit_date || '').slice(5),
|
||||||
|
// 仅「已支付 + 待接诊」可改期(待支付单让患者先支付或取消,已接诊单改期无意义)
|
||||||
|
can_reschedule: item.is_pay === 1 && item.status === 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 拨打就诊人电话(call_mobile 拦截器解码不脱敏,专供拨号) */
|
||||||
|
callPatient(item) {
|
||||||
|
const phone = item.call_mobile || ''
|
||||||
|
if (!phone) {
|
||||||
|
uni.showToast({ title: '暂无联系电话', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.makePhoneCall({ phoneNumber: phone })
|
||||||
|
},
|
||||||
|
/** 打开改期子抽屉并拉取医生本人号源日历(每次打开取最新余号) */
|
||||||
|
openReschedule(item) {
|
||||||
|
this.rescheduleTarget = item
|
||||||
|
this.rescheduleRemark = ''
|
||||||
|
this.rescheduleShow = true
|
||||||
|
this.calendarLoading = true
|
||||||
|
this.calendarDays = []
|
||||||
|
this.selectedSlotId = -1
|
||||||
|
getMyScheduleCalendarApi().then((res) => {
|
||||||
|
this.calendarLoading = false
|
||||||
|
if (res && res.code === 0 && res.result) {
|
||||||
|
this.applyCalendar(res.result)
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.calendarLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closeReschedule() {
|
||||||
|
this.rescheduleShow = false
|
||||||
|
},
|
||||||
|
onReschedulePopupInput(v) {
|
||||||
|
this.rescheduleShow = v
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 预处理日历:MM-DD 短日期、余号/约满/休诊文案、标记当前预约时段
|
||||||
|
* 与患者端挂号日历同一套预处理逻辑(模板禁止插值调方法,全部提前算好)
|
||||||
|
*/
|
||||||
|
applyCalendar(calendar) {
|
||||||
|
const target = this.rescheduleTarget || {}
|
||||||
|
const days = (calendar && calendar.days) || []
|
||||||
|
const processed = days.map((day) => {
|
||||||
|
const dateParts = (day.date || '').split('-')
|
||||||
|
const dateText = dateParts.length === 3 ? dateParts[1] + '-' + dateParts[2] : day.date
|
||||||
|
let stateText = '休诊'
|
||||||
|
if (day.is_work) {
|
||||||
|
stateText = day.is_full ? '约满' : '余' + day.left
|
||||||
|
}
|
||||||
|
const slots = (day.slots || []).map((slot) => {
|
||||||
|
let slotState = '余' + slot.left
|
||||||
|
if (slot.is_expired) slotState = '已过时'
|
||||||
|
else if (slot.is_full) slotState = '约满'
|
||||||
|
if (day.date === target.visit_date && slot.slot_id === target.slot_id) slotState = '当前'
|
||||||
|
return { ...slot, state_text: slotState }
|
||||||
|
})
|
||||||
|
return { ...day, date_text: dateText, state_text: stateText, slots }
|
||||||
|
})
|
||||||
|
this.calendarDays = processed
|
||||||
|
// 默认选第一个可约日期(出诊且未约满),都不可约时停在今天
|
||||||
|
let idx = 0
|
||||||
|
for (let i = 0; i < processed.length; i++) {
|
||||||
|
if (processed[i].is_work && !processed[i].is_full) {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.selectedDayIndex = idx
|
||||||
|
this.autoSelectSlot()
|
||||||
|
},
|
||||||
|
/** 切换改期日期:默认选中该日第一个可约时段 */
|
||||||
|
selectDay(idx) {
|
||||||
|
this.selectedDayIndex = idx
|
||||||
|
this.autoSelectSlot()
|
||||||
|
},
|
||||||
|
/** 选择时段:约满/已过时不可选 */
|
||||||
|
selectSlot(slot) {
|
||||||
|
if (slot.is_full || slot.is_expired) return
|
||||||
|
this.selectedSlotId = slot.slot_id
|
||||||
|
},
|
||||||
|
/** 自动选中当日第一个可约时段(全天单时段直接选中) */
|
||||||
|
autoSelectSlot() {
|
||||||
|
this.selectedSlotId = -1
|
||||||
|
const day = this.calendarDays[this.selectedDayIndex]
|
||||||
|
if (!day || !day.is_work) return
|
||||||
|
const slots = day.slots || []
|
||||||
|
for (let i = 0; i < slots.length; i++) {
|
||||||
|
if (!slots[i].is_full && !slots[i].is_expired) {
|
||||||
|
this.selectedSlotId = slots[i].slot_id
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 提交改期:成功后刷新明细列表并通知父组件刷新工作台统计 */
|
||||||
|
confirmReschedule() {
|
||||||
|
if (!this.canConfirmReschedule || this.rescheduleSubmitting || !this.rescheduleTarget) return
|
||||||
|
this.rescheduleSubmitting = true
|
||||||
|
rescheduleAppointmentApi({
|
||||||
|
register_id: this.rescheduleTarget.register_id,
|
||||||
|
visit_date: this.selectedDay.date,
|
||||||
|
slot_id: this.selectedSlotId,
|
||||||
|
remark: this.rescheduleRemark,
|
||||||
|
}).then((res) => {
|
||||||
|
this.rescheduleSubmitting = false
|
||||||
|
if (res && res.code === 0) {
|
||||||
|
this.rescheduleShow = false
|
||||||
|
uni.showToast({ title: '改期成功', icon: 'success' })
|
||||||
|
this.resetAndLoad()
|
||||||
|
// 父组件(工作台)可据此刷新预约统计卡片
|
||||||
|
this.$emit('rescheduled', res.result || null)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: (res && res.message) || '改期失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.rescheduleSubmitting = false
|
||||||
|
uni.showToast({ title: '改期失败,请稍后重试', icon: 'none' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.appt-drawer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 28rpx 32rpx;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__close {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__filters {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__filters-inner {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10rpx 24rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #6c7380;
|
||||||
|
background: #f5f6f8;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: #fff;
|
||||||
|
background: #6acdbb;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 8rpx;
|
||||||
|
border-bottom: 1rpx solid #f5f6f8;
|
||||||
|
|
||||||
|
&__date {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 108rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__week {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__line1 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__patient {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__slot {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__line2 {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #6c7380;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 6rpx 18rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
|
||||||
|
&.is-wait-pay {
|
||||||
|
color: #f97316;
|
||||||
|
background: rgba(249, 115, 22, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-wait-accept {
|
||||||
|
color: #3b82f6;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-accepting {
|
||||||
|
color: #00c853;
|
||||||
|
background: rgba(0, 200, 83, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-normal {
|
||||||
|
color: #6c7380;
|
||||||
|
background: #f5f6f8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 100rpx 0;
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-drawer__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24rpx 0 40rpx;
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行内操作按钮(电话/改期):1px 同色系边框 + 微光,不用左侧色条
|
||||||
|
.appt-row__actions {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6rpx 20rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
border: 1px solid rgba(106, 205, 187, 0.3);
|
||||||
|
box-shadow: 0 0 6px rgba(106, 205, 187, 0.18);
|
||||||
|
border-radius: 999rpx;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
margin-left: 6rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 医生代改期子抽屉
|
||||||
|
.resch {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__current {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 16rpx 32rpx 0;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #6c7380;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 16rpx 24rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__days {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__days-inner {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch-day {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 124rpx;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
&__week {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__date {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #6c7380;
|
||||||
|
margin: 6rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__state {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
|
||||||
|
&--rest {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
border-color: rgba(106, 205, 187, 0.6);
|
||||||
|
background: rgba(106, 205, 187, 0.08);
|
||||||
|
box-shadow: 0 0 6px rgba(106, 205, 187, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__slots {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch-slot {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 210rpx;
|
||||||
|
padding: 14rpx 0;
|
||||||
|
margin: 0 16rpx 16rpx 0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #6c7380;
|
||||||
|
margin: 4rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__state {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
border-color: rgba(106, 205, 187, 0.6);
|
||||||
|
background: rgba(106, 205, 187, 0.08);
|
||||||
|
box-shadow: 0 0 6px rgba(106, 205, 187, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__tip {
|
||||||
|
padding: 32rpx 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__remark {
|
||||||
|
margin: 8rpx 0 32rpx;
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 76rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1d2129;
|
||||||
|
background: #f5f6f8;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 16rpx 32rpx;
|
||||||
|
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__selected {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resch__btn {
|
||||||
|
height: 86rpx;
|
||||||
|
line-height: 86rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
background: #6acdbb;
|
||||||
|
border-radius: 64rpx;
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
185
components/claim-qrcode-modal/claim-qrcode-modal.vue
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<page-container v-if="pageGuard" :show="show" :overlay="false" @leave="close" @afterleave="releasePageGuard" />
|
||||||
|
<!-- #endif -->
|
||||||
|
<u-popup :value="show" mode="center" border-radius="24" :mask-close-able="true" @input="onPopupInput" @close="close">
|
||||||
|
<view class="claim">
|
||||||
|
<view class="claim__header">
|
||||||
|
<text class="claim__title">就诊人认领码</text>
|
||||||
|
<text class="claim__close" @click="close">关闭</text>
|
||||||
|
</view>
|
||||||
|
<view class="claim__body">
|
||||||
|
<view v-if="loading" class="claim__loading">
|
||||||
|
<u-loading mode="circle" size="40"></u-loading>
|
||||||
|
<text class="claim__loading-text">生成中...</text>
|
||||||
|
</view>
|
||||||
|
<block v-else-if="qrcode">
|
||||||
|
<image class="claim__qrcode" :src="qrcode" mode="aspectFit" show-menu-by-longpress></image>
|
||||||
|
<view class="claim__tip">{{ patientName || '就诊人' }} · 请用患者端小程序扫码认领</view>
|
||||||
|
</block>
|
||||||
|
<view v-else class="claim__error">
|
||||||
|
<text class="claim__error-text">{{ errorText || '认领码生成失败' }}</text>
|
||||||
|
<view class="claim__retry" @click="load">重试</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 就诊人认领码弹窗(医生创建未认领就诊人)
|
||||||
|
* 取值:xk-api unwrapXkApi
|
||||||
|
*/
|
||||||
|
import { getUserPatientClaimQrcodeApi } from '@/api/reception.js'
|
||||||
|
import { unwrapXkApi } from '@/utils/api-response.js'
|
||||||
|
import { base64ToLocalPath, unlinkLocalFile } from '@/utils/base64ToLocalPath.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ClaimQrcodeModal',
|
||||||
|
props: {
|
||||||
|
value: { type: Boolean, default: false },
|
||||||
|
userPatientId: { type: [Number, String], default: 0 },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
qrcode: '',
|
||||||
|
patientName: '',
|
||||||
|
errorText: '',
|
||||||
|
pageGuard: false,
|
||||||
|
releaseTimer: null,
|
||||||
|
localPath: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
this.$emit('input', v)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
if (val) {
|
||||||
|
this.pageGuard = true
|
||||||
|
this.load()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.cleanupLocal()
|
||||||
|
if (this.releaseTimer) clearTimeout(this.releaseTimer)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onPopupInput(v) {
|
||||||
|
this.show = v
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.show = false
|
||||||
|
this.cleanupLocal()
|
||||||
|
},
|
||||||
|
releasePageGuard() {
|
||||||
|
this.pageGuard = false
|
||||||
|
},
|
||||||
|
cleanupLocal() {
|
||||||
|
if (this.localPath) {
|
||||||
|
unlinkLocalFile(this.localPath)
|
||||||
|
this.localPath = ''
|
||||||
|
}
|
||||||
|
this.qrcode = ''
|
||||||
|
},
|
||||||
|
async load() {
|
||||||
|
const id = Number(this.userPatientId || 0)
|
||||||
|
if (!id) {
|
||||||
|
this.errorText = '就诊人ID无效'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
this.errorText = ''
|
||||||
|
this.cleanupLocal()
|
||||||
|
try {
|
||||||
|
const res = await getUserPatientClaimQrcodeApi({ user_patient_id: id })
|
||||||
|
const { ok, payload, message } = unwrapXkApi(res)
|
||||||
|
if (!ok) {
|
||||||
|
this.errorText = message || '生成失败'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const base64 = payload && payload.qrcode ? payload.qrcode : ''
|
||||||
|
this.patientName = (payload && payload.name) || ''
|
||||||
|
if (!base64) {
|
||||||
|
this.errorText = '二维码为空'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const path = await base64ToLocalPath(base64)
|
||||||
|
this.localPath = path
|
||||||
|
this.qrcode = path
|
||||||
|
} catch (e) {
|
||||||
|
this.errorText = '网络异常'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.claim {
|
||||||
|
width: 620rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
}
|
||||||
|
.claim__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
.claim__title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
.claim__close {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #6acdbb;
|
||||||
|
}
|
||||||
|
.claim__loading,
|
||||||
|
.claim__error {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 48rpx 0;
|
||||||
|
}
|
||||||
|
.claim__loading-text,
|
||||||
|
.claim__error-text {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.claim__qrcode {
|
||||||
|
width: 400rpx;
|
||||||
|
height: 400rpx;
|
||||||
|
}
|
||||||
|
.claim__tip {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.claim__retry {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding: 12rpx 32rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #6acdbb;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="flex-col flex-ali-center state">
|
<view class="flex-col flex-ali-center state">
|
||||||
<image src="../../static/image/sf.png"></image>
|
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/sf.png"></image>
|
||||||
<d-text text="您的认证信息已提交!" className="tips-c m-t-10 m-t-3"></d-text>
|
<d-text text="您的认证信息已提交!" className="tips-c m-t-10 m-t-3"></d-text>
|
||||||
<d-text text="我们会尽快审核,请耐心等待" className="tips-c m-t-1"></d-text>
|
<d-text text="我们会尽快审核,请耐心等待" className="tips-c m-t-1"></d-text>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -33,8 +33,8 @@
|
|||||||
type: [Object,Array],
|
type: [Object,Array],
|
||||||
default: () => {
|
default: () => {
|
||||||
return {
|
return {
|
||||||
'list': '../../static/image/list.png',
|
'list': 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/list.png',
|
||||||
'search': '../../static/image/search.png'
|
'search': 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/search.png'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<page-container
|
<page-container
|
||||||
|
|
||||||
v-if="usePageContainer && show"
|
v-if="usePageContainer && pageGuard"
|
||||||
|
|
||||||
:show="show"
|
:show="show"
|
||||||
|
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
@leave="close"
|
@leave="close"
|
||||||
|
|
||||||
|
@afterleave="releasePageGuard"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
@@ -94,6 +96,10 @@
|
|||||||
|
|
||||||
show: this.value,
|
show: this.value,
|
||||||
|
|
||||||
|
pageGuard: false,
|
||||||
|
|
||||||
|
releaseTimer: null,
|
||||||
|
|
||||||
canvas: null,
|
canvas: null,
|
||||||
|
|
||||||
loading: false
|
loading: false
|
||||||
@@ -108,6 +114,16 @@
|
|||||||
|
|
||||||
this.show = v
|
this.show = v
|
||||||
|
|
||||||
|
if (v) {
|
||||||
|
|
||||||
|
this.pageGuard = true
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this.scheduleReleaseGuard()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
show(v) {
|
show(v) {
|
||||||
@@ -134,8 +150,48 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
releasePageGuard() {
|
||||||
|
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
|
||||||
|
this.pageGuard = false
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduleReleaseGuard() {
|
||||||
|
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
|
||||||
|
this.releaseTimer = setTimeout(() => {
|
||||||
|
|
||||||
|
this.pageGuard = false
|
||||||
|
|
||||||
|
this.releaseTimer = null
|
||||||
|
|
||||||
|
}, 350)
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
clearReleaseTimer() {
|
||||||
|
|
||||||
|
if (this.releaseTimer) {
|
||||||
|
|
||||||
|
clearTimeout(this.releaseTimer)
|
||||||
|
|
||||||
|
this.releaseTimer = null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
initCanvas() {
|
initCanvas() {
|
||||||
|
|
||||||
this.canvas = new Mycanvas({
|
this.canvas = new Mycanvas({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
:maxlength="11"
|
:maxlength="11"
|
||||||
height="96"
|
height="96"
|
||||||
v-model="form.mobile"
|
v-model="form.mobile"
|
||||||
:prefixIcon="require('../../static/image/act.png')"
|
:prefixIcon="'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/act.png'"
|
||||||
borderRadius="96rpx"
|
borderRadius="96rpx"
|
||||||
prefixIconSize="40rpx"
|
prefixIconSize="40rpx"
|
||||||
placeholder="请输入手机号"
|
placeholder="请输入手机号"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
height="96"
|
height="96"
|
||||||
v-model="form.password"
|
v-model="form.password"
|
||||||
type="password"
|
type="password"
|
||||||
:prefixIcon="require('../../static/image/pwd.png')"
|
:prefixIcon="'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/pwd.png'"
|
||||||
password
|
password
|
||||||
borderRadius="96rpx"
|
borderRadius="96rpx"
|
||||||
prefixIconSize="40rpx"
|
prefixIconSize="40rpx"
|
||||||
@@ -37,12 +37,12 @@
|
|||||||
type="number"
|
type="number"
|
||||||
:maxlength="6"
|
:maxlength="6"
|
||||||
placeholder="请填写验证码"
|
placeholder="请填写验证码"
|
||||||
:prefixIcon="require('../../static/image/pwd.png')"
|
:prefixIcon="'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/pwd.png'"
|
||||||
:placeholder-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
|
:placeholder-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
|
||||||
:custom-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
|
:custom-style="{ fontSize: '32rpx', paddingLeft: '60rpx' }"
|
||||||
>
|
>
|
||||||
<template slot="icon">
|
<template slot="icon">
|
||||||
<!-- <image src="../../../../static/image/pwd.png"></image>-->
|
<!-- <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/pwd.png"></image>-->
|
||||||
<u-icon
|
<u-icon
|
||||||
size="40"
|
size="40"
|
||||||
name="chat"
|
name="chat"
|
||||||
|
|||||||
282
components/proxy-pay-qrcode-modal/proxy-pay-qrcode-modal.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 页面级返回拦截:pageGuard 与 show 拆开,关弹窗后先离场再卸,避免锁死页面滑动 -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<page-container
|
||||||
|
v-if="pageGuard"
|
||||||
|
:show="show"
|
||||||
|
:overlay="false"
|
||||||
|
@leave="close"
|
||||||
|
@afterleave="releasePageGuard"
|
||||||
|
/>
|
||||||
|
<!-- #endif -->
|
||||||
|
<u-popup :value="show" mode="center" border-radius="24" :mask-close-able="true" @input="onPopupInput" @close="close">
|
||||||
|
<view class="ppay">
|
||||||
|
<view class="ppay__header">
|
||||||
|
<text class="ppay__title">患者订单代付码</text>
|
||||||
|
<text class="ppay__close" @click="close">关闭</text>
|
||||||
|
</view>
|
||||||
|
<view class="ppay__body">
|
||||||
|
<view v-if="loading" class="ppay__loading">
|
||||||
|
<u-loading mode="circle" size="40"></u-loading>
|
||||||
|
<text class="ppay__loading-text">代付码生成中...</text>
|
||||||
|
</view>
|
||||||
|
<block v-else-if="qrcode">
|
||||||
|
<!-- src 是本地临时文件:小程序 <image> 不能稳渲染 data URI,长按仍可转发给患者 -->
|
||||||
|
<image class="ppay__qrcode" :src="qrcode" mode="aspectFit" show-menu-by-longpress></image>
|
||||||
|
<view class="ppay__meta">
|
||||||
|
<view class="ppay__meta-row">
|
||||||
|
<text class="ppay__meta-label">订单号</text>
|
||||||
|
<text class="ppay__meta-value">{{ orderNo }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="ppay__meta-row">
|
||||||
|
<text class="ppay__meta-label">待支付金额</text>
|
||||||
|
<text class="ppay__meta-value ppay__meta-value--price">¥{{ totalPayPrice }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="ppay__tip">请让患者用微信「扫一扫」或者长按图片识别二维码打开「惠佑康」小程序,进入小程序完成支付</view>
|
||||||
|
</block>
|
||||||
|
<view v-else class="ppay__error">
|
||||||
|
<text class="ppay__error-text">{{ errorText || '代付码生成失败' }}</text>
|
||||||
|
<view class="ppay__retry" @click="load">重试</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 医生端「患者代付码」弹窗
|
||||||
|
*
|
||||||
|
* 用途:本次挂号处方存在待支付订单时,医生出示小程序码给患者扫码代付
|
||||||
|
* (复用 PC 接诊台同款后端 proxy-pay-qrcode,scene=ppay={orderId},患者端解析链路零改动)。
|
||||||
|
* 展示:接口返回 data URI,写入 USER_DATA_PATH 再绑 <image>,不上传 OSS。
|
||||||
|
* 必须放主包 components/:easycom 会把 <proxy-pay-qrcode-modal> 解析成 /components/proxy-pay-qrcode-modal,
|
||||||
|
* 源码若只在分包,发行产物对不上会报 ENOENT(找不到 wxml/wxss)。lazyCodeLoading 按页注入,不常驻主包。
|
||||||
|
* 读值:xk-api 形态,拦截器已返回 body → code / result / message(unwrapXkApi)
|
||||||
|
*/
|
||||||
|
import { getProxyPayQrcodeApi } from '@/api/reception.js'
|
||||||
|
import { unwrapXkApi } from '@/utils/api-response.js'
|
||||||
|
import { base64ToLocalPath, unlinkLocalFile } from '@/utils/base64ToLocalPath.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProxyPayQrcodeModal',
|
||||||
|
props: {
|
||||||
|
/** v-model 显隐 */
|
||||||
|
value: { type: Boolean, default: false },
|
||||||
|
/** 待支付订单ID(处方关联的商品订单) */
|
||||||
|
orderId: { type: [Number, String], default: 0 },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
qrcode: '',
|
||||||
|
orderNo: '',
|
||||||
|
totalPayPrice: '',
|
||||||
|
errorText: '',
|
||||||
|
pageGuard: false,
|
||||||
|
releaseTimer: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() { return this.value },
|
||||||
|
set(v) { this.$emit('input', v) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
/** 每次打开都重新生成:订单可能已被支付/取消,后端会按最新状态校验 */
|
||||||
|
value(v) {
|
||||||
|
if (v) {
|
||||||
|
this.pageGuard = true
|
||||||
|
this.load()
|
||||||
|
} else {
|
||||||
|
this.clearLocalQrcode()
|
||||||
|
this.scheduleReleaseGuard()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
this.clearLocalQrcode()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
releasePageGuard() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
this.pageGuard = false
|
||||||
|
},
|
||||||
|
scheduleReleaseGuard() {
|
||||||
|
this.clearReleaseTimer()
|
||||||
|
this.releaseTimer = setTimeout(() => {
|
||||||
|
this.pageGuard = false
|
||||||
|
this.releaseTimer = null
|
||||||
|
}, 350)
|
||||||
|
},
|
||||||
|
clearReleaseTimer() {
|
||||||
|
if (this.releaseTimer) {
|
||||||
|
clearTimeout(this.releaseTimer)
|
||||||
|
this.releaseTimer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
onPopupInput(v) {
|
||||||
|
this.show = v
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 清掉上一张本地码,避免 USER_DATA_PATH 堆积
|
||||||
|
* 为什么先置空再 unlink:避免删除过程中 <image> 仍指向已删文件
|
||||||
|
*/
|
||||||
|
async clearLocalQrcode() {
|
||||||
|
const path = this.qrcode
|
||||||
|
this.qrcode = ''
|
||||||
|
await unlinkLocalFile(path)
|
||||||
|
},
|
||||||
|
/** 请求代付码:接口仍返回 base64,写入本地后再绑 <image>(不上传 OSS) */
|
||||||
|
async load() {
|
||||||
|
if (!this.orderId) {
|
||||||
|
await this.clearLocalQrcode()
|
||||||
|
this.errorText = '缺少订单信息'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
this.errorText = ''
|
||||||
|
await this.clearLocalQrcode()
|
||||||
|
try {
|
||||||
|
const res = await getProxyPayQrcodeApi({ order_id: this.orderId })
|
||||||
|
const { ok, payload, message } = unwrapXkApi(res)
|
||||||
|
if (ok && payload && payload.qrcode) {
|
||||||
|
this.qrcode = await base64ToLocalPath(payload.qrcode, 'ppay')
|
||||||
|
this.orderNo = payload.order_no || ''
|
||||||
|
this.totalPayPrice = payload.total_pay_price || ''
|
||||||
|
} else {
|
||||||
|
this.errorText = message || '代付码生成失败'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.errorText = (e && e.message) || '代付码生成失败'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ppay {
|
||||||
|
width: 600rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2a2e35;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #8a92a3;
|
||||||
|
padding: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 60rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading-text {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #8a92a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__qrcode {
|
||||||
|
width: 400rpx;
|
||||||
|
height: 400rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 订单信息卡:1px 同色系边框 + 微光(规范禁止左侧竖色条强调) */
|
||||||
|
&__meta {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid rgba(0, 168, 138, 0.3);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
box-shadow: 0 0 6px rgba(0, 168, 138, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #8a92a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta-value {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #2a2e35;
|
||||||
|
|
||||||
|
&--price {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fa3534;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tip {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #8a92a3;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #fa3534;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__retry {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
padding: 12rpx 48rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #00a88a;
|
||||||
|
border: 1px solid rgba(0, 168, 138, 0.5);
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 公账逾期条幅:门店逾期即可展示,点击进锁店页(硬锁 reLaunch / 软进 soft) -->
|
||||||
|
<view v-if="visible" class="pap-banner" :class="toneClass" @click="onTap">
|
||||||
|
<view class="pap-banner-main">
|
||||||
|
<text class="pap-banner-title">公账日账单已逾期</text>
|
||||||
|
<text class="pap-banner-desc">{{ descTxt }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="pap-banner-action">去处理</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 工作台顶部公账逾期条幅
|
||||||
|
* 用 lock-status 现算;store_locked 即显示,不要求当前身份硬锁
|
||||||
|
*/
|
||||||
|
import { getPublicAccountLockStatusApi } from '@/api/publicAccountPay.js'
|
||||||
|
import { unwrapXkApi } from '@/utils/api-response.js'
|
||||||
|
import {
|
||||||
|
isPublicAccountStoreLocked,
|
||||||
|
openPublicAccountLockFromBanner,
|
||||||
|
} from '@/utils/publicAccountLock.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PublicAccountLockBanner',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
payload: null,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
visible() {
|
||||||
|
return isPublicAccountStoreLocked(this.payload)
|
||||||
|
},
|
||||||
|
amountTxt() {
|
||||||
|
const p = this.payload || {}
|
||||||
|
return p.platform_amount_total || p.platform_amount || '0.00'
|
||||||
|
},
|
||||||
|
billCount() {
|
||||||
|
const n = Number((this.payload && this.payload.bill_count) || 0)
|
||||||
|
return n > 0 ? Math.floor(n) : 0
|
||||||
|
},
|
||||||
|
descTxt() {
|
||||||
|
const range = String((this.payload && this.payload.bill_date_range_txt) || '').trim()
|
||||||
|
if (this.billCount > 1) {
|
||||||
|
return '合并 ' + this.billCount + ' 张(' + (range || '多日') + '),合计 ¥' + this.amountTxt + ',点此处理'
|
||||||
|
}
|
||||||
|
return '应付款 ¥' + this.amountTxt + ',点此处理'
|
||||||
|
},
|
||||||
|
toneClass() {
|
||||||
|
const color = (this.payload && this.payload.alert_color) || 'yellow'
|
||||||
|
if (color === 'blue' || color === 'red') {
|
||||||
|
return 'is-' + color
|
||||||
|
}
|
||||||
|
return 'is-yellow'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 工作台 onShow 调用:拉现算锁态,决定是否展示条幅
|
||||||
|
*/
|
||||||
|
async refresh() {
|
||||||
|
if (this.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const res = await getPublicAccountLockStatusApi()
|
||||||
|
const wrap = unwrapXkApi(res)
|
||||||
|
if (!wrap.ok) {
|
||||||
|
this.payload = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.payload = wrap.payload || null
|
||||||
|
} catch (e) {
|
||||||
|
this.payload = null
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap() {
|
||||||
|
if (!this.payload) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
openPublicAccountLockFromBanner(this.payload)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.pap-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin: 16rpx 24rpx 0;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 1px solid rgba(217, 119, 6, 0.35);
|
||||||
|
box-shadow: 0 0 12rpx rgba(217, 119, 6, 0.16);
|
||||||
|
background: rgba(217, 119, 6, 0.12);
|
||||||
|
}
|
||||||
|
.pap-banner.is-blue {
|
||||||
|
border-color: rgba(37, 99, 235, 0.35);
|
||||||
|
box-shadow: 0 0 12rpx rgba(37, 99, 235, 0.16);
|
||||||
|
background: rgba(37, 99, 235, 0.12);
|
||||||
|
}
|
||||||
|
.pap-banner.is-red {
|
||||||
|
border-color: rgba(220, 38, 38, 0.35);
|
||||||
|
box-shadow: 0 0 12rpx rgba(220, 38, 38, 0.16);
|
||||||
|
background: rgba(220, 38, 38, 0.12);
|
||||||
|
}
|
||||||
|
.pap-banner-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.pap-banner-title {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
.pap-banner.is-blue .pap-banner-title {
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
.pap-banner.is-red .pap-banner-title {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
.pap-banner-desc {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
.pap-banner-action {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
.pap-banner.is-blue .pap-banner-action {
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
.pap-banner.is-red .pap-banner-action {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<!--
|
<!--
|
||||||
工作台卡片样式入口行(四个业务角色「我的」页共用,放 setting-panel 内)
|
工作台卡片样式入口:与「个人中心 / 绑定微信」同一行规格(单行标题 + 右侧当前值 + 箭头)
|
||||||
由旧版 u-switch 开关改为导航行:显示当前样式文案 + chevron,点击进入图形化单选页
|
不写副标题,避免行高和信息密度跟菜单卡其它项不一致
|
||||||
-->
|
-->
|
||||||
<view class="switch-row" hover-class="row-hover" @tap="goStylePage">
|
<view class="entry-row" hover-class="entry-row-hover" :hover-stay-time="100" @tap="goStylePage">
|
||||||
<view class="pref-text">
|
<text class="entry-label">工作台卡片样式</text>
|
||||||
<text class="switch-label">工作台卡片样式</text>
|
<view class="entry-right">
|
||||||
<text class="pref-desc">长卡片 / 宫格两种形态可选</text>
|
<text class="entry-value">{{ styleLabel }}</text>
|
||||||
</view>
|
<u-icon name="arrow-right" color="#C9CDD4" size="28"></u-icon>
|
||||||
<view class="pref-value">
|
|
||||||
<text class="value-text">{{ styleLabel }}</text>
|
|
||||||
<u-icon name="arrow-right" size="24" color="#C0C4CC"></u-icon>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,9 +15,7 @@
|
|||||||
<script>
|
<script>
|
||||||
/**
|
/**
|
||||||
* card-pref-entry 工作台卡片样式入口
|
* card-pref-entry 工作台卡片样式入口
|
||||||
* 为什么做成组件:四个业务端「我的」页都需要同一入口,抽组件避免复制粘贴;
|
* 四个业务端「我的」页共用;偏好存本地 wb_card_style,选择页写入后 uni.$emit 同步文案
|
||||||
* 偏好存本地 storage(wb_card_style),选择页写入后 uni.$emit 广播,本组件监听同步文案,
|
|
||||||
* 各工作台首页 onShow 时读取生效
|
|
||||||
*/
|
*/
|
||||||
import { getCardStyle } from '@/utils/workbenchCardPref.js';
|
import { getCardStyle } from '@/utils/workbenchCardPref.js';
|
||||||
|
|
||||||
@@ -39,7 +34,6 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.style = getCardStyle();
|
this.style = getCardStyle();
|
||||||
// 选择页切换后广播同步(组件拿不到页面 onShow,用事件总线保证返回时文案已更新)
|
|
||||||
uni.$on('wbCardStyleChanged', this.onStyleChanged);
|
uni.$on('wbCardStyleChanged', this.onStyleChanged);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@@ -49,7 +43,7 @@ export default {
|
|||||||
onStyleChanged(style) {
|
onStyleChanged(style) {
|
||||||
this.style = style === 'long' ? 'long' : 'short';
|
this.style = style === 'long' ? 'long' : 'short';
|
||||||
},
|
},
|
||||||
/** 进入图形化单选页(共享分包页面,四端均可 navigateTo) */
|
/** 进入图形化单选页(共享分包,四端均可 navigateTo) */
|
||||||
goStylePage() {
|
goStylePage() {
|
||||||
uni.navigateTo({ url: '/subPackages/sub_business_shared/card-style/index' });
|
uni.navigateTo({ url: '/subPackages/sub_business_shared/card-style/index' });
|
||||||
},
|
},
|
||||||
@@ -58,40 +52,25 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.switch-row {
|
.entry-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 36rpx 0;
|
padding: 36rpx 0;
|
||||||
border-bottom: 1rpx solid #f2f3f5;
|
border-bottom: 1rpx solid #f2f3f5;
|
||||||
}
|
}
|
||||||
|
.entry-row-hover {
|
||||||
.row-hover {
|
background: #f7f8fa;
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
.entry-label {
|
||||||
.pref-text {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-label {
|
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
color: #31353d;
|
color: #31353d;
|
||||||
}
|
}
|
||||||
|
.entry-right {
|
||||||
.pref-desc {
|
|
||||||
margin-top: 8rpx;
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #86909c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pref-value {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.entry-value {
|
||||||
.value-text {
|
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #86909c;
|
color: #86909c;
|
||||||
margin-right: 8rpx;
|
margin-right: 8rpx;
|
||||||
|
|||||||
374
components/workbench-fav-editor/workbench-fav-editor.vue
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 编辑「我的常用」抽屉:page-container 拦截返回键,v-if 卸节点避免滚动锁 -->
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<page-container
|
||||||
|
v-if="pageGuard"
|
||||||
|
:show="popupShow"
|
||||||
|
:overlay="false"
|
||||||
|
@leave="onPageLeave"
|
||||||
|
@afterleave="releasePageGuard"
|
||||||
|
/>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- z-index 高于自定义 tabbar(998) / 工作台看板浮卡,避免编辑抽屉被统计卡盖住 -->
|
||||||
|
<u-popup
|
||||||
|
:value="popupShow"
|
||||||
|
mode="bottom"
|
||||||
|
border-radius="24"
|
||||||
|
height="80%"
|
||||||
|
:z-index="10080"
|
||||||
|
:safe-area-inset-bottom="true"
|
||||||
|
:mask-close-able="true"
|
||||||
|
@input="onPopupInput"
|
||||||
|
@close="closePopup"
|
||||||
|
>
|
||||||
|
<view class="wfe">
|
||||||
|
<view class="wfe-header">
|
||||||
|
<text class="wfe-title">编辑常用功能</text>
|
||||||
|
<text class="wfe-sub">已添加显示减号,未添加显示加号,点完成一次性保存</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="wfe-scroll">
|
||||||
|
<view v-if="loading" class="wfe-center">
|
||||||
|
<text class="wfe-center-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="groups.length === 0" class="wfe-center">
|
||||||
|
<text class="wfe-center-text">暂无可用功能</text>
|
||||||
|
</view>
|
||||||
|
<view v-else>
|
||||||
|
<view v-for="group in groups" :key="group.key" class="wfe-group">
|
||||||
|
<text class="wfe-group-title">{{ group.title }}</text>
|
||||||
|
<view class="wfe-list">
|
||||||
|
<view
|
||||||
|
v-for="item in group.items"
|
||||||
|
:key="item.key"
|
||||||
|
class="wfe-row"
|
||||||
|
hover-class="wfe-row-hover"
|
||||||
|
@tap="toggleItem(item)"
|
||||||
|
>
|
||||||
|
<view class="wfe-icon" :style="item.iconStyle">
|
||||||
|
<u-icon :name="item.icon" :color="item.iconColor" size="36"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="wfe-meta">
|
||||||
|
<text class="wfe-name">{{ item.label }}</text>
|
||||||
|
<text v-if="item.desc" class="wfe-desc">{{ item.desc }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="wfe-op" :class="{ 'is-on': item.selected, 'is-off': !item.selected }">
|
||||||
|
<u-icon v-if="item.selected" name="minus" color="#EF4444" size="28"></u-icon>
|
||||||
|
<u-icon v-else name="plus" color="#6ACDBB" size="28"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="wfe-footer">
|
||||||
|
<view class="wfe-cancel" hover-class="wfe-btn-hover" @tap="closePopup">
|
||||||
|
<text>取消</text>
|
||||||
|
</view>
|
||||||
|
<view class="wfe-ok" hover-class="wfe-btn-hover" @tap="submit">
|
||||||
|
<text>{{ saving ? '保存中...' : '完成' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 工作台「我的常用」批量编辑抽屉(功能页悬浮按钮 / 首页快捷添加共用)
|
||||||
|
* 列出当前角色全部入口,已添加减号、未添加加号,点完成调 batchSaveFavorites
|
||||||
|
* 抽屉必须 page-container + v-if(规则),关抽屉先卸 show 再卸节点避免滚动锁
|
||||||
|
*/
|
||||||
|
import { groupEntriesToSections } from '@/utils/workbenchEntries.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorkbenchFavEditor',
|
||||||
|
props: {
|
||||||
|
/** { getEntries, getUserData, batchSaveFavorites },统一返回 { res, data, ok } */
|
||||||
|
api: { type: Object, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
popupShow: false,
|
||||||
|
pageGuard: false,
|
||||||
|
releaseTimer: null,
|
||||||
|
loading: false,
|
||||||
|
saving: false,
|
||||||
|
groups: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearReleaseTimer();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 打开抽屉并拉取全部入口 + 当前常用,供加减回显 */
|
||||||
|
open() {
|
||||||
|
this.pageGuard = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.popupShow = true;
|
||||||
|
this.loadData();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
closePopup() {
|
||||||
|
this.popupShow = false;
|
||||||
|
this.scheduleReleaseGuard();
|
||||||
|
},
|
||||||
|
onPopupInput(val) {
|
||||||
|
this.popupShow = !!val;
|
||||||
|
if (!val) this.scheduleReleaseGuard();
|
||||||
|
},
|
||||||
|
onPageLeave() {
|
||||||
|
this.popupShow = false;
|
||||||
|
this.scheduleReleaseGuard();
|
||||||
|
},
|
||||||
|
releasePageGuard() {
|
||||||
|
this.clearReleaseTimer();
|
||||||
|
this.pageGuard = false;
|
||||||
|
},
|
||||||
|
scheduleReleaseGuard() {
|
||||||
|
this.clearReleaseTimer();
|
||||||
|
this.releaseTimer = setTimeout(() => {
|
||||||
|
this.pageGuard = false;
|
||||||
|
this.releaseTimer = null;
|
||||||
|
}, 350);
|
||||||
|
},
|
||||||
|
clearReleaseTimer() {
|
||||||
|
if (this.releaseTimer) {
|
||||||
|
clearTimeout(this.releaseTimer);
|
||||||
|
this.releaseTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 拉入口字典 + 已收藏 code,组装分组列表(selected 存在 data 里,避免模板调方法)
|
||||||
|
*/
|
||||||
|
loadData() {
|
||||||
|
if (!this.api || !this.api.getEntries) return;
|
||||||
|
this.loading = true;
|
||||||
|
const entriesP = this.api.getEntries();
|
||||||
|
const userP = this.api.getUserData ? this.api.getUserData() : Promise.resolve({ ok: true, data: { favorites: [] } });
|
||||||
|
Promise.all([entriesP, userP]).then(([entryWrap, userWrap]) => {
|
||||||
|
const entries = entryWrap && entryWrap.ok && Array.isArray(entryWrap.data) ? entryWrap.data : [];
|
||||||
|
const favs = userWrap && userWrap.ok && userWrap.data && Array.isArray(userWrap.data.favorites)
|
||||||
|
? userWrap.data.favorites
|
||||||
|
: [];
|
||||||
|
const favCodes = {};
|
||||||
|
favs.forEach((row) => {
|
||||||
|
if (row && row.code) favCodes[row.code] = true;
|
||||||
|
});
|
||||||
|
const sections = groupEntriesToSections(entries);
|
||||||
|
this.groups = sections.map((section) => {
|
||||||
|
const items = [];
|
||||||
|
section.items.forEach((item) => {
|
||||||
|
items.push(this.decorateItem(item, !!favCodes[item.key]));
|
||||||
|
});
|
||||||
|
return { key: section.key, title: section.title, items };
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
this.groups = [];
|
||||||
|
uni.showToast({ title: '功能列表加载失败', icon: 'none' });
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 给渲染项补 iconStyle / selected,模板禁止方法调用绑 class/style */
|
||||||
|
decorateItem(item, selected) {
|
||||||
|
const theme = item.theme || {};
|
||||||
|
return {
|
||||||
|
key: item.key,
|
||||||
|
label: item.label,
|
||||||
|
desc: item.desc,
|
||||||
|
icon: item.icon,
|
||||||
|
iconColor: theme.color || '#6B7280',
|
||||||
|
iconStyle: theme.bg ? ('background:' + theme.bg) : 'background:#F3F4F6',
|
||||||
|
selected: !!selected,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/** 本地加减,点完成才提交,支持一次勾选多个 */
|
||||||
|
toggleItem(item) {
|
||||||
|
item.selected = !item.selected;
|
||||||
|
},
|
||||||
|
/** 把当前勾选的 code 一次性提交,成功后通知父页刷新常用 */
|
||||||
|
submit() {
|
||||||
|
if (this.saving) return;
|
||||||
|
if (!this.api || !this.api.batchSaveFavorites) {
|
||||||
|
uni.showToast({ title: '暂不支持批量保存', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const codes = [];
|
||||||
|
this.groups.forEach((group) => {
|
||||||
|
group.items.forEach((item) => {
|
||||||
|
if (item.selected) codes.push(item.key);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.saving = true;
|
||||||
|
this.api.batchSaveFavorites(codes).then(({ res, ok }) => {
|
||||||
|
if (ok) {
|
||||||
|
uni.showToast({ title: '已更新常用', icon: 'none' });
|
||||||
|
this.closePopup();
|
||||||
|
this.$emit('saved');
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: (res && res.message) || '保存失败,请稍后重试', icon: 'none' });
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||||
|
}).finally(() => {
|
||||||
|
this.saving = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wfe {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
.wfe-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 28rpx 32rpx 16rpx;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-bottom: 1rpx solid rgba(60, 60, 67, 0.06);
|
||||||
|
}
|
||||||
|
.wfe-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1E293B;
|
||||||
|
}
|
||||||
|
.wfe-sub {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #8E8E93;
|
||||||
|
}
|
||||||
|
.wfe-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 8rpx 24rpx 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.wfe-group {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
.wfe-group-title {
|
||||||
|
display: block;
|
||||||
|
padding: 8rpx 8rpx 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1E293B;
|
||||||
|
}
|
||||||
|
.wfe-list {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
border: 1rpx solid rgba(106, 205, 187, 0.3);
|
||||||
|
box-shadow: 0 0 12rpx rgba(106, 205, 187, 0.18);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.wfe-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
border-bottom: 1rpx solid rgba(60, 60, 67, 0.06);
|
||||||
|
}
|
||||||
|
.wfe-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.wfe-row-hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
.wfe-icon {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.wfe-meta {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0 20rpx;
|
||||||
|
}
|
||||||
|
.wfe-name {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1E293B;
|
||||||
|
}
|
||||||
|
.wfe-desc {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #8E8E93;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.wfe-op {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.wfe-op.is-on {
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
border: 1rpx solid rgba(239, 68, 68, 0.3);
|
||||||
|
box-shadow: 0 0 10rpx rgba(239, 68, 68, 0.16);
|
||||||
|
}
|
||||||
|
.wfe-op.is-off {
|
||||||
|
background: rgba(106, 205, 187, 0.12);
|
||||||
|
border: 1rpx solid rgba(106, 205, 187, 0.3);
|
||||||
|
box-shadow: 0 0 10rpx rgba(106, 205, 187, 0.16);
|
||||||
|
}
|
||||||
|
.wfe-center {
|
||||||
|
padding: 80rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.wfe-center-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #8E8E93;
|
||||||
|
}
|
||||||
|
.wfe-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 16rpx 24rpx 24rpx;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-top: 1rpx solid rgba(60, 60, 67, 0.06);
|
||||||
|
}
|
||||||
|
.wfe-cancel,
|
||||||
|
.wfe-ok {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.wfe-cancel {
|
||||||
|
background: #F3F4F6;
|
||||||
|
text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #64748B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wfe-ok {
|
||||||
|
background: #6ACDBB;
|
||||||
|
text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wfe-btn-hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<!--
|
<!--
|
||||||
功能中心页共享组件(诊所管理员/平台管理员/业务员/诊所推广员四端「功能」tab 复用)
|
功能中心页共享组件(诊所管理员/平台管理员/业务员/诊所推广员四端「功能」tab 复用)
|
||||||
结构:顶部搜索框 + u-tabs/swiper 联动(常用 | 全部 | 各分组),金刚区宫格展示入口
|
结构:顶部搜索框 + u-tabs/swiper 联动(全部 | 各分组),金刚区宫格展示入口
|
||||||
交互:点击入口跳转并静默上报使用;长按入口收藏/取消收藏到「我的常用」;搜索本地过滤
|
常用/最近只在首页,本页不再重复;长按仍可收藏到首页「我的常用」
|
||||||
放主包 components/ 原因:四端页面分属不同分包,微信不允许跨分包引用组件(同 xk-service-card)
|
放主包 components/ 原因:四端页面分属不同分包,微信不允许跨分包引用组件(同 xk-service-card)
|
||||||
-->
|
-->
|
||||||
<view class="wf-wrap" :style="{ height: wrapHeight }">
|
<view class="wf-wrap" :style="{ height: wrapHeight }">
|
||||||
@@ -33,7 +33,14 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 搜索态:覆盖 tabs/swiper,展示扁平匹配结果 -->
|
<!-- 搜索态:覆盖 tabs/swiper,展示扁平匹配结果 -->
|
||||||
<scroll-view v-else-if="isSearching" scroll-y class="wf-search-scroll">
|
<scroll-view
|
||||||
|
v-else-if="isSearching"
|
||||||
|
scroll-y
|
||||||
|
class="wf-search-scroll"
|
||||||
|
refresher-enabled
|
||||||
|
:refresher-triggered="isRefreshing"
|
||||||
|
@refresherrefresh="onRefresherRefresh"
|
||||||
|
>
|
||||||
<view class="wf-page">
|
<view class="wf-page">
|
||||||
<view class="wf-panel" v-if="searchItems.length">
|
<view class="wf-panel" v-if="searchItems.length">
|
||||||
<view class="wf-grid">
|
<view class="wf-grid">
|
||||||
@@ -65,40 +72,15 @@
|
|||||||
</view>
|
</view>
|
||||||
<swiper class="wf-swiper" :current="current" @change="onSwiperChange">
|
<swiper class="wf-swiper" :current="current" @change="onSwiperChange">
|
||||||
<swiper-item v-for="(page, pi) in swiperPages" :key="pi">
|
<swiper-item v-for="(page, pi) in swiperPages" :key="pi">
|
||||||
<scroll-view scroll-y class="wf-scroll">
|
<scroll-view
|
||||||
<!-- 常用 tab:我的常用 + 最近使用两个分区 -->
|
scroll-y
|
||||||
<view v-if="page.type === 'fav'" class="wf-page">
|
class="wf-scroll"
|
||||||
<view class="wf-group-title">我的常用</view>
|
refresher-enabled
|
||||||
<view class="wf-panel" v-if="favItems.length">
|
:refresher-triggered="isRefreshing"
|
||||||
<view class="wf-grid">
|
@refresherrefresh="onRefresherRefresh"
|
||||||
<xk-service-card
|
>
|
||||||
v-for="item in favItems" :key="item.key"
|
|
||||||
mode="grid"
|
|
||||||
:icon="item.icon" :label="item.label" :theme="item.theme"
|
|
||||||
@click="onItemTap(item)" @longpress="onItemLongpress(item)"
|
|
||||||
></xk-service-card>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="wf-hint" v-else>
|
|
||||||
<text>长按任意功能,可添加到我的常用</text>
|
|
||||||
</view>
|
|
||||||
<view class="wf-group-title">最近使用</view>
|
|
||||||
<view class="wf-panel" v-if="recentItems.length">
|
|
||||||
<view class="wf-grid">
|
|
||||||
<xk-service-card
|
|
||||||
v-for="item in recentItems" :key="item.key"
|
|
||||||
mode="grid"
|
|
||||||
:icon="item.icon" :label="item.label" :theme="item.theme"
|
|
||||||
@click="onItemTap(item)" @longpress="onItemLongpress(item)"
|
|
||||||
></xk-service-card>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="wf-hint" v-else>
|
|
||||||
<text>暂无使用记录,点击功能后会出现在这里</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 全部 tab:所有分组一页看全(分组标题 + 各组金刚区) -->
|
<!-- 全部 tab:所有分组一页看全(分组标题 + 各组金刚区) -->
|
||||||
<view v-else-if="page.type === 'all'" class="wf-page">
|
<view v-if="page.type === 'all'" class="wf-page">
|
||||||
<view v-for="section in sections" :key="section.key">
|
<view v-for="section in sections" :key="section.key">
|
||||||
<view class="wf-group-title">{{ section.title }}</view>
|
<view class="wf-group-title">{{ section.title }}</view>
|
||||||
<view class="wf-panel">
|
<view class="wf-panel">
|
||||||
@@ -130,6 +112,12 @@
|
|||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
</block>
|
</block>
|
||||||
|
|
||||||
|
<!-- 右下角悬浮:打开全部入口的加减编辑(已加减号 / 未加加号,支持批量) -->
|
||||||
|
<view v-if="!loading && !failed && sections.length" class="wf-fab" hover-class="wf-fab-hover" @tap="openEditor">
|
||||||
|
<u-icon name="plus" color="#FFFFFF" size="40"></u-icon>
|
||||||
|
</view>
|
||||||
|
<workbench-fav-editor ref="favEditor" :api="api" @saved="onEditorSaved"></workbench-fav-editor>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -144,17 +132,20 @@
|
|||||||
* - getEntries() GET 当前角色启用入口(扁平数组)
|
* - getEntries() GET 当前角色启用入口(扁平数组)
|
||||||
* - getUserData() GET { favorites: [], recents: [] }
|
* - getUserData() GET { favorites: [], recents: [] }
|
||||||
* - toggleFavorite(code) POST 收藏/取消收藏,data = { is_favorite }
|
* - toggleFavorite(code) POST 收藏/取消收藏,data = { is_favorite }
|
||||||
|
* - batchSaveFavorites(codes) POST 批量保存常用
|
||||||
* - reportUsage(code) POST 使用上报(静默,失败不影响跳转)
|
* - reportUsage(code) POST 使用上报(静默,失败不影响跳转)
|
||||||
*
|
*
|
||||||
* 事件:
|
* 事件:
|
||||||
* - @special(item):点击了无 path 的特殊入口(如推广员端 cs_transfer 传方弹窗),由壳页按 code 处理
|
* - @special(item):点击了无 path 的特殊入口(如推广员端 cs_transfer 传方弹窗),由壳页按 code 处理
|
||||||
*/
|
*/
|
||||||
import { groupEntriesToSections, normalizeEntry } from '@/utils/workbenchEntries.js';
|
import { groupEntriesToSections, normalizeEntry } from '@/utils/workbenchEntries.js';
|
||||||
|
import pullRefreshMixin from '@/subPackages/sub_business_shared/common/pullRefreshMixin.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkbenchFeatures',
|
name: 'WorkbenchFeatures',
|
||||||
|
mixins: [pullRefreshMixin],
|
||||||
props: {
|
props: {
|
||||||
/** 角色接口集合:{ getEntries, getUserData, toggleFavorite, reportUsage } */
|
/** 角色接口集合:{ getEntries, getUserData, toggleFavorite, batchSaveFavorites, reportUsage } */
|
||||||
api: { type: Object, required: true },
|
api: { type: Object, required: true },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -163,9 +154,8 @@ export default {
|
|||||||
failed: false,
|
failed: false,
|
||||||
/** 分组入口(groupEntriesToSections 输出结构) */
|
/** 分组入口(groupEntriesToSections 输出结构) */
|
||||||
sections: [],
|
sections: [],
|
||||||
/** 我的常用 / 最近使用(已归一化为渲染项) */
|
/** 仅用于长按菜单判断是否已收藏,不在本页展示列表(展示在首页) */
|
||||||
favItems: [],
|
favItems: [],
|
||||||
recentItems: [],
|
|
||||||
keyword: '',
|
keyword: '',
|
||||||
current: 0,
|
current: 0,
|
||||||
/** 内容区高度(px):layout 的 navbar 与 tabbar 之间,swiper 需要确定高度才能滚动 */
|
/** 内容区高度(px):layout 的 navbar 与 tabbar 之间,swiper 需要确定高度才能滚动 */
|
||||||
@@ -179,10 +169,9 @@ export default {
|
|||||||
isSearching() {
|
isSearching() {
|
||||||
return this.keyword.trim() !== '';
|
return this.keyword.trim() !== '';
|
||||||
},
|
},
|
||||||
/** swiper 页序列:固定「常用」「全部」两页在前,其后每个分组一页 */
|
/** swiper 页序列:固定「全部」在前,其后每个分组一页(常用/最近只在首页) */
|
||||||
swiperPages() {
|
swiperPages() {
|
||||||
const pages = [
|
const pages = [
|
||||||
{ key: '__fav__', type: 'fav', title: '常用', items: [] },
|
|
||||||
{ key: '__all__', type: 'all', title: '全部', items: [] },
|
{ key: '__all__', type: 'all', title: '全部', items: [] },
|
||||||
];
|
];
|
||||||
this.sections.forEach((s) => {
|
this.sections.forEach((s) => {
|
||||||
@@ -238,34 +227,39 @@ export default {
|
|||||||
}
|
}
|
||||||
this.bodyHeight = Math.max(400, sys.windowHeight - top - 100 * rpx - safeBottom);
|
this.bodyHeight = Math.max(400, sys.windowHeight - top - 100 * rpx - safeBottom);
|
||||||
},
|
},
|
||||||
/** 全量加载:入口列表(必须)+ 用户数据(失败静默,常用/历史留空) */
|
/**
|
||||||
|
* 全量加载入口 + 收藏状态。
|
||||||
|
* 下拉刷新时不切整页 loading,否则 scroll-view 被卸掉转圈收不起来。
|
||||||
|
*/
|
||||||
reload() {
|
reload() {
|
||||||
this.loading = true;
|
const silent = this.isRefreshing;
|
||||||
this.failed = false;
|
if (!silent) {
|
||||||
this.api.getEntries().then(({ data, ok }) => {
|
this.loading = true;
|
||||||
|
this.failed = false;
|
||||||
|
}
|
||||||
|
const entriesP = this.api.getEntries().then(({ data, ok }) => {
|
||||||
if (ok && Array.isArray(data)) {
|
if (ok && Array.isArray(data)) {
|
||||||
this.sections = groupEntriesToSections(data);
|
this.sections = groupEntriesToSections(data);
|
||||||
// 分组数变化时防止 current 越界(固定页为常用+全部两页,如后台调整了分组)
|
if (this.current >= this.sections.length + 1) this.current = 0;
|
||||||
if (this.current >= this.sections.length + 2) this.current = 0;
|
this.failed = false;
|
||||||
} else {
|
} else if (!silent) {
|
||||||
this.failed = true;
|
this.failed = true;
|
||||||
}
|
}
|
||||||
this.loading = false;
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.failed = true;
|
if (!silent) this.failed = true;
|
||||||
|
}).finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
this.refreshUserData();
|
return Promise.all([entriesP, this.refreshUserData()]);
|
||||||
},
|
},
|
||||||
/** 刷新常用/最近使用(壳页 onShow 也会调用,保证跳转返回后历史及时更新) */
|
/** 刷新收藏状态(长按文案用;列表本身只在首页展示) */
|
||||||
refreshUserData() {
|
refreshUserData() {
|
||||||
if (!this.api.getUserData) return;
|
if (!this.api.getUserData) return Promise.resolve();
|
||||||
this.api.getUserData().then(({ data, ok }) => {
|
return this.api.getUserData().then(({ data, ok }) => {
|
||||||
if (!ok || !data) return;
|
if (!ok || !data) return;
|
||||||
this.favItems = this.normalizeList(data.favorites);
|
this.favItems = this.normalizeList(data.favorites);
|
||||||
this.recentItems = this.normalizeList(data.recents);
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// 用户数据是增强信息,失败静默(不打断功能页主流程)
|
// 收藏状态是增强信息,失败静默(不打断功能页主流程)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/** 后端入口对象数组 → 渲染项数组(与首页同一映射,字段完全一致) */
|
/** 后端入口对象数组 → 渲染项数组(与首页同一映射,字段完全一致) */
|
||||||
@@ -318,6 +312,14 @@ export default {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/** 打开全部入口编辑抽屉(悬浮按钮) */
|
||||||
|
openEditor() {
|
||||||
|
if (this.$refs.favEditor) this.$refs.favEditor.open();
|
||||||
|
},
|
||||||
|
/** 批量保存后刷新收藏状态,长按菜单文案与首页常用保持一致 */
|
||||||
|
onEditorSaved() {
|
||||||
|
this.refreshUserData();
|
||||||
|
},
|
||||||
/** 收藏切换:成功后按后端返回状态提示并刷新常用列表 */
|
/** 收藏切换:成功后按后端返回状态提示并刷新常用列表 */
|
||||||
doToggleFavorite(item) {
|
doToggleFavorite(item) {
|
||||||
if (!this.api.toggleFavorite) return;
|
if (!this.api.toggleFavorite) return;
|
||||||
@@ -340,6 +342,7 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 纵向 flex:搜索/tabs 固定高度,swiper 吃满剩余空间(swiper 必须有确定高度才能滚动) */
|
/* 纵向 flex:搜索/tabs 固定高度,swiper 吃满剩余空间(swiper 必须有确定高度才能滚动) */
|
||||||
.wf-wrap {
|
.wf-wrap {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
@@ -429,4 +432,22 @@ export default {
|
|||||||
.wf-retry-hover {
|
.wf-retry-hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
/* 悬浮添加:贴内容区右下,wrap 已扣掉 tabbar 高度 */
|
||||||
|
.wf-fab {
|
||||||
|
position: absolute;
|
||||||
|
right: 32rpx;
|
||||||
|
bottom: 32rpx;
|
||||||
|
width: 104rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 52rpx;
|
||||||
|
background: #6ACDBB;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(106, 205, 187, 0.35);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.wf-fab-hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,10 +34,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 我的常用:空态给引导条(点击也进功能页),避免新用户首页空洞无从下手 -->
|
<!-- 我的常用:标题旁快捷添加;空态点开编辑抽屉,不再引导去功能页长按 -->
|
||||||
<view class="wue-section">
|
<view class="wue-section">
|
||||||
<view class="wue-header">
|
<view class="wue-header">
|
||||||
<text class="wue-title">我的常用</text>
|
<text class="wue-title">我的常用</text>
|
||||||
|
<view class="wue-add" hover-class="wue-add-hover" @tap="openEditor">
|
||||||
|
<u-icon name="plus" size="26" color="#6ACDBB"></u-icon>
|
||||||
|
<text>添加</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="favorites.length && cardLong" class="wue-grid is-long">
|
<view v-if="favorites.length && cardLong" class="wue-grid is-long">
|
||||||
<view v-for="item in favorites" :key="item.key" class="wue-cell">
|
<view v-for="item in favorites" :key="item.key" class="wue-cell">
|
||||||
@@ -60,9 +64,9 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="wue-hint" hover-class="wue-hint-hover" @tap="$emit('more')">
|
<view v-else class="wue-hint" hover-class="wue-hint-hover" @tap="openEditor">
|
||||||
<u-icon name="star" size="30" color="#8E8E93"></u-icon>
|
<u-icon name="plus-circle" size="30" color="#6ACDBB"></u-icon>
|
||||||
<text class="wue-hint-text">暂无常用功能,去功能页长按任意入口即可添加</text>
|
<text class="wue-hint-text">暂无常用功能,点此添加</text>
|
||||||
<u-icon name="arrow-right" size="22" color="#C7C7CC"></u-icon>
|
<u-icon name="arrow-right" size="22" color="#C7C7CC"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -75,6 +79,7 @@
|
|||||||
@click="$emit('more')"
|
@click="$emit('more')"
|
||||||
></xk-service-card>
|
></xk-service-card>
|
||||||
</view>
|
</view>
|
||||||
|
<workbench-fav-editor ref="favEditor" :api="api" @saved="onEditorSaved"></workbench-fav-editor>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -86,7 +91,7 @@
|
|||||||
* 三段结构与双形态布局完全一致,收敛到主包组件避免四份复制。
|
* 三段结构与双形态布局完全一致,收敛到主包组件避免四份复制。
|
||||||
* 数据与行为职责划分:
|
* 数据与行为职责划分:
|
||||||
* - 父页面负责拉取 entry-user-data、按 code 注入本端行为(角标/表单预填/传方弹窗)后传入
|
* - 父页面负责拉取 entry-user-data、按 code 注入本端行为(角标/表单预填/传方弹窗)后传入
|
||||||
* - 本组件只管渲染与转发事件:@item-click(item) 点了某入口、@more 要去功能页
|
* - 本组件只管渲染与转发事件:@item-click(item) 点了某入口、@more 要去功能页、@saved 常用已批量更新
|
||||||
* 点击上报使用也由父页面在 @item-click 里做(各端上报接口不同)
|
* 点击上报使用也由父页面在 @item-click 里做(各端上报接口不同)
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
@@ -98,6 +103,8 @@ export default {
|
|||||||
favorites: { type: Array, default: () => [] },
|
favorites: { type: Array, default: () => [] },
|
||||||
/** 卡片形态:true 长卡一行两个 / false 金刚区宫格 */
|
/** 卡片形态:true 长卡一行两个 / false 金刚区宫格 */
|
||||||
cardLong: { type: Boolean, default: false },
|
cardLong: { type: Boolean, default: false },
|
||||||
|
/** 快捷添加抽屉用:{ getEntries, getUserData, batchSaveFavorites } */
|
||||||
|
api: { type: Object, default: () => ({}) },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -110,6 +117,14 @@ export default {
|
|||||||
onItemTap(item) {
|
onItemTap(item) {
|
||||||
this.$emit('item-click', item);
|
this.$emit('item-click', item);
|
||||||
},
|
},
|
||||||
|
/** 打开常用编辑抽屉(标题「添加」与空态共用) */
|
||||||
|
openEditor() {
|
||||||
|
if (this.$refs.favEditor) this.$refs.favEditor.open();
|
||||||
|
},
|
||||||
|
/** 批量保存成功:通知首页重拉常用/最近,角标等装饰仍由父页注入 */
|
||||||
|
onEditorSaved() {
|
||||||
|
this.$emit('saved');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -119,9 +134,29 @@ export default {
|
|||||||
margin: 0 30rpx 30rpx;
|
margin: 0 30rpx 30rpx;
|
||||||
}
|
}
|
||||||
.wue-header {
|
.wue-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
padding: 0 10rpx;
|
padding: 0 10rpx;
|
||||||
}
|
}
|
||||||
|
.wue-add {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
border: 1rpx solid rgba(106, 205, 187, 0.3);
|
||||||
|
box-shadow: 0 0 10rpx rgba(106, 205, 187, 0.16);
|
||||||
|
background: rgba(106, 205, 187, 0.08);
|
||||||
|
text {
|
||||||
|
margin-left: 6rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #6ACDBB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wue-add-hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
/* 分区标题:对齐四首页既有 panel-title 规格 */
|
/* 分区标题:对齐四首页既有 panel-title 规格 */
|
||||||
.wue-title {
|
.wue-title {
|
||||||
font-size: 34rpx;
|
font-size: 34rpx;
|
||||||
|
|||||||
6
main.js
@@ -10,6 +10,7 @@ import uView from "uview-ui";
|
|||||||
import timeFormatIOSFix from '@/common/js/timeFormat.js'
|
import timeFormatIOSFix from '@/common/js/timeFormat.js'
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import share from '@/common/share.js'
|
import share from '@/common/share.js'
|
||||||
|
import { staticOss } from '@/utils/staticOss.js'
|
||||||
Vue.mixin(share)
|
Vue.mixin(share)
|
||||||
Vue.use(uView);
|
Vue.use(uView);
|
||||||
// 覆盖 uView 的 timeFormat / date:修复 iOS 下 `yyyy-MM-dd HH:mm:ss` 无法解析
|
// 覆盖 uView 的 timeFormat / date:修复 iOS 下 `yyyy-MM-dd HH:mm:ss` 无法解析
|
||||||
@@ -71,8 +72,9 @@ Vue.prototype.$navbarHeight = uni.$d.navbarHeight()
|
|||||||
Vue.prototype.$windowHeight = systemInfo.windowHeight
|
Vue.prototype.$windowHeight = systemInfo.windowHeight
|
||||||
Vue.prototype.$screenHeight = systemInfo.screenHeight
|
Vue.prototype.$screenHeight = systemInfo.screenHeight
|
||||||
Vue.prototype.$checkID = m_by_checkID
|
Vue.prototype.$checkID = m_by_checkID
|
||||||
Vue.prototype.$male = require('@/static/image/nan.png')
|
Vue.prototype.$staticOss = staticOss
|
||||||
Vue.prototype.$girl = require('@/static/image/nv.png')
|
Vue.prototype.$male = staticOss('image/nan.png')
|
||||||
|
Vue.prototype.$girl = staticOss('image/nv.png')
|
||||||
// #ifndef VUE3
|
// #ifndef VUE3
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|||||||
102
pages.json
@@ -3,7 +3,9 @@
|
|||||||
"autoscan": true,
|
"autoscan": true,
|
||||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
|
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
|
||||||
"^d-(.*)": "@/components/d-$1/d-$1.vue",
|
"^d-(.*)": "@/components/d-$1/d-$1.vue",
|
||||||
"^ai-generation-loading$": "@/components/ai-generation-loading/ai-generation-loading.vue"
|
"^ai-generation-loading$": "@/components/ai-generation-loading/ai-generation-loading.vue",
|
||||||
|
"^proxy-pay-qrcode-modal$": "@/components/proxy-pay-qrcode-modal/proxy-pay-qrcode-modal.vue",
|
||||||
|
"^claim-qrcode-modal$": "@/components/claim-qrcode-modal/claim-qrcode-modal.vue"
|
||||||
},
|
},
|
||||||
"pages": [{
|
"pages": [{
|
||||||
"path": "pages/login/index",
|
"path": "pages/login/index",
|
||||||
@@ -35,7 +37,8 @@
|
|||||||
"path": "pages/patient/index",
|
"path": "pages/patient/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "患者",
|
"navigationBarTitleText": "患者",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -79,6 +82,20 @@
|
|||||||
"enablePullDownRefresh": true
|
"enablePullDownRefresh": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "create_user_patient",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "新建就诊人",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "doctor_created_patient_list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "创建记录",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "workbench_succeed",
|
"path": "workbench_succeed",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -149,6 +166,14 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "处方详情"
|
"navigationBarTitleText": "处方详情"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "public_account_lock/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "公账已锁定",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"disableSwipeBack": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -281,7 +306,7 @@
|
|||||||
"root": "subPackages/sub_clinic_admin",
|
"root": "subPackages/sub_clinic_admin",
|
||||||
"pages": [{
|
"pages": [{
|
||||||
"path": "home/index",
|
"path": "home/index",
|
||||||
"style": { "navigationBarTitleText": "工作台", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "工作台", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "features/index",
|
"path": "features/index",
|
||||||
@@ -289,7 +314,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "my/index",
|
"path": "my/index",
|
||||||
"style": { "navigationBarTitleText": "我的", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "我的", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "my/vip-detail",
|
"path": "my/vip-detail",
|
||||||
@@ -297,11 +322,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "withdrawal/index",
|
"path": "withdrawal/index",
|
||||||
"style": { "navigationBarTitleText": "提现管理", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "提现管理", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "withdrawal/settlement/index",
|
"path": "withdrawal/settlement/index",
|
||||||
"style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "withdrawal/settlement/detail",
|
"path": "withdrawal/settlement/detail",
|
||||||
@@ -309,7 +334,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "withdrawal/account-change/index",
|
"path": "withdrawal/account-change/index",
|
||||||
"style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "withdrawal/apply",
|
"path": "withdrawal/apply",
|
||||||
@@ -323,9 +348,13 @@
|
|||||||
"path": "withdrawal/record-detail",
|
"path": "withdrawal/record-detail",
|
||||||
"style": { "navigationBarTitleText": "提现详情", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "提现详情", "navigationStyle": "custom" }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "withdrawal/withdrawable-orders",
|
||||||
|
"style": { "navigationBarTitleText": "可提订单", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "reconciliation/index",
|
"path": "reconciliation/index",
|
||||||
"style": { "navigationBarTitleText": "对账单", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "对账单", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "order/index",
|
"path": "order/index",
|
||||||
@@ -369,18 +398,19 @@
|
|||||||
{ "path": "order/index", "style": { "navigationBarTitleText": "商品订单", "navigationStyle": "custom", "disableScroll": true } },
|
{ "path": "order/index", "style": { "navigationBarTitleText": "商品订单", "navigationStyle": "custom", "disableScroll": true } },
|
||||||
{ "path": "order/detail", "style": { "navigationBarTitleText": "订单详情", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
{ "path": "order/detail", "style": { "navigationBarTitleText": "订单详情", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "order/trace/index", "style": { "navigationBarTitleText": "订单追溯", "navigationStyle": "custom" } },
|
{ "path": "order/trace/index", "style": { "navigationBarTitleText": "订单追溯", "navigationStyle": "custom" } },
|
||||||
{ "path": "reconciliation/index", "style": { "navigationBarTitleText": "对账单", "navigationStyle": "custom" } },
|
{ "path": "reconciliation/index", "style": { "navigationBarTitleText": "对账单", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "withdrawal/index", "style": { "navigationBarTitleText": "提现管理", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/index", "style": { "navigationBarTitleText": "提现管理", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "withdrawal/apply", "style": { "navigationBarTitleText": "申请提现", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/apply", "style": { "navigationBarTitleText": "申请提现", "navigationStyle": "custom" } },
|
||||||
{ "path": "withdrawal/card-edit", "style": { "navigationBarTitleText": "编辑账户", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/card-edit", "style": { "navigationBarTitleText": "编辑账户", "navigationStyle": "custom" } },
|
||||||
{ "path": "withdrawal/record-detail", "style": { "navigationBarTitleText": "提现详情", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/record-detail", "style": { "navigationBarTitleText": "提现详情", "navigationStyle": "custom" } },
|
||||||
{ "path": "withdrawal/settlement/index", "style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/withdrawable-orders", "style": { "navigationBarTitleText": "可提订单", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
|
{ "path": "withdrawal/settlement/index", "style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "withdrawal/settlement/detail", "style": { "navigationBarTitleText": "结算明细", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/settlement/detail", "style": { "navigationBarTitleText": "结算明细", "navigationStyle": "custom" } },
|
||||||
{ "path": "withdrawal/account-change/index", "style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/account-change/index", "style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "warehouse/index", "style": { "navigationBarTitleText": "仓库管理", "navigationStyle": "custom" } },
|
{ "path": "warehouse/index", "style": { "navigationBarTitleText": "仓库管理", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "warehouse/detail", "style": { "navigationBarTitleText": "药品详情", "navigationStyle": "custom" } },
|
{ "path": "warehouse/detail", "style": { "navigationBarTitleText": "药品详情", "navigationStyle": "custom" } },
|
||||||
{ "path": "user-patient/index", "style": { "navigationBarTitleText": "会员管理", "navigationStyle": "custom" } },
|
{ "path": "user-patient/index", "style": { "navigationBarTitleText": "会员管理", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "user-patient/patients", "style": { "navigationBarTitleText": "就诊人列表", "navigationStyle": "custom" } },
|
{ "path": "user-patient/patients", "style": { "navigationBarTitleText": "就诊人列表", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "user-patient/detail", "style": { "navigationBarTitleText": "就诊人详情", "navigationStyle": "custom" } },
|
{ "path": "user-patient/detail", "style": { "navigationBarTitleText": "就诊人详情", "navigationStyle": "custom" } },
|
||||||
{ "path": "user-patient/register-detail", "style": { "navigationBarTitleText": "挂号详情", "navigationStyle": "custom" } },
|
{ "path": "user-patient/register-detail", "style": { "navigationBarTitleText": "挂号详情", "navigationStyle": "custom" } },
|
||||||
{ "path": "profile/index", "style": { "navigationBarTitleText": "个人中心", "navigationStyle": "custom" } }
|
{ "path": "profile/index", "style": { "navigationBarTitleText": "个人中心", "navigationStyle": "custom" } }
|
||||||
@@ -388,28 +418,28 @@
|
|||||||
}, {
|
}, {
|
||||||
"root": "subPackages/sub_platform_admin",
|
"root": "subPackages/sub_platform_admin",
|
||||||
"pages": [
|
"pages": [
|
||||||
{ "path": "home/index", "style": { "navigationBarTitleText": "工作台", "navigationStyle": "custom" } },
|
{ "path": "home/index", "style": { "navigationBarTitleText": "工作台", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "features/index", "style": { "navigationBarTitleText": "功能", "navigationStyle": "custom" } },
|
{ "path": "features/index", "style": { "navigationBarTitleText": "功能", "navigationStyle": "custom" } },
|
||||||
{ "path": "my/index", "style": { "navigationBarTitleText": "我的", "navigationStyle": "custom" } },
|
{ "path": "my/index", "style": { "navigationBarTitleText": "我的", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "withdrawal/audit/index", "style": { "navigationBarTitleText": "提现审核", "navigationStyle": "custom" } },
|
{ "path": "withdrawal/audit/index", "style": { "navigationBarTitleText": "提现审核", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "store/index", "style": { "navigationBarTitleText": "门店管理", "navigationStyle": "custom" } },
|
{ "path": "store/index", "style": { "navigationBarTitleText": "门店管理", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "store/detail", "style": { "navigationBarTitleText": "门店详情", "navigationStyle": "custom" } },
|
{ "path": "store/detail", "style": { "navigationBarTitleText": "门店详情", "navigationStyle": "custom" } },
|
||||||
{ "path": "store/edit", "style": { "navigationBarTitleText": "编辑诊所", "navigationStyle": "custom" } },
|
{ "path": "store/edit", "style": { "navigationBarTitleText": "编辑诊所", "navigationStyle": "custom" } },
|
||||||
{ "path": "store/drug-price", "style": { "navigationBarTitleText": "药品价格", "navigationStyle": "custom" } },
|
{ "path": "store/drug-price", "style": { "navigationBarTitleText": "药品价格", "navigationStyle": "custom" } },
|
||||||
{ "path": "store/consultation", "style": { "navigationBarTitleText": "在线复诊", "navigationStyle": "custom" } },
|
{ "path": "store/consultation", "style": { "navigationBarTitleText": "在线复诊", "navigationStyle": "custom" } },
|
||||||
{ "path": "store-input/audit/index", "style": { "navigationBarTitleText": "门店审核", "navigationStyle": "custom" } },
|
{ "path": "store-input/audit/index", "style": { "navigationBarTitleText": "门店审核", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "store-input/audit/detail", "style": { "navigationBarTitleText": "审核详情", "navigationStyle": "custom" } },
|
{ "path": "store-input/audit/detail", "style": { "navigationBarTitleText": "审核详情", "navigationStyle": "custom" } },
|
||||||
{ "path": "doctor-input/audit/index", "style": { "navigationBarTitleText": "医生审核", "navigationStyle": "custom" } },
|
{ "path": "doctor-input/audit/index", "style": { "navigationBarTitleText": "医生审核", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "doctor-input/audit/detail", "style": { "navigationBarTitleText": "审核详情", "navigationStyle": "custom" } }
|
{ "path": "doctor-input/audit/detail", "style": { "navigationBarTitleText": "审核详情", "navigationStyle": "custom" } }
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
"root": "subPackages/sub_salesperson_manage",
|
"root": "subPackages/sub_salesperson_manage",
|
||||||
"pages": [
|
"pages": [
|
||||||
{ "path": "index", "style": { "navigationBarTitleText": "推广员管理", "navigationStyle": "custom" } },
|
{ "path": "index", "style": { "navigationBarTitleText": "推广员管理", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "form", "style": { "navigationBarTitleText": "推广员", "navigationStyle": "custom" } },
|
{ "path": "form", "style": { "navigationBarTitleText": "推广员", "navigationStyle": "custom" } },
|
||||||
{ "path": "drug-commission/index", "style": { "navigationBarTitleText": "药品佣金", "navigationStyle": "custom" } },
|
{ "path": "drug-commission/index", "style": { "navigationBarTitleText": "药品佣金", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "leads/index", "style": { "navigationBarTitleText": "获客记录", "navigationStyle": "custom" } },
|
{ "path": "leads/index", "style": { "navigationBarTitleText": "获客记录", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "commission/index", "style": { "navigationBarTitleText": "服务费与结算", "navigationStyle": "custom" } },
|
{ "path": "commission/index", "style": { "navigationBarTitleText": "服务费与结算", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "settlement/preview", "style": { "navigationBarTitleText": "按时间段结算", "navigationStyle": "custom" } },
|
{ "path": "settlement/preview", "style": { "navigationBarTitleText": "按时间段结算", "navigationStyle": "custom" } },
|
||||||
{ "path": "settlement/confirm", "style": { "navigationBarTitleText": "确认结算", "navigationStyle": "custom" } },
|
{ "path": "settlement/confirm", "style": { "navigationBarTitleText": "确认结算", "navigationStyle": "custom" } },
|
||||||
{ "path": "settlement/detail", "style": { "navigationBarTitleText": "结算详情", "navigationStyle": "custom" } }
|
{ "path": "settlement/detail", "style": { "navigationBarTitleText": "结算详情", "navigationStyle": "custom" } }
|
||||||
@@ -419,12 +449,12 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
{ "path": "home/index", "style": { "navigationBarTitleText": "工作台", "navigationStyle": "custom" } },
|
{ "path": "home/index", "style": { "navigationBarTitleText": "工作台", "navigationStyle": "custom" } },
|
||||||
{ "path": "features/index", "style": { "navigationBarTitleText": "功能", "navigationStyle": "custom" } },
|
{ "path": "features/index", "style": { "navigationBarTitleText": "功能", "navigationStyle": "custom" } },
|
||||||
{ "path": "my/index", "style": { "navigationBarTitleText": "我的", "navigationStyle": "custom" } },
|
{ "path": "my/index", "style": { "navigationBarTitleText": "我的", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "store-input/index", "style": { "navigationBarTitleText": "列表", "navigationStyle": "custom" } },
|
{ "path": "store-input/index", "style": { "navigationBarTitleText": "列表", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "store-input/form", "style": { "navigationBarTitleText": "录入", "navigationStyle": "custom" } },
|
{ "path": "store-input/form", "style": { "navigationBarTitleText": "录入", "navigationStyle": "custom" } },
|
||||||
{ "path": "store-input/detail", "style": { "navigationBarTitleText": "录入详情", "navigationStyle": "custom" } },
|
{ "path": "store-input/detail", "style": { "navigationBarTitleText": "录入详情", "navigationStyle": "custom" } },
|
||||||
{ "path": "store-bank-card/report", "style": { "navigationBarTitleText": "银行卡报备", "navigationStyle": "custom" } },
|
{ "path": "store-bank-card/report", "style": { "navigationBarTitleText": "银行卡报备", "navigationStyle": "custom" } },
|
||||||
{ "path": "doctor-input/index", "style": { "navigationBarTitleText": "列表", "navigationStyle": "custom" } },
|
{ "path": "doctor-input/index", "style": { "navigationBarTitleText": "列表", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "doctor-input/form", "style": { "navigationBarTitleText": "录入", "navigationStyle": "custom" } },
|
{ "path": "doctor-input/form", "style": { "navigationBarTitleText": "录入", "navigationStyle": "custom" } },
|
||||||
{ "path": "doctor-input/detail", "style": { "navigationBarTitleText": "预填详情", "navigationStyle": "custom" } }
|
{ "path": "doctor-input/detail", "style": { "navigationBarTitleText": "预填详情", "navigationStyle": "custom" } }
|
||||||
]
|
]
|
||||||
@@ -436,6 +466,7 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "工作台",
|
"navigationBarTitleText": "工作台",
|
||||||
"navigationStyle": "custom",
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
"usingComponents": {
|
"usingComponents": {
|
||||||
"transfer-patient-drawer": "/subPackages/sub_workbench/prescription_v2/components/modals/TransferPatientDrawer"
|
"transfer-patient-drawer": "/subPackages/sub_workbench/prescription_v2/components/modals/TransferPatientDrawer"
|
||||||
},
|
},
|
||||||
@@ -448,13 +479,13 @@
|
|||||||
"path": "features/index",
|
"path": "features/index",
|
||||||
"style": { "navigationBarTitleText": "功能", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "功能", "navigationStyle": "custom" }
|
||||||
},
|
},
|
||||||
{ "path": "my/index", "style": { "navigationBarTitleText": "我的", "navigationStyle": "custom" } },
|
{ "path": "my/index", "style": { "navigationBarTitleText": "我的", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "leads/index", "style": { "navigationBarTitleText": "获客记录", "navigationStyle": "custom" } },
|
{ "path": "leads/index", "style": { "navigationBarTitleText": "获客记录", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "commission/index", "style": { "navigationBarTitleText": "服务费记录", "navigationStyle": "custom" } },
|
{ "path": "commission/index", "style": { "navigationBarTitleText": "服务费记录", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "invitees/index", "style": { "navigationBarTitleText": "我邀请的推广员", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
{ "path": "invitees/index", "style": { "navigationBarTitleText": "我邀请的推广员", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "invitees/detail", "style": { "navigationBarTitleText": "收益明细", "navigationStyle": "custom" } },
|
{ "path": "invitees/detail", "style": { "navigationBarTitleText": "收益明细", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "settlement/index", "style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom" } },
|
{ "path": "settlement/index", "style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||||
{ "path": "transfer-prescription/list", "style": { "navigationBarTitleText": "传方记录", "navigationStyle": "custom" } }
|
{ "path": "transfer-prescription/list", "style": { "navigationBarTitleText": "传方记录", "navigationStyle": "custom", "enablePullDownRefresh": true } }
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
"root": "subPackages/sub_agreement",
|
"root": "subPackages/sub_agreement",
|
||||||
@@ -470,7 +501,8 @@
|
|||||||
"path": "pages/list",
|
"path": "pages/list",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "在线接诊",
|
"navigationBarTitleText": "在线接诊",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
"path": "pages/chat",
|
"path": "pages/chat",
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ export default {
|
|||||||
onShow() {
|
onShow() {
|
||||||
if (this.pendingAccountSelect) return;
|
if (this.pendingAccountSelect) return;
|
||||||
this.$refs.doctorLogin?.loadWxConfig?.();
|
this.$refs.doctorLogin?.loadWxConfig?.();
|
||||||
redirectIfLoggedIn();
|
// 等当前页进栈后再跳,避免启动阶段 reLaunch 触发 appLaunch with non-empty page stack
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.pendingAccountSelect) return;
|
||||||
|
redirectIfLoggedIn();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openAccountPicker({ accounts, defaultAccountId, defaultAccountType }) {
|
openAccountPicker({ accounts, defaultAccountId, defaultAccountType }) {
|
||||||
|
|||||||
@@ -105,29 +105,29 @@
|
|||||||
userInfo: uni.getStorageSync('userInfo') || {},
|
userInfo: uni.getStorageSync('userInfo') || {},
|
||||||
functions: [{
|
functions: [{
|
||||||
name: '我的处方',
|
name: '我的处方',
|
||||||
icon: '../../static/image/wd-cf.png',
|
icon: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/wd-cf.png',
|
||||||
url: '/subPackages/sub_my/prescriptionList'
|
url: '/subPackages/sub_my/prescriptionList'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '我的常用方',
|
name: '我的常用方',
|
||||||
icon: '../../static/image/wd-cyf.png',
|
icon: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/wd-cyf.png',
|
||||||
url: '../../subPackages/sub_workbench/workbench_recipe/common?type=0'
|
url: '../../subPackages/sub_workbench/workbench_recipe/common?type=0'
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: '挂号费用',
|
// name: '挂号费用',
|
||||||
// icon: '../../static/image/wd-ghfy.png',
|
// icon: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/wd-ghfy.png',
|
||||||
// url: '../../subPackages/sub_my/my_revenue/index'
|
// url: '../../subPackages/sub_my/my_revenue/index'
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
name: '服务设置',
|
name: '服务设置',
|
||||||
icon: '../../static/image/fwsz.png',
|
icon: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/fwsz.png',
|
||||||
url: '../../subPackages/sub_my/my_service'
|
url: '../../subPackages/sub_my/my_service'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: '常用医嘱管理',
|
name: '常用医嘱管理',
|
||||||
icon: '../../static/image/i1.png',
|
icon: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/i1.png',
|
||||||
url: '../../subPackages/sub_my/my_diagnose_common'
|
url: '../../subPackages/sub_my/my_diagnose_common'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
computed: {
|
computed: {
|
||||||
// getAvatar() {
|
// getAvatar() {
|
||||||
// let doctorInfo = uni.getStorageSync('doctorInfo')
|
// let doctorInfo = uni.getStorageSync('doctorInfo')
|
||||||
// let url = require(`@/static/image/${doctorInfo.DoctorInfo.user.role%2==0?'nv':'nan'}.png`);
|
// let url = staticOss('image/' + (doctorInfo.DoctorInfo.user.role%2==0?'nv':'nan') + '.png');
|
||||||
// // console.log(info, 'ddd')
|
// // console.log(info, 'ddd')
|
||||||
// return doctorInfo.DoctorInfo['avatar'] ? doctorInfo.DoctorInfo['avatar'] : doctorInfo
|
// return doctorInfo.DoctorInfo['avatar'] ? doctorInfo.DoctorInfo['avatar'] : doctorInfo
|
||||||
// .DoctorInfo.user.role == 1 ? url : url
|
// .DoctorInfo.user.role == 1 ? url : url
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
@click="$go('../../subPackages/sub_pharmacist/pharmacist_info?avatar='+avatar+'&info='+JSON.stringify(info))">
|
@click="$go('../../subPackages/sub_pharmacist/pharmacist_info?avatar='+avatar+'&info='+JSON.stringify(info))">
|
||||||
<view class="flex-row flex-ali-center">
|
<view class="flex-row flex-ali-center">
|
||||||
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
||||||
:name="require('../../static/image/grzl.png')" label="个人资料" size="30"></u-icon>
|
:name="'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/grzl.png'" label="个人资料" size="30"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
@click="$go('../../subPackages/sub_pharmacist/pharmacist_setInfo')">
|
@click="$go('../../subPackages/sub_pharmacist/pharmacist_setInfo')">
|
||||||
<view class="flex-row flex-ali-center">
|
<view class="flex-row flex-ali-center">
|
||||||
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
<u-icon margin-left="20" label-size="32" label-color="#31353D"
|
||||||
:name="require('../../static/image/set.png')" label="设置" size="30"></u-icon>
|
:name="'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/set.png'" label="设置" size="30"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
<u-icon name="arrow-right" size="32" color="#A7ABB0"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<view class="flex-row flex-jus-sp">
|
<view class="flex-row flex-jus-sp">
|
||||||
<view class="entry-card flex-1" hover-class="entry-card-hover"
|
<view class="entry-card flex-1" hover-class="entry-card-hover"
|
||||||
@click="$go('../../subPackages/sub_patient/to-store?id='+1)">
|
@click="$go('../../subPackages/sub_patient/to-store?id='+1)">
|
||||||
<image class="entry-icon" src="../../static/image/hz_xsjz.png"></image>
|
<image class="entry-icon" src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/hz_xsjz.png"></image>
|
||||||
<view class="entry-info">
|
<view class="entry-info">
|
||||||
<text class="entry-name">到店接诊</text>
|
<text class="entry-name">到店接诊</text>
|
||||||
<text class="entry-desc">处理到店患者</text>
|
<text class="entry-desc">处理到店患者</text>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="entry-card flex-1 m-l-2" hover-class="entry-card-hover"
|
<view class="entry-card flex-1 m-l-2" hover-class="entry-card-hover"
|
||||||
@click="$go('../../subPackages/sub_patient/to-store?id='+2)">
|
@click="$go('../../subPackages/sub_patient/to-store?id='+2)">
|
||||||
<image class="entry-icon" src="../../static/image/hz_ddjz.png"></image>
|
<image class="entry-icon" src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/hz_ddjz.png"></image>
|
||||||
<view class="entry-info">
|
<view class="entry-info">
|
||||||
<text class="entry-name">线上接诊</text>
|
<text class="entry-name">线上接诊</text>
|
||||||
<text class="entry-desc">线上问诊接待</text>
|
<text class="entry-desc">线上问诊接待</text>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<!-- 群发消息入口行 -->
|
<!-- 群发消息入口行 -->
|
||||||
<view class="list-new" hover-class="entry-card-hover" @click="$go('../../subPackages/sub_patient/patient_massInfo')">
|
<view class="list-new" hover-class="entry-card-hover" @click="$go('../../subPackages/sub_patient/patient_massInfo')">
|
||||||
<view class="l-l-row">
|
<view class="l-l-row">
|
||||||
<image src="/static/image/hz-qfxx.png" mode=""></image>
|
<image src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/hz-qfxx.png' mode=""></image>
|
||||||
<view class="entry-info">
|
<view class="entry-info">
|
||||||
<text class="entry-name">群发消息</text>
|
<text class="entry-name">群发消息</text>
|
||||||
<text class="entry-desc">向患者批量发送通知</text>
|
<text class="entry-desc">向患者批量发送通知</text>
|
||||||
@@ -46,9 +46,9 @@
|
|||||||
<xk-list-card
|
<xk-list-card
|
||||||
v-for="(item, index) in list" :key="index"
|
v-for="(item, index) in list" :key="index"
|
||||||
mode="flat"
|
mode="flat"
|
||||||
:avatar="item.user.avatarurl || require(`../../static/image/${item.sex%2==0?'nv':'nan'}.png`)"
|
:avatar="item.card_avatar"
|
||||||
:title="item.patient"
|
:title="item.patient"
|
||||||
:meta="(item.sex % 2 != 0 ? '男' : '女') + ' · ' + item.age + '岁'"
|
:meta="item.card_meta"
|
||||||
:arrow="true"
|
:arrow="true"
|
||||||
@click="toDetail(item.patient_id)"
|
@click="toDetail(item.patient_id)"
|
||||||
></xk-list-card>
|
></xk-list-card>
|
||||||
@@ -127,7 +127,8 @@
|
|||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.errcode == 0) {
|
if (res.errcode == 0) {
|
||||||
this.list = this.page === 1 ? res.data.list : [...this.list, ...res.data.list];
|
const rows = (res.data.list || []).map((item) => this.normalizeItem(item));
|
||||||
|
this.list = this.page === 1 ? rows : [...this.list, ...rows];
|
||||||
this.totalPage = res.data.pagination.totalPage;
|
this.totalPage = res.data.pagination.totalPage;
|
||||||
if (this.page >= this.totalPage) {
|
if (this.page >= this.totalPage) {
|
||||||
this.status = "nomore";
|
this.status = "nomore";
|
||||||
@@ -153,6 +154,22 @@
|
|||||||
this.loading2 = false;
|
this.loading2 = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 列表展示字段提前算好:部分患者 user 为 null,模板读 user.avatarurl 会直接崩
|
||||||
|
* 微信模板禁止 ?. / ??,头像和性别文案统一在这里兜底
|
||||||
|
*/
|
||||||
|
normalizeItem(item) {
|
||||||
|
const isFemale = item.sex % 2 == 0;
|
||||||
|
const user = item.user || {};
|
||||||
|
const fallback = isFemale
|
||||||
|
? (this.girl || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/nv.png')
|
||||||
|
: (this.male || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/nan.png');
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
card_avatar: user.avatarurl || fallback,
|
||||||
|
card_meta: (isFemale ? '女' : '男') + ' · ' + (item.age || '-') + '岁',
|
||||||
|
};
|
||||||
|
},
|
||||||
toDetail(id) {
|
toDetail(id) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/subPackages/sub_patient/patient_detail?id=' + id
|
url: '/subPackages/sub_patient/patient_detail?id=' + id
|
||||||
|
|||||||
@@ -13,14 +13,21 @@
|
|||||||
|
|
||||||
<!-- 未通过入驻审核:整页空态守卫,不渲染 swiper -->
|
<!-- 未通过入驻审核:整页空态守卫,不渲染 swiper -->
|
||||||
<view class="empty" v-if="status != 2">
|
<view class="empty" v-if="status != 2">
|
||||||
<image src="/static/image/none.png" mode="aspectFit"></image>
|
<image src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/none.png' mode="aspectFit"></image>
|
||||||
<text>您还没有通过审核,请稍后再试~</text>
|
<text>您还没有通过审核,请稍后再试~</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 审方列表:swiper 承载三个状态 tab,支持左右滑动切换;每个 tab 独立分页 + 上拉加载 -->
|
<!-- 审方列表:swiper 承载三个状态 tab,支持左右滑动切换;每个 tab 独立分页 + 上拉加载 -->
|
||||||
<swiper v-else class="tab-swiper" :current="currents" @change="onSwiperChange">
|
<swiper v-else class="tab-swiper" :current="currents" @change="onSwiperChange">
|
||||||
<swiper-item v-for="(tab, tabIndex) in tabPages" :key="tab.status">
|
<swiper-item v-for="(tab, tabIndex) in tabPages" :key="tab.status">
|
||||||
<scroll-view scroll-y class="tab-scroll" @scrolltolower="loadMore(tabIndex)">
|
<scroll-view
|
||||||
|
scroll-y
|
||||||
|
class="tab-scroll"
|
||||||
|
refresher-enabled
|
||||||
|
:refresher-triggered="isRefreshing"
|
||||||
|
@refresherrefresh="onRefresherRefresh"
|
||||||
|
@scrolltolower="loadMore(tabIndex)"
|
||||||
|
>
|
||||||
<view class="list-wrap">
|
<view class="list-wrap">
|
||||||
<xk-list-card
|
<xk-list-card
|
||||||
v-for="item in tab.list" :key="item.id"
|
v-for="item in tab.list" :key="item.id"
|
||||||
@@ -38,7 +45,7 @@
|
|||||||
|
|
||||||
<!-- 各 tab 独立空态:首屏加载完成且无数据时展示 -->
|
<!-- 各 tab 独立空态:首屏加载完成且无数据时展示 -->
|
||||||
<view class="tab-empty" v-if="tab.loaded && tab.list.length === 0">
|
<view class="tab-empty" v-if="tab.loaded && tab.list.length === 0">
|
||||||
<image src="/static/image/none.png" mode="aspectFit"></image>
|
<image src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/none.png' mode="aspectFit"></image>
|
||||||
<text>暂无{{lists[tabIndex].name}}的处方</text>
|
<text>暂无{{lists[tabIndex].name}}的处方</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -69,7 +76,9 @@
|
|||||||
import {
|
import {
|
||||||
unwrapXkApi
|
unwrapXkApi
|
||||||
} from "@/utils/api-response.js";
|
} from "@/utils/api-response.js";
|
||||||
|
import pullRefreshMixin from '@/subPackages/sub_business_shared/common/pullRefreshMixin.js';
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [pullRefreshMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
status: uni.getStorageSync('login_status'),
|
status: uni.getStorageSync('login_status'),
|
||||||
@@ -122,6 +131,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* 下拉刷新当前审方 tab
|
||||||
|
*/
|
||||||
|
reload() {
|
||||||
|
return this.refreshTab(this.currents)
|
||||||
|
},
|
||||||
/** tabs 与 swiper 统一切换入口:点 tab、手势左右滑都收敛到这里 */
|
/** tabs 与 swiper 统一切换入口:点 tab、手势左右滑都收敛到这里 */
|
||||||
switchTab(index) {
|
switchTab(index) {
|
||||||
if (this.currents === index) return
|
if (this.currents === index) return
|
||||||
@@ -140,7 +155,7 @@
|
|||||||
const tab = this.tabPages[index]
|
const tab = this.tabPages[index]
|
||||||
tab.page = 1
|
tab.page = 1
|
||||||
tab.finished = false
|
tab.finished = false
|
||||||
this.fetchTab(index, true)
|
return this.fetchTab(index, true)
|
||||||
},
|
},
|
||||||
/** 上拉加载下一页 */
|
/** 上拉加载下一页 */
|
||||||
loadMore(index) {
|
loadMore(index) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<view class="head">
|
<view class="head">
|
||||||
<view class="info">
|
<view class="info">
|
||||||
<image :src="list.avatar || '/static/image/nv.png'" mode=""></image>
|
<image :src="list.avatar || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/nv.png'" mode=""></image>
|
||||||
<view class="name">
|
<view class="name">
|
||||||
<text>{{list.name}}</text>
|
<text>{{list.name}}</text>
|
||||||
<text>药师</text>
|
<text>药师</text>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container safe-area-inset-bottom" :style="{ height: screenHeight }">
|
<view class="container safe-area-inset-bottom" :style="{ height: screenHeight }">
|
||||||
<image class="image" src="/static/login/bg_shh.png" mode=""></image>
|
<image class="image" src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/login/bg_shh.png' mode=""></image>
|
||||||
<view class="bg">
|
<view class="bg">
|
||||||
<view class="img">
|
<view class="img">
|
||||||
<image :src="doctorInfo.DoctorInfo.avatar || doctorInfo.avatar" mode=""></image>
|
<image :src="doctorInfo.DoctorInfo.avatar || doctorInfo.avatar" mode=""></image>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
{{doctorInfo.store.store.name || doctorInfo.store.name}}
|
{{doctorInfo.store.store.name || doctorInfo.store.name}}
|
||||||
{{doctorInfo.DoctorInfo.depart.name || doctorInfo.depart.name || ''}}
|
{{doctorInfo.DoctorInfo.depart.name || doctorInfo.depart.name || ''}}
|
||||||
</view>
|
</view>
|
||||||
<image src="/static/login/bg_shhz.png" mode=""></image>
|
<image src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/login/bg_shhz.png' mode=""></image>
|
||||||
<!-- doctorInfo.DoctorInfo.user.status<=1 -->
|
<!-- doctorInfo.DoctorInfo.user.status<=1 -->
|
||||||
<view class="state" v-if="role==1">
|
<view class="state" v-if="role==1">
|
||||||
{{ doctorInfo.DoctorInfo.user.status<=1&&'您的申请已提交,请耐心等待!感谢您的配合!' ||doctorInfo.DoctorInfo.user.status==3&&'抱歉您的申请已被驳回!'}}
|
{{ doctorInfo.DoctorInfo.user.status<=1&&'您的申请已提交,请耐心等待!感谢您的配合!' ||doctorInfo.DoctorInfo.user.status==3&&'抱歉您的申请已被驳回!'}}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="box_info_right flex-col flex-jus-sp flex-ali-center"
|
<view class="box_info_right flex-col flex-jus-sp flex-ali-center"
|
||||||
@click="$go('../../subPackages/sub_workbench/workbench_code')">
|
@click="$go('../../subPackages/sub_workbench/workbench_code')">
|
||||||
<image src="../../static/image/mp.png"></image>
|
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/mp.png"></image>
|
||||||
<d-text text="我的名片" className="w-c fs-2"></d-text>
|
<d-text text="我的名片" className="w-c fs-2"></d-text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -45,6 +45,8 @@
|
|||||||
|
|
||||||
<view class="header-spacer" :style="{ height: headerSpacerHeight + 'px' }"></view>
|
<view class="header-spacer" :style="{ height: headerSpacerHeight + 'px' }"></view>
|
||||||
|
|
||||||
|
<public-account-lock-banner ref="papLockBanner" />
|
||||||
|
|
||||||
<view class="content">
|
<view class="content">
|
||||||
<!-- 看板统计 -->
|
<!-- 看板统计 -->
|
||||||
<view class="dashboard-panel">
|
<view class="dashboard-panel">
|
||||||
@@ -95,6 +97,36 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- AI 今日简报:系统配置关闭时不挂载,避免进工作台就打生成接口 -->
|
||||||
|
<ai-brief-card v-if="aiBriefEnabled" ref="aiBrief" :store-id="store_id" @popup-change="onBriefPopupChange"></ai-brief-card>
|
||||||
|
|
||||||
|
<!-- 未来7天预约卡片:点击总数或某天打开明细抽屉 -->
|
||||||
|
<view class="appt-panel">
|
||||||
|
<view class="appt-panel__head" @click="openApptDrawer('')">
|
||||||
|
<view class="appt-panel__title-wrap">
|
||||||
|
<text class="appt-panel__title">未来7天预约</text>
|
||||||
|
<text class="appt-panel__sub">含今天</text>
|
||||||
|
</view>
|
||||||
|
<view class="appt-panel__total-wrap">
|
||||||
|
<text class="appt-panel__total">{{ appointmentUpcoming.total }}</text>
|
||||||
|
<text class="appt-panel__unit">个</text>
|
||||||
|
<u-icon name="arrow-right" color="#C0C4CC" size="26"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="appt-panel__days">
|
||||||
|
<view
|
||||||
|
v-for="day in upcomingDayList"
|
||||||
|
:key="day.date"
|
||||||
|
class="appt-panel__day"
|
||||||
|
:class="{ 'appt-panel__day--active': day.count > 0 }"
|
||||||
|
@click="openApptDrawer(day.date)"
|
||||||
|
>
|
||||||
|
<text class="appt-panel__day-label">{{ day.label }}</text>
|
||||||
|
<text class="appt-panel__day-count">{{ day.count }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="function p-32" v-if="onlineConsultationConfigReady && canUseOnlineConsultation">
|
<view class="function p-32" v-if="onlineConsultationConfigReady && canUseOnlineConsultation">
|
||||||
<view class="function_grid">
|
<view class="function_grid">
|
||||||
<view
|
<view
|
||||||
@@ -106,8 +138,8 @@
|
|||||||
<image :src="item.icon"></image>
|
<image :src="item.icon"></image>
|
||||||
<view>{{ item.name }}</view>
|
<view>{{ item.name }}</view>
|
||||||
<u-badge
|
<u-badge
|
||||||
:count="i === 0 ? doctorInfo.wait_accept : 0"
|
:count="offlineWaitAccept"
|
||||||
v-if="i === 0"
|
v-if="i === 0 && offlineWaitAccept > 0"
|
||||||
:offset="[-6, 98]"
|
:offset="[-6, 98]"
|
||||||
bgColor="#F44336"
|
bgColor="#F44336"
|
||||||
></u-badge>
|
></u-badge>
|
||||||
@@ -125,8 +157,8 @@
|
|||||||
<text class="offline-entry-desc">到店开方、处理线下挂号</text>
|
<text class="offline-entry-desc">到店开方、处理线下挂号</text>
|
||||||
</view>
|
</view>
|
||||||
<u-badge
|
<u-badge
|
||||||
v-if="doctorInfo.wait_accept > 0"
|
v-if="offlineWaitAccept > 0"
|
||||||
:count="doctorInfo.wait_accept"
|
:count="offlineWaitAccept"
|
||||||
:offset="[-8, 8]"
|
:offset="[-8, 8]"
|
||||||
bgColor="#F44336"
|
bgColor="#F44336"
|
||||||
></u-badge>
|
></u-badge>
|
||||||
@@ -149,7 +181,7 @@
|
|||||||
>
|
>
|
||||||
<view class="wb-avatar-wrap">
|
<view class="wb-avatar-wrap">
|
||||||
<u-image width="80rpx" height="80rpx" shape="circle"
|
<u-image width="80rpx" height="80rpx" shape="circle"
|
||||||
:src="item.user_patient && item.user_patient.avatar || require(`../../static/image/${item.user_patient && item.user_patient.sex%2==0?'nv':'nan'}.png`)">
|
:src="item.user_patient && item.user_patient.avatar || ('https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/' + (item.user_patient && item.user_patient.sex%2==0?'nv':'nan') + '.png')">
|
||||||
<u-loading slot="loading"></u-loading>
|
<u-loading slot="loading"></u-loading>
|
||||||
</u-image>
|
</u-image>
|
||||||
<u-badge
|
<u-badge
|
||||||
@@ -242,7 +274,7 @@
|
|||||||
<view class="news_item flat-card flex-row flex-ali-start"
|
<view class="news_item flat-card flex-row flex-ali-start"
|
||||||
@click="$go('../../subPackages/sub_patient/patient_label?type=1')" v-if="systemList.num>0">
|
@click="$go('../../subPackages/sub_patient/patient_label?type=1')" v-if="systemList.num>0">
|
||||||
<view class="wb-avatar-wrap">
|
<view class="wb-avatar-wrap">
|
||||||
<u-image width="80rpx" height="80rpx" shape="circle" src="/static/image/home1.png">
|
<u-image width="80rpx" height="80rpx" shape="circle" src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/home1.png'>
|
||||||
<u-loading slot="loading"></u-loading>
|
<u-loading slot="loading"></u-loading>
|
||||||
</u-image>
|
</u-image>
|
||||||
<u-badge :count="systemList.num" v-if="systemList.num>0" :offset="[-4,-4]"
|
<u-badge :count="systemList.num" v-if="systemList.num>0" :offset="[-4,-4]"
|
||||||
@@ -266,7 +298,7 @@
|
|||||||
<view class="news_item flat-card flex-row flex-ali-start"
|
<view class="news_item flat-card flex-row flex-ali-start"
|
||||||
@click="$go('../../subPackages/sub_patient/patient_label?type=2')" v-if="infoList.num>0">
|
@click="$go('../../subPackages/sub_patient/patient_label?type=2')" v-if="infoList.num>0">
|
||||||
<view class="wb-avatar-wrap">
|
<view class="wb-avatar-wrap">
|
||||||
<u-image width="80rpx" height="80rpx" shape="circle" :src="'/static/image/home2.png'">
|
<u-image width="80rpx" height="80rpx" shape="circle" :src="'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/home2.png'">
|
||||||
<u-loading slot="loading"></u-loading>
|
<u-loading slot="loading"></u-loading>
|
||||||
</u-image>
|
</u-image>
|
||||||
<u-badge :count="infoList.num" v-if="infoList.num>0" :offset="[-4,-4]" bgColor="#F44336"
|
<u-badge :count="infoList.num" v-if="infoList.num>0" :offset="[-4,-4]" bgColor="#F44336"
|
||||||
@@ -291,7 +323,7 @@
|
|||||||
v-if="!loading&&systemList.length==0"
|
v-if="!loading&&systemList.length==0"
|
||||||
@click="$go('../../subPackages/sub_patient/patient_label?type=1')">
|
@click="$go('../../subPackages/sub_patient/patient_label?type=1')">
|
||||||
<view class="wb-avatar-wrap">
|
<view class="wb-avatar-wrap">
|
||||||
<u-image width="80rpx" height="80rpx" shape="circle" src="/static/image/home1.png">
|
<u-image width="80rpx" height="80rpx" shape="circle" src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/home1.png'>
|
||||||
<u-loading slot="loading"></u-loading>
|
<u-loading slot="loading"></u-loading>
|
||||||
</u-image>
|
</u-image>
|
||||||
</view>
|
</view>
|
||||||
@@ -313,7 +345,7 @@
|
|||||||
<view class="news_item flat-card flex-row flex-ali-start" v-if="!loading&&infoList.length==0"
|
<view class="news_item flat-card flex-row flex-ali-start" v-if="!loading&&infoList.length==0"
|
||||||
@click="$go('../../subPackages/sub_patient/patient_label?type=2')">
|
@click="$go('../../subPackages/sub_patient/patient_label?type=2')">
|
||||||
<view class="wb-avatar-wrap">
|
<view class="wb-avatar-wrap">
|
||||||
<u-image width="80rpx" height="80rpx" shape="circle" :src="'/static/image/home2.png'">
|
<u-image width="80rpx" height="80rpx" shape="circle" :src="'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/home2.png'">
|
||||||
<u-loading slot="loading"></u-loading>
|
<u-loading slot="loading"></u-loading>
|
||||||
</u-image>
|
</u-image>
|
||||||
</view>
|
</view>
|
||||||
@@ -335,7 +367,7 @@
|
|||||||
<view v-if="noticeList.length" class="news_item flat-card flex-row flex-ali-start"
|
<view v-if="noticeList.length" class="news_item flat-card flex-row flex-ali-start"
|
||||||
@click="$go('/subPackages/sub_workbench/prescriptionList')">
|
@click="$go('/subPackages/sub_workbench/prescriptionList')">
|
||||||
<view class="wb-avatar-wrap">
|
<view class="wb-avatar-wrap">
|
||||||
<u-image width="80rpx" height="80rpx" shape="circle" src="/static/image/home1.png">
|
<u-image width="80rpx" height="80rpx" shape="circle" src='https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/home1.png'>
|
||||||
<u-loading slot="loading"></u-loading>
|
<u-loading slot="loading"></u-loading>
|
||||||
</u-image>
|
</u-image>
|
||||||
<u-badge :count="noticeList.length" :offset="[-4,-4]"
|
<u-badge :count="noticeList.length" :offset="[-4,-4]"
|
||||||
@@ -387,8 +419,18 @@
|
|||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
|
||||||
|
<!-- 未来7天预约明细抽屉(page-container + v-if 由组件内部处理);改期成功后静默刷新看板统计 -->
|
||||||
|
<appointment-upcoming-drawer
|
||||||
|
v-model="apptDrawerShow"
|
||||||
|
role="doctor"
|
||||||
|
:days="upcomingDayList"
|
||||||
|
:default-date="apptDefaultDate"
|
||||||
|
@rescheduled="onAppointmentRescheduled"
|
||||||
|
></appointment-upcoming-drawer>
|
||||||
|
|
||||||
<d-top-message ref="topMessage"></d-top-message>
|
<d-top-message ref="topMessage"></d-top-message>
|
||||||
<d-tabbar></d-tabbar>
|
<!-- 简报弹层在 content 内,层级低于自定义 tabbar;打开时隐藏 tabbar,避免底部按钮被挡住 -->
|
||||||
|
<d-tabbar v-show="!briefPopupShow"></d-tabbar>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -402,15 +444,13 @@ import {
|
|||||||
homeSession,
|
homeSession,
|
||||||
getNotice
|
getNotice
|
||||||
} from '@/api/all.js'
|
} from '@/api/all.js'
|
||||||
import {
|
|
||||||
registerList
|
|
||||||
} from '../../api/consult';
|
|
||||||
import {
|
import {
|
||||||
getAcceptingPatientList as fetchAcceptingPatientListApi,
|
getAcceptingPatientList as fetchAcceptingPatientListApi,
|
||||||
getOnlineConsultationPatientListApi,
|
getOnlineConsultationPatientListApi,
|
||||||
getWorkbenchStats,
|
getWorkbenchStats,
|
||||||
} from '../../api/reception';
|
} from '../../api/reception';
|
||||||
import { checkOnlineConsultationConfigApi } from '../../api/storeConsultation.js';
|
import { checkOnlineConsultationConfigApi } from '../../api/storeConsultation.js';
|
||||||
|
import PublicAccountLockBanner from '@/components/public-account-lock-banner/public-account-lock-banner.vue';
|
||||||
|
|
||||||
/** 房间 ID 归一化(避免引入 chatRoomManager 整模块) */
|
/** 房间 ID 归一化(避免引入 chatRoomManager 整模块) */
|
||||||
function normalizeRoomId(id) {
|
function normalizeRoomId(id) {
|
||||||
@@ -441,12 +481,12 @@ function formatSessionRelativeTime(item) {
|
|||||||
const WORKBENCH_STATS_RANGE_KEY = 'workbench_stats_range';
|
const WORKBENCH_STATS_RANGE_KEY = 'workbench_stats_range';
|
||||||
const OFFLINE_RECEPTION_ENTRY = {
|
const OFFLINE_RECEPTION_ENTRY = {
|
||||||
name: '线下接诊',
|
name: '线下接诊',
|
||||||
icon: '../../static/image/zxzx.png',
|
icon: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/zxzx.png',
|
||||||
url: '../../subPackages/sub_workbench/workbench_consult?type=1',
|
url: '../../subPackages/sub_workbench/workbench_consult?type=1',
|
||||||
};
|
};
|
||||||
const ONLINE_RECEPTION_ENTRY = {
|
const ONLINE_RECEPTION_ENTRY = {
|
||||||
name: '在线接诊',
|
name: '在线接诊',
|
||||||
icon: '../../static/image/xsfz.png',
|
icon: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-doctor-wx/static/image/xsfz.png',
|
||||||
url: '../../subPackages/sub_online_reception/pages/list',
|
url: '../../subPackages/sub_online_reception/pages/list',
|
||||||
};
|
};
|
||||||
const RECEPTION_GRID_ENTRIES = [OFFLINE_RECEPTION_ENTRY, ONLINE_RECEPTION_ENTRY];
|
const RECEPTION_GRID_ENTRIES = [OFFLINE_RECEPTION_ENTRY, ONLINE_RECEPTION_ENTRY];
|
||||||
@@ -459,6 +499,9 @@ const EMPTY_WORKBENCH_STATS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
PublicAccountLockBanner,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
noticeList: [],
|
noticeList: [],
|
||||||
@@ -486,7 +529,7 @@ export default {
|
|||||||
name: '已结束',
|
name: '已结束',
|
||||||
color: '#C4C7CC'
|
color: '#C4C7CC'
|
||||||
},
|
},
|
||||||
3: {
|
4: {
|
||||||
name: '已取消',
|
name: '已取消',
|
||||||
color: '#C4C7CC'
|
color: '#C4C7CC'
|
||||||
}
|
}
|
||||||
@@ -529,6 +572,14 @@ export default {
|
|||||||
workbenchStats: { ...EMPTY_WORKBENCH_STATS },
|
workbenchStats: { ...EMPTY_WORKBENCH_STATS },
|
||||||
statsLoading: false,
|
statsLoading: false,
|
||||||
statsInitialized: false,
|
statsInitialized: false,
|
||||||
|
// 未来7天预约统计(医生本人维度,跨门店;随 workbench-stats 一起返回)
|
||||||
|
appointmentUpcoming: { total: 0, days: [] },
|
||||||
|
apptDrawerShow: false,
|
||||||
|
apptDefaultDate: '',
|
||||||
|
// AI 简报全文弹层是否打开(打开时隐藏自定义 tabbar,避免抽屉被挡住)
|
||||||
|
briefPopupShow: false,
|
||||||
|
// 系统配置总开关:默认不展示,看板接口确认开启后再挂载简报卡
|
||||||
|
aiBriefEnabled: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -554,6 +605,20 @@ export default {
|
|||||||
const contentPx = typeof uni.upx2px === 'function' ? uni.upx2px(312) : 156;
|
const contentPx = typeof uni.upx2px === 'function' ? uni.upx2px(312) : 156;
|
||||||
return this.statusBarHeight + this.navbarHeight + contentPx;
|
return this.statusBarHeight + this.navbarHeight + contentPx;
|
||||||
},
|
},
|
||||||
|
/** 未来7天 pill:接口偶发不带 days 时给空数组,避免渲染层对 undefined 做迭代报错 */
|
||||||
|
upcomingDayList() {
|
||||||
|
const days = this.appointmentUpcoming && this.appointmentUpcoming.days;
|
||||||
|
return Array.isArray(days) ? days : [];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 线下接诊角标:优先用今日看板(已剔除未到就诊日的预约),避免 Yii info 把后天预约算进来
|
||||||
|
*/
|
||||||
|
offlineWaitAccept() {
|
||||||
|
const todayStats = this.statsCache && this.statsCache.today;
|
||||||
|
if (todayStats) return Number(todayStats.wait_accept) || 0;
|
||||||
|
if (this.statsRange === 'today') return Number(this.workbenchStats.wait_accept) || 0;
|
||||||
|
return Number(this.doctorInfo && this.doctorInfo.wait_accept) || 0;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.initStatsRangeFromStorage();
|
this.initStatsRangeFromStorage();
|
||||||
@@ -601,6 +666,11 @@ export default {
|
|||||||
},
|
},
|
||||||
refreshWorkbenchData() {
|
refreshWorkbenchData() {
|
||||||
const silentStats = this.statsInitialized || this.hasStatsCache(this.statsRange);
|
const silentStats = this.statsInitialized || this.hasStatsCache(this.statsRange);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.papLockBanner && this.$refs.papLockBanner.refresh) {
|
||||||
|
this.$refs.papLockBanner.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.getNotice(),
|
this.getNotice(),
|
||||||
this.getInfo(),
|
this.getInfo(),
|
||||||
@@ -608,7 +678,18 @@ export default {
|
|||||||
this.getList(),
|
this.getList(),
|
||||||
this.getAcceptingPatientList(),
|
this.getAcceptingPatientList(),
|
||||||
this.fetchOnlineConsultationConfig(),
|
this.fetchOnlineConsultationConfig(),
|
||||||
this.fetchWorkbenchStats({ silent: silentStats })
|
this.fetchWorkbenchStats({ silent: silentStats }).then(() => {
|
||||||
|
// 先确认开关再拉简报:关闭时不请求,避免无谓生成
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.aiBriefEnabled && this.$refs.aiBrief) {
|
||||||
|
this.$refs.aiBrief.fetchBrief(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 角标用今日待接诊;当前看板不是「今日」时补拉一份 today 缓存
|
||||||
|
if (this.statsRange !== 'today') {
|
||||||
|
return this.fetchWorkbenchStats({ silent: true, range: 'today' });
|
||||||
|
}
|
||||||
|
})
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
this.notifyWorkbenchRefreshed();
|
this.notifyWorkbenchRefreshed();
|
||||||
});
|
});
|
||||||
@@ -811,6 +892,19 @@ export default {
|
|||||||
if (Number.isNaN(num)) return '0.000'
|
if (Number.isNaN(num)) return '0.000'
|
||||||
return num.toFixed(3)
|
return num.toFixed(3)
|
||||||
},
|
},
|
||||||
|
/** AI 简报弹层开合:打开时隐藏自定义 tabbar,避免抽屉底部「知道了」被挡住 */
|
||||||
|
onBriefPopupChange(show) {
|
||||||
|
this.briefPopupShow = !!show;
|
||||||
|
},
|
||||||
|
/** 打开未来7天预约明细抽屉;date 为空看全部,点某天卡格预筛选该天 */
|
||||||
|
openApptDrawer(date) {
|
||||||
|
this.apptDefaultDate = date || '';
|
||||||
|
this.apptDrawerShow = true;
|
||||||
|
},
|
||||||
|
/** 抽屉内改期成功:预约分布/待接诊数已变化,静默刷新看板(不打断用户继续操作抽屉) */
|
||||||
|
onAppointmentRescheduled() {
|
||||||
|
this.fetchWorkbenchStats({ silent: true });
|
||||||
|
},
|
||||||
fetchWorkbenchStats(options = {}) {
|
fetchWorkbenchStats(options = {}) {
|
||||||
const silent = options.silent === true;
|
const silent = options.silent === true;
|
||||||
const range = options.range || this.statsRange;
|
const range = options.range || this.statsRange;
|
||||||
@@ -829,6 +923,15 @@ export default {
|
|||||||
if (range === this.statsRange) {
|
if (range === this.statsRange) {
|
||||||
this.workbenchStats = { ...stats };
|
this.workbenchStats = { ...stats };
|
||||||
}
|
}
|
||||||
|
// 未来7天预约统计不随 range 变化(医生本人全量口径),有值就更新
|
||||||
|
if (payload.appointment_upcoming) {
|
||||||
|
this.appointmentUpcoming = {
|
||||||
|
total: payload.appointment_upcoming.total || 0,
|
||||||
|
days: payload.appointment_upcoming.days || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 简报总开关随看板下发,关闭则卸掉卡片
|
||||||
|
this.aiBriefEnabled = payload.ai_daily_brief_enabled === true || payload.ai_daily_brief_enabled === 1;
|
||||||
this.statsInitialized = true;
|
this.statsInitialized = true;
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
@@ -980,7 +1083,7 @@ export default {
|
|||||||
},
|
},
|
||||||
acceptingStatusLabel(item) {
|
acceptingStatusLabel(item) {
|
||||||
const st = Number(item.status);
|
const st = Number(item.status);
|
||||||
const m = { 1: '待接诊', 2: '接诊中', 3: '已结束' };
|
const m = { 1: '待接诊', 2: '接诊中', 3: '已结束', 4: '已取消', 7: '已拒诊' };
|
||||||
return m[st] || '接诊中';
|
return m[st] || '接诊中';
|
||||||
},
|
},
|
||||||
acceptingStatusColor(item) {
|
acceptingStatusColor(item) {
|
||||||
@@ -1197,6 +1300,95 @@ page {
|
|||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 未来7天预约卡片 */
|
||||||
|
.appt-panel {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
&__head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sub {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__total-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__total {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6acdbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__unit {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin: 0 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__days {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #f7f8fa;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
background: rgba(106, 205, 187, 0.12);
|
||||||
|
|
||||||
|
.appt-panel__day-count {
|
||||||
|
color: #6acdbb;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day-label {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #6c7380;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day-count {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #c0c4cc;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-tabs {
|
.dashboard-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 351 B |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 679 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 692 B |
|
Before Width: | Height: | Size: 931 B |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 837 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 811 B |
|
Before Width: | Height: | Size: 650 B |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 812 B |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |