feat: 首页按后台配置展示订单提醒,挂号详情支持自行改期

首页金刚区和订单提醒样式走配置下发,消息页改为四入口宫格;挂号详情可改期并展示轨迹,取消态展示与支付取值统一按 xk-api 的 code/result。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
李琦
2026-08-18 08:53:15 +08:00
parent cc252b5d1a
commit 8dd1f5cca5
15 changed files with 1038 additions and 265 deletions

View File

@@ -112,8 +112,9 @@ export default {
method: 'post',
data: { register_id: registerId },
})
if (res.data && (res.data.errcode == 0 || res.data.errcode === '0')) {
const data = res.data.data || {}
// registePay 为 xk-api按 code/result/message 取值
if (res.data && (res.data.code == 0 || res.data.code === '0')) {
const data = res.data.result || {}
if (data.is_paid == 1 || data.is_pay == 1) {
uni.showToast({ title: '认领成功', icon: 'success' })
return
@@ -134,7 +135,7 @@ export default {
})
} else {
uni.showToast({
title: (res.data && (res.data.message || res.data.msg)) || '获取支付参数失败',
title: (res.data && res.data.message) || '获取支付参数失败',
icon: 'none',
})
}

View File

@@ -54,7 +54,15 @@
<view v-if="homeZonesInitialLoading" class="zone-wrapper">
<ListSkeleton type="zone" :rows="5" />
</view>
<view v-else-if="homeZones.length > 0" class="zone-wrapper" :class="zoneLayoutMode === 'scroll' ? 'zone-scroll-mode' : 'zone-wrap-mode'">
<view
v-else-if="homeZones.length > 0"
class="zone-wrapper"
:class="{
'zone-scroll-mode': zoneLayoutMode === 'scroll',
'zone-wrap-mode': zoneLayoutMode === 'wrap',
'zone-wrap2-mode': zoneLayoutMode === 'wrap2'
}"
>
<view v-for="(item, index) in homeZones" :key="index" class="zone-item" @click="goZoneList(item)">
<view class="zone-icon-box">
<image
@@ -105,24 +113,59 @@
</view>
</view>
<!-- 待支付挂号提醒 -->
<!-- 订单提醒后台可配一行两列卡片 / 金刚区图标 -->
<view
v-if="unpaidRegisterCount > 0"
class="unpaid-banner unpaid-banner--register"
@click="onUnpaidRegisterBannerClick"
v-if="visibleOrderReminds.length > 0 && orderRemindStyle === 'grid'"
class="order-remind-grid"
>
<text class="unpaid-banner-text">您有 {{ unpaidRegisterCount }} 个待支付挂号订单点击前往查看</text>
<text class="iconfont icon-jiantou1 unpaid-banner-arrow"></text>
<view
v-for="item in visibleOrderReminds"
:key="item.key"
class="order-remind-card"
:class="{
'order-remind-card--register': item.key === 'unpaid_register',
'order-remind-card--product': item.key === 'unpaid_product',
'order-remind-card--shipped': item.key === 'shipped',
'order-remind-card--arrived': item.key === 'arrived'
}"
@click="onOrderRemindTap(item)"
>
<view class="order-remind-card-main">
<text class="order-remind-card-title">{{ item.title }}</text>
<text class="order-remind-card-count">{{ item.count }}</text>
</view>
<text class="iconfont icon-jiantou1 order-remind-card-arrow"></text>
</view>
</view>
<!-- 待支付商品订单提醒 -->
<view
v-if="unpaidProductOrderCount > 0"
class="unpaid-banner unpaid-banner--product"
@click="onUnpaidProductOrderBannerClick"
v-else-if="visibleOrderReminds.length > 0"
class="zone-wrapper order-remind-zone"
:class="{
'zone-scroll-mode': zoneLayoutMode === 'scroll',
'zone-wrap-mode': zoneLayoutMode === 'wrap',
'zone-wrap2-mode': zoneLayoutMode === 'wrap2'
}"
>
<text class="unpaid-banner-text">您有 {{ unpaidProductOrderCount }} 个待支付商品订单点击前往查看</text>
<text class="iconfont icon-jiantou1 unpaid-banner-arrow"></text>
<view
v-for="item in visibleOrderReminds"
:key="item.key"
class="zone-item"
@click="onOrderRemindTap(item)"
>
<view
class="zone-icon-box order-remind-zone-icon"
:class="{
'order-remind-zone-icon--register': item.key === 'unpaid_register',
'order-remind-zone-icon--product': item.key === 'unpaid_product',
'order-remind-zone-icon--shipped': item.key === 'shipped',
'order-remind-zone-icon--arrived': item.key === 'arrived'
}"
>
<text :class="'iconfont ' + item.icon"></text>
<text v-if="item.count > 0" class="order-remind-badge">{{ item.count }}</text>
</view>
<text class="zone-text">{{ item.title }}</text>
</view>
</view>
<!-- 4. 诊所模式内容特色药方 -->
@@ -324,12 +367,18 @@ import {
storeChange,
storeInfo,
storeList,
fetchUnpaidProductOrderListApi,
} from '../../request/api/api'
import { getPlatformQualificationsApi } from '../../request/api/platform'
import { getStoreDrugListBySalespersonApi, getHomeTopProductsApi, getHomeZonesApi, checkOnlineConsultationConfigApi } from "@/request/api/product";
import {
getStoreDrugListBySalespersonApi,
getHomeTopProductsApi,
getHomeZonesApi,
getHomeDisplayConfigApi,
getHomeOrderRemindApi,
checkOnlineConsultationConfigApi,
} from "@/request/api/product";
import { getSpecialPrescriptionHomeApi } from '@/request/api/specialPrescription';
import { fetchUnpaidRegisterListApi } from '@/request/api/register';
import { unwrapXkApi } from '@/utils/api-response.js';
import request from "@/request/api/request";
import MessageNotification from "@/components/MessageNotification/MessageNotification.vue";
import ClaimPatientModal from "@/components/claim-patient-modal/claim-patient-modal.vue";
@@ -375,13 +424,11 @@ export default {
statusBarHeight: 20,
isSticky: false,
// 控制金刚区布局模式: 'scroll' (滑动) 或 'wrap' (换行)
// 金刚区布局scroll 横滑 / wrap 一行五个 / wrap2 一行两个(后台可配)
zoneLayoutMode: 'scroll',
unpaidRegisterCount: 0,
unpaidRegisterList: [],
unpaidProductOrderCount: 0,
unpaidProductOrderList: [],
// 订单提醒grid 一行两列 / zone 金刚区(后台可配)
orderRemindStyle: 'grid',
orderRemindItems: [],
// 特色药方API
specialPrescription: {
@@ -402,7 +449,11 @@ export default {
// 判断是否为guest模式用于模板
isGuestMode() {
return isGuestMode;
}
},
// 只展示有数量的订单提醒,避免空宫格占位
visibleOrderReminds() {
return (this.orderRemindItems || []).filter((item) => item && item.count > 0);
},
},
onLoad() {
@@ -458,53 +509,85 @@ export default {
}
},
methods: {
async fetchUnpaidCounts() {
/**
* 拉取首页展示样式(游客/登录都要,金刚区布局跟配置走)
*/
async fetchHomeDisplayConfig() {
try {
const res = await getHomeDisplayConfigApi();
const { ok, payload } = unwrapXkApi(res);
if (!ok || !payload) {
return;
}
if (payload.zone_layout === 'scroll' || payload.zone_layout === 'wrap' || payload.zone_layout === 'wrap2') {
this.zoneLayoutMode = payload.zone_layout;
}
if (payload.order_remind_style === 'grid' || payload.order_remind_style === 'zone') {
this.orderRemindStyle = payload.order_remind_style;
}
} catch (e) {
console.error('fetchHomeDisplayConfig', e);
}
},
/**
* 首页订单提醒:待支付挂号/商品、已发货、已到货待确认
* 未登录不请求,避免游客打到需登录接口
*/
async fetchOrderReminds() {
if (!isLoggedIn()) {
this.unpaidRegisterCount = 0;
this.unpaidRegisterList = [];
this.unpaidProductOrderCount = 0;
this.unpaidProductOrderList = [];
this.orderRemindItems = [];
return;
}
try {
const [registerList, productList] = await Promise.all([
fetchUnpaidRegisterListApi(),
fetchUnpaidProductOrderListApi(),
]);
this.unpaidRegisterList = registerList;
this.unpaidRegisterCount = registerList.length;
this.unpaidProductOrderList = productList;
this.unpaidProductOrderCount = productList.length;
const res = await getHomeOrderRemindApi();
const { ok, payload } = unwrapXkApi(res);
if (!ok || !payload) {
this.orderRemindItems = [];
return;
}
if (payload.zone_layout === 'scroll' || payload.zone_layout === 'wrap' || payload.zone_layout === 'wrap2') {
this.zoneLayoutMode = payload.zone_layout;
}
if (payload.order_remind_style === 'grid' || payload.order_remind_style === 'zone') {
this.orderRemindStyle = payload.order_remind_style;
}
this.orderRemindItems = payload.items || [];
} catch (e) {
console.error('fetchUnpaidCounts', e);
this.unpaidRegisterCount = 0;
this.unpaidRegisterList = [];
this.unpaidProductOrderCount = 0;
this.unpaidProductOrderList = [];
console.error('fetchOrderReminds', e);
this.orderRemindItems = [];
}
},
onUnpaidRegisterBannerClick() {
if (this.unpaidRegisterCount === 1 && this.unpaidRegisterList[0]) {
const id = this.unpaidRegisterList[0].id;
/**
* 订单提醒点击1 条进详情,多条进对应列表
* 已发货/待确认都落在商品订单「待收货」tab
*/
onOrderRemindTap(item) {
if (!item) {
return;
}
const count = Number(item.count) || 0;
const firstId = Number(item.first_id) || 0;
if (item.key === 'unpaid_register') {
if (count === 1 && firstId > 0) {
uni.navigateTo({
url: `/subPackages/doctor/doctor-userinfo?step=2&register_id=${firstId}`,
});
return;
}
uni.navigateTo({
url: `/subPackages/doctor/doctor-userinfo?step=2&register_id=${id}`,
url: '/subPackages/my/myappiont',
});
return;
}
uni.navigateTo({
url: '/subPackages/my/myappiont',
});
},
onUnpaidProductOrderBannerClick() {
if (this.unpaidProductOrderCount === 1 && this.unpaidProductOrderList[0]) {
const id = this.unpaidProductOrderList[0].id;
if (count === 1 && firstId > 0) {
uni.navigateTo({
url: `/subPackages/my/my-drug/drug-info?id=${id}`,
url: `/subPackages/my/my-drug/drug-info?id=${firstId}`,
});
return;
}
const tabIndex = item.key === 'unpaid_product' ? 1 : 3;
uni.navigateTo({
url: '/subPackages/my/my-drug/drug-list?index=1',
url: `/subPackages/my/my-drug/drug-list?index=${tabIndex}`,
});
},
// 处理购物车按钮点击
@@ -834,6 +917,8 @@ export default {
this._productFetchDone = 0;
return Promise.all([
this.getHomeZones(),
this.fetchHomeDisplayConfig(),
this.fetchOrderReminds(),
this.getList(),
this.getProductList(),
this.getHomeTopProducts(),
@@ -963,7 +1048,8 @@ export default {
this.getHomeZones();
this.getPlatformQualifications(); // 已补回
this.getDefaultDoctorId();
this.fetchUnpaidCounts();
this.fetchHomeDisplayConfig();
this.fetchOrderReminds();
this.fetchSpecialPrescriptionHome();
this.openClaimPatientModal();
this.openProxyPayConfirmModal();
@@ -1050,13 +1136,20 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
.zone-item { width: 140rpx; margin-right: 10rpx; }
}
/* 模式2: 换行 */
/* 模式2: 换行(一行五个) */
&.zone-wrap-mode {
flex-wrap: wrap;
justify-content: flex-start;
.zone-item { width: 20%; margin-bottom: 24rpx; }
}
/* 模式3: 一行两个 */
&.zone-wrap2-mode {
flex-wrap: wrap;
justify-content: flex-start;
.zone-item { width: 50%; margin-bottom: 24rpx; }
}
.zone-item {
flex-shrink: 0; text-align: center; display: flex; flex-direction: column; align-items: center;
&:active { transform: scale(0.95); }
@@ -1089,44 +1182,77 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
}
}
.unpaid-banner {
.order-remind-grid {
display: flex;
flex-wrap: wrap;
padding: 0 32rpx 8rpx;
box-sizing: border-box;
}
.order-remind-card {
width: calc(50% - 10rpx);
margin: 0 20rpx 16rpx 0;
padding: 22rpx 20rpx;
border-radius: 16rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
margin: 16rpx 32rpx 0;
padding: 24rpx 28rpx;
border-radius: 16rpx;
&:nth-child(2n) { margin-right: 0; }
&:active { transform: scale(0.98); }
&--register {
background: linear-gradient(90deg, rgba(65, 117, 238, 0.08) 0%, rgba(65, 117, 238, 0.04) 100%);
border: 1rpx solid rgba(65, 117, 238, 0.2);
.unpaid-banner-text,
.unpaid-banner-arrow {
color: #4175EE;
}
border: 1rpx solid rgba(65, 117, 238, 0.3);
box-shadow: 0 0 12rpx rgba(65, 117, 238, 0.18);
background: rgba(65, 117, 238, 0.06);
.order-remind-card-title, .order-remind-card-count, .order-remind-card-arrow { color: #4175EE; }
}
&--product {
background: linear-gradient(90deg, rgba(0, 181, 120, 0.08) 0%, rgba(0, 181, 120, 0.04) 100%);
border: 1rpx solid rgba(0, 181, 120, 0.25);
.unpaid-banner-text,
.unpaid-banner-arrow {
color: #00B578;
border: 1rpx solid rgba(0, 181, 120, 0.3);
box-shadow: 0 0 12rpx rgba(0, 181, 120, 0.18);
background: rgba(0, 181, 120, 0.06);
.order-remind-card-title, .order-remind-card-count, .order-remind-card-arrow { color: #00B578; }
}
&--shipped {
border: 1rpx solid rgba(255, 140, 0, 0.3);
box-shadow: 0 0 12rpx rgba(255, 140, 0, 0.18);
background: rgba(255, 140, 0, 0.06);
.order-remind-card-title, .order-remind-card-count, .order-remind-card-arrow { color: #E67E00; }
}
&--arrived {
border: 1rpx solid rgba(139, 92, 246, 0.3);
box-shadow: 0 0 12rpx rgba(139, 92, 246, 0.18);
background: rgba(139, 92, 246, 0.06);
.order-remind-card-title, .order-remind-card-count, .order-remind-card-arrow { color: #7C3AED; }
}
.order-remind-card-main { flex: 1; min-width: 0; }
.order-remind-card-title { display: block; font-size: 24rpx; font-weight: 500; }
.order-remind-card-count { display: block; margin-top: 6rpx; font-size: 36rpx; font-weight: 800; line-height: 1.1; }
.order-remind-card-arrow { font-size: 22rpx; margin-left: 8rpx; }
}
.order-remind-zone {
padding-top: 8rpx;
.order-remind-zone-icon {
position: relative;
&--register { color: #4175EE; }
&--product { color: #00B578; }
&--shipped { color: #E67E00; }
&--arrived { color: #7C3AED; }
.iconfont { font-size: 48rpx; }
.order-remind-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
min-width: 28rpx;
height: 28rpx;
padding: 0 8rpx;
border-radius: 14rpx;
background: #FF4D4F;
color: #fff;
font-size: 18rpx;
line-height: 28rpx;
text-align: center;
box-sizing: border-box;
}
}
.unpaid-banner-text {
flex: 1;
font-size: 26rpx;
line-height: 1.5;
}
.unpaid-banner-arrow {
font-size: 24rpx;
margin-left: 12rpx;
}
}
.section-header {

View File

@@ -12,107 +12,43 @@
</view>
</view>
<!-- 首次加载四条固定入口用骨架避免 num 未定义时闪暂无新消息 -->
<ListSkeleton v-if="noticeInitialLoading" type="message" :rows="4" />
<template v-else>
<!-- 消息列表 -->
<view class="list" @click="goSystem">
<!-- 头像 -->
<view class="avator">
<view class="img">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/xt.png" mode=""></image>
</view>
<u-badge size="default" v-if="system.num>=0" :count="system.num" :is-dot="system.num==0?true:false"
type="error" :offset="system.num>0?[-4,-8]:[0,0]"></u-badge>
</view>
<!-- 描述 -->
<view class="info">
<view class="infos_title">
<text class="infos">系统消息</text>
<text class="time">{{ system.time }}</text>
</view>
<view class="preview" v-if="system.num >0">
您有{{ system.num }}条未读信息
</view>
<view class="preview" v-if="system.num==undefined">
暂无新消息
</view>
<!-- 首次加载宫格骨架避免 num 未定义时闪暂无新消息 -->
<view v-if="noticeInitialLoading" class="notice-grid">
<view v-for="i in 4" :key="i" class="notice-card notice-card--sk">
<view class="notice-sk-icon"></view>
<view class="notice-card__body">
<view class="notice-sk-line notice-sk-line--title"></view>
<view class="notice-sk-line"></view>
</view>
</view>
<view class="list" @click="goOrder">
<!-- 头像 -->
<view class="avator">
<view class="img">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cp.png" mode=""></image>
</view>
<u-badge size="default" v-if="order.num>=0" :count="order.num" :is-dot="order.num==0?true:false"
type="error" :offset="order.num>0?[-4,-8]:[0,0]"></u-badge>
</view>
<!-- 四条固定入口一行两列宫格会话列表仍走下方长条 -->
<view v-else class="notice-grid">
<view
v-for="item in noticeEntries"
:key="item.key"
class="notice-card"
hover-class="notice-card-hover"
@click="onNoticeTap(item.key)"
>
<view class="notice-card__icon">
<image :src="item.icon" mode="aspectFit"></image>
<u-badge
size="mini"
v-if="item.showBadge"
:count="item.num"
:is-dot="item.isDot"
type="error"
:offset="item.num > 0 ? [-4, -4] : [0, 0]"
></u-badge>
</view>
<!-- 描述 -->
<view class="info">
<view class="infos_title">
<text class="infos">产品订单</text>
<text class="time">{{ order.time }}</text>
</view>
<view class="preview" v-if="order.num>0">
您有{{ order.num }}条未读信息
</view>
<view class="preview" v-if="order.num==undefined">
暂无新消息
</view>
<view class="notice-card__body">
<text class="notice-card__title">{{ item.title }}</text>
<text class="notice-card__preview">{{ item.preview }}</text>
<text v-if="item.time" class="notice-card__time">{{ item.time }}</text>
</view>
</view>
<view class="list" @click="goDoctor">
<!-- 头像 -->
<view class="avator">
<view class="img">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png" mode=""></image>
</view>
<u-badge size="default" v-if="doctor.num>=0" :count="doctor.num" :is-dot="doctor.num==0?true:false"
type="error" :offset="doctor.num>0?[-4,-8]:[0,0]"></u-badge>
</view>
<!-- 描述 -->
<view class="info">
<view class="infos_title">
<text class="infos">医生消息</text>
<text class="time">{{ doctor.time }}</text>
</view>
<view class="preview" v-if="doctor.num>0">
您有{{ doctor.num }}条新的消息
</view>
<view class="preview" v-if="doctor.num==undefined">
暂无新消息
</view>
</view>
</view>
<!-- 转诊消息 -->
<view class="list" @click="goTransfer">
<!-- 头像 -->
<view class="avator">
<view class="img">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png" mode=""></image>
</view>
<u-badge size="default" v-if="transfer.num>=0" :count="transfer.num" :is-dot="transfer.num==0?true:false"
type="error" :offset="transfer.num>0?[-4,-8]:[0,0]"></u-badge>
</view>
<!-- 描述 -->
<view class="info">
<view class="infos_title">
<text class="infos">转诊消息</text>
<text class="time">{{ transfer.time }}</text>
</view>
<view class="preview" v-if="transfer.num>0">
您有{{ transfer.num }}条转诊消息待处理
</view>
<view class="preview" v-if="transfer.num==undefined || transfer.num==0">
暂无转诊消息
</view>
</view>
</view>
</template>
</view>
<!-- 标题 - 在线咨询 -->
<view v-if="checkDev('open-im') && onlineConsultationList.length > 0" class="title">
@@ -183,6 +119,9 @@
</template>
<script>
/**
* 患者端消息首页:顶部四入口宫格(系统/订单/医生/转诊)+ 下方 IM 会话列表
*/
import {
getseesionRead
} from '../../request/api/api'
@@ -214,6 +153,19 @@ export default {
chatInitialLoading: true
}
},
computed: {
/**
* 顶部宫格入口:文案/角标在这里算好,模板禁止方法调用和 ?.
*/
noticeEntries() {
return [
this.buildNoticeEntry('system', '系统消息', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/xt.png', this.system, '条未读信息', '暂无新消息'),
this.buildNoticeEntry('order', '产品订单', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cp.png', this.order, '条未读信息', '暂无新消息'),
this.buildNoticeEntry('doctor', '医生消息', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png', this.doctor, '条新的消息', '暂无新消息'),
this.buildNoticeEntry('transfer', '转诊消息', 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png', this.transfer, '条转诊消息待处理', '暂无转诊消息')
]
}
},
onLoad() {
// 监听聊天列表更新事件
uni.$on('chat-list-update', this.handleChatListUpdate);
@@ -225,7 +177,35 @@ export default {
},
methods: {
checkDev,
/**
* 组装单条宫格入口(未读数未回来时不展示角标,避免闪 0
*/
buildNoticeEntry(key, title, icon, raw, unreadSuffix, emptyText) {
const num = raw && raw.num
const hasNum = num !== undefined && num !== null
const unread = Number(num) || 0
let preview = emptyText
if (hasNum && unread > 0) {
preview = '您有' + unread + unreadSuffix
}
return {
key,
title,
icon,
num: unread,
time: (raw && raw.time) || '',
preview,
showBadge: hasNum,
isDot: unread === 0
}
},
/** 宫格点击:按 key 走原来的四个跳转 */
onNoticeTap(key) {
if (key === 'system') this.goSystem()
else if (key === 'order') this.goOrder()
else if (key === 'doctor') this.goDoctor()
else if (key === 'transfer') this.goTransfer()
},
/**
* 处理聊天列表更新事件
* 收到新消息时自动刷新列表
@@ -437,6 +417,108 @@ export default {
}
}
/* 顶部四入口:一行两列,卡片用同色细边框+微光(不用左侧色条) */
.notice-grid {
display: flex;
flex-wrap: wrap;
padding: 0 24rpx 8rpx;
}
.notice-card {
width: 343rpx;
margin: 0 16rpx 16rpx 0;
padding: 24rpx 20rpx;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: flex-start;
background: #fff;
border-radius: 20rpx;
border: 1px solid rgba(83, 109, 254, 0.28);
box-shadow: 0 0 6px rgba(83, 109, 254, 0.16);
}
.notice-card:nth-child(2n) {
margin-right: 0;
}
.notice-card-hover {
opacity: 0.88;
}
.notice-card__icon {
position: relative;
flex-shrink: 0;
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #E6EAFF;
overflow: visible;
image {
width: 72rpx;
height: 72rpx;
}
}
.notice-card__body {
flex: 1;
min-width: 0;
margin-left: 16rpx;
display: flex;
flex-direction: column;
}
.notice-card__title {
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
line-height: 40rpx;
}
.notice-card__preview {
margin-top: 4rpx;
font-size: 22rpx;
color: #64748B;
line-height: 32rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notice-card__time {
margin-top: 4rpx;
font-size: 20rpx;
color: #94A3B8;
line-height: 28rpx;
}
.notice-card--sk {
min-height: 140rpx;
}
.notice-sk-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #EEF2FF;
flex-shrink: 0;
}
.notice-sk-line {
height: 20rpx;
margin-top: 12rpx;
border-radius: 8rpx;
background: #F1F5F9;
width: 100%;
}
.notice-sk-line--title {
height: 28rpx;
width: 140rpx;
flex: none;
}
.list {
width: 750rpx;
display: flex;

View File

@@ -532,12 +532,6 @@ export async function confirmAccept(params) {
let data = await http('/xkApi/product-order/receive', params)
return normalizeXkApiResponse(data)
}
//清空购物车
export async function clearAll(params) {
let data = await http('/newApi/cart/clearAll', params)
return data
}
export async function getVersion(params) {
let data = await http('/v1/other/xcx-version?store_id=11001', params)
return data

View File

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

View File

@@ -40,6 +40,20 @@ export async function getHomeTopProductsApi(params) {
export async function getHomeZonesApi(params) {
return await get('/product/home-zones', params, 3)
}
/**
* 首页展示样式(金刚区布局 + 订单提醒样式)
* xk-api GET /product/home-display-config游客态 request.js 会改写到 guest
*/
export async function getHomeDisplayConfigApi() {
return await get('/product/home-display-config', {}, 3)
}
/**
* 首页订单提醒(待支付挂号/商品、已发货、待确认收货)
* xk-api GET /product-order/home-remind取值 unwrapXkApi → code / result / message
*/
export async function getHomeOrderRemindApi() {
return await get('/product-order/home-remind', {}, 3)
}
/**
* 更新商品介绍图
* @param params

View File

@@ -10,19 +10,6 @@ import { unwrapXkApi } from '@/utils/api-response.js'
* 老签名不动,避免改动全部调用方的传参结构;新增接口请用 post(url, params, 3) 新封装。
*/
/**
* 存量页面兼容:把 xk-api 的 code/result 抹成旧版 errcode/data
* @deprecated 仅供 registePay 的存量调用方(读 res.data.errcode/data过渡使用
* 新代码一律直接按 code/result 取值或用 unwrapXkApi禁止再新增本函数的调用
*/
function normalizeXkApiResponse(res) {
if (res && res.data && (res.data.code === 0 || res.data.code === '0')) {
res.data.errcode = 0;
res.data.data = res.data.result;
}
return res;
}
/**
* 获取挂号记录列表 xk-api POST /register/list
* @param params { patient_id: number, store_id: number, page: number }
@@ -95,11 +82,12 @@ export async function registeInfo(params) {
/**
* 挂号支付 xk-api POST /register/pay易票联
* 存量调用方读 errcode/data这里保留 normalize 过渡0 元单返回 data.is_paid=1
* 返回原生 xk-api 形态res.data.code == 0 → res.data.result0 元单 result.is_paid=1
* (原 normalize 成 errcode/data 的兼容层已删除,调用方一律按 code/result/message 取值)
*/
export async function registePay(params) {
let data = await http('/xkApi/register/pay', params)
return normalizeXkApiResponse(data)
return data
}
/**
@@ -118,3 +106,19 @@ export async function registeCancle(params) {
let data = await http('/xkApi/register/refund', params)
return data
}
/**
* 修改预约时间(患者本人改期) xk-api POST /register/reschedule
* 参数 { register_id, visit_date: 'Y-m-d', slot_id };就诊当天后端会拦截(提示联系医生/诊所)
*/
export async function registerReschedule(params = {}) {
return await post('/register/reschedule', params, 3)
}
/**
* 挂号改期记录 xk-api POST /register/reschedule-logs
* 返回 result 数组:谁在什么时候把预约从哪个时段改到哪个时段(详情页溯源展示)
*/
export async function registerRescheduleLogs(params = {}) {
return await post('/register/reschedule-logs', params, 3)
}

View File

@@ -135,6 +135,9 @@ function request(url, params, method = 0) {
} else if (url.indexOf('/xkApi/product/home-zones') !== -1) {
url = url.replace('/xkApi/product/home-zones', '/guest/guest-product/home-zones');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/product/home-display-config') !== -1) {
url = url.replace('/xkApi/product/home-display-config', '/guest/guest-product/home-display-config');
baseURL = envUrls.xkApi;
} else if (url.indexOf('/xkApi/platform/config') !== -1) {
url = url.replace('/xkApi/platform/config', '/guest/guest-platform/config');
baseURL = envUrls.xkApi;

View File

@@ -1135,7 +1135,7 @@ export default {
},
getStatusText(status) { const map = { 0: '待审核', 1: '已审核', 2: '未通过' }; return map[status] || '未知状态'; },
getRegisterStatusText(status) { const map = { 0: '待就诊', 1: '已缴费', 2: '已就诊', 3: '已取消', 4: '已退费' }; return map[status] || '未知状态'; },
getRegisterStatusText(status) { const map = { 0: '待支付', 1: '待接诊', 2: '接诊中', 3: '已结束', 4: '已取消', 5: '待评价', 6: '已评价', 7: '已拒诊' }; return map[status] || '未知状态'; },
viewPrescription(id, orderNo) { uni.navigateTo({ url: "/subPackages/my/myrecord-detail?id=" + id + '&no=' + orderNo }); },
goToOrderMedicine(id, orderNo) { uni.navigateTo({ url: "/subPackages/my/my-drug/drug-info?id=" + id + '&order_type=prescription' }); },
viewRegister(id, orderNo) { uni.navigateTo({ url: `/subPackages/register/register-info?id=${id}&type=1` }); },

View File

@@ -409,7 +409,7 @@ export default {
return Math.min(w, max) + 'rpx';
},
getStatusText(status) { const map = { 0: '待审核', 1: '已审核', 2: '未通过' }; return map[status] || '状态未知'; },
getRegisterStatusText(status) { const map = { 0: '待就诊', 1: '已缴费', 2: '已就诊', 3: '已取消', 4: '已退费' }; return map[status] || '状态未知'; },
getRegisterStatusText(status) { const map = { 0: '待支付', 1: '待接诊', 2: '接诊中', 3: '已结束', 4: '已取消', 5: '待评价', 6: '已评价', 7: '已拒诊' }; return map[status] || '状态未知'; },
// 处理转诊同意
handleTransferAgree(messageData) {
if (!messageData || !messageData.transfer_id) {

View File

@@ -28,8 +28,8 @@
<view class="list" v-for="(item,index) in infoList" :key="item.id">
<view class="li_title">
<text>{{item.created_at}}</text>
<text class="text" v-if="item.status<4">{{ statusText(item.status) }}</text>
<text class="txt" v-if="item.status>=4">{{ statusText(item.status) }}</text>
<text class="text" v-if="item.is_cancel!=1 && item.status<4">{{ statusText(item) }}</text>
<text class="txt" v-if="item.is_cancel==1 || item.status>=4">{{ statusText(item) }}</text>
</view>
<view class="info">
<view class="list_name">患者姓名</view>
@@ -86,7 +86,10 @@
components: { TransferPrescriptionCard },
mixins: [patientTabList],
methods: {
statusText(status) {
statusText(item) {
// 取消优先于完结:未支付取消以前可能只写 is_cancel 或只写 status=4避免误显示「已结束」
if (item && item.is_cancel == 1 && item.status != 7) return '已取消'
const status = item && item.status
if (status == 0) return '待支付'
if (status == 1) return '待接诊'
if (status == 2) return '接诊中'

View File

@@ -187,8 +187,7 @@
</view>
<view class="info">
<text>发药人</text>
<!-- <text class="name">{{rpList.name || ' '}}</text> -->
<image :src="checkImageUrl(data.Pharmacist_sign_image)" mode="aspectFit"></image>
<image v-if="data.sender_sign_image" :src="checkImageUrl(data.sender_sign_image)" mode="aspectFit"></image>
</view>
<view class="info">
<text>核对人</text>
@@ -196,7 +195,7 @@
</view>
<view class="info">
<text>调配人</text>
<image :src="checkImageUrl(data.Pharmacist_sign_image)" mode="aspectFit"></image>
<image v-if="data.dispenser_sign_image" :src="checkImageUrl(data.dispenser_sign_image)" mode="aspectFit"></image>
</view>
<!-- <view class="info">
<text>复审药师</text>

View File

@@ -1,11 +1,15 @@
<template>
<view class="container safe-area-inset-bottom" style="padding-bottom: 100rpx">
<!-- #ifdef MP-WEIXIN -->
<!-- 仅作返回拦截 page-container 默认是底部白底浮层会把预约信息整块盖住只剩步骤条和底栏 -->
<!-- pageGuard show 拆开 show=false 播完离场再卸节点避免原生滚动锁残留 -->
<page-container
v-if="pageBackGuardShow"
v-if="pageGuard"
:show="pageBackGuardShow"
:overlay="false"
custom-style="height:1px;width:1px;overflow:hidden;opacity:0;background:transparent;"
@leave="onPageBackGuardLeave"
@afterleave="releasePageGuard"
/>
<!-- #endif -->
<!-- 预约信息 -->
@@ -161,7 +165,6 @@
} from '@/request/api/register.js';
import { createRoomByDoctorIdApi, sendToUserApi } from '@/request/api/im';
import { checkDev } from '@/utils/utils';
import { genOrderApi } from '@/request/api/order';
import request from '@/request/api/request';
import {
CLINIC_POST_REGISTER_STORAGE_KEY,
@@ -207,6 +210,9 @@
_urlDoctorId: '',
_getInfoPromise: null,
pageBackGuardShow: false,
// 原生容器挂载开关:关闭时先关 showafterleave/超时后再卸
pageGuard: false,
releaseTimer: null,
_suppressPageBackGuardLeave: false,
};
},
@@ -246,6 +252,9 @@
const uid = uni.getStorageSync('user_id');
this.currentUserId = 'user-' + (uid !== '' && uid !== undefined && uid !== null ? uid : '2512');
},
beforeDestroy() {
this.clearReleaseTimer();
},
methods: {
bootstrapFromProps() {
this.register_id = this.registerId;
@@ -310,9 +319,34 @@
setPageBackGuardShow(show, { suppressLeave = false } = {}) {
// #ifdef MP-WEIXIN
if (suppressLeave) this._suppressPageBackGuardLeave = true;
this.pageBackGuardShow = !!show;
if (show) {
this.pageGuard = true;
this.$nextTick(() => {
this.pageBackGuardShow = true;
});
} else {
this.pageBackGuardShow = false;
this.scheduleReleaseGuard();
}
// #endif
},
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;
}
},
syncPageBackGuard() {
// #ifdef MP-WEIXIN
this.$nextTick(() => {
@@ -605,15 +639,9 @@
that.disableBackGuardBeforeLeave();
uni.navigateTo({ url: chatUrl });
};
genOrderApi({
register_id: that.register_id,
register_info: typeof that.rInfo === 'string' ? that.rInfo : JSON.stringify(that.rInfo),
}).then(() => {
goAfterGenOrder();
}).catch((error) => {
console.error('生成订单失败:', error);
goAfterGenOrder();
});
// 历史遗留的 genOrderApi 调用已删除genOrder 是购药下单专用接口,
// 传挂号参数必然失败、结果又被 catch 吞掉照样跳转,纯属无效请求,直接进入复诊后续流程
goAfterGenOrder();
} catch (error) {
uni.hideLoading();
console.error('处理在线复诊流程失败:', error);
@@ -636,11 +664,12 @@
}).then(async (res) => {
uni.showLoading({ title: '加载中' });
if (!(res.data && (res.data.errcode == 0 || res.data.errcode === '0'))) {
// registePay 为 xk-api按 code/result/message 取值
if (!(res.data && (res.data.code == 0 || res.data.code === '0'))) {
that.flag = true;
uni.hideLoading();
that.$refs.uToast && that.$refs.uToast.show({
title: (res.data && (res.data.message || res.data.msg)) || '获取支付参数失败',
title: (res.data && res.data.message) || '获取支付参数失败',
type: 'default',
icon: false
});
@@ -665,7 +694,7 @@
});
}
if (res.data.data && res.data.data.is_paid == 1) {
if (res.data.result && res.data.result.is_paid == 1) {
await that.waitForRegisterDetail();
if (that.registList && (that.registList.register_type === 2 || that.registList.register_type === 3)) {
await that.handleOnlineConsultationAfterPayment();
@@ -682,12 +711,12 @@
}
uni.requestPayment({
provider: res.data.data.appId,
timeStamp: res.data.data.timestamp,
nonceStr: res.data.data.nonceStr,
package: res.data.data.package,
signType: res.data.data.signType,
paySign: res.data.data.paySign,
provider: res.data.result.appId,
timeStamp: res.data.result.timestamp,
nonceStr: res.data.result.nonceStr,
package: res.data.result.package,
signType: res.data.result.signType,
paySign: res.data.result.paySign,
success: async () => {
await that.waitForRegisterDetail();
that.flag = false;

View File

@@ -3,8 +3,8 @@
<MessageNotification />
<u-navbar class="navbar" :is-back="true" title="挂号详情" :custom-back="customBack" title-color="#000">
</u-navbar>
<!-- 已预约 -->
<view class="appoined" v-if="registList.is_cancel==0 && registList.status>0">
<!-- 已预约排除已取消(4)/已拒诊(7)避免取消后仍显示已挂号成功 -->
<view class="appoined" v-if="registList.is_cancel==0 && registList.status>0 && registList.status!=4 && registList.status!=7">
<view class="appoin">
<text class="iconfont icon-ys-xuanzhong"></text>
已挂号成功
@@ -14,8 +14,8 @@
</view>
</view>
<!-- 已取消 -->
<view class="appoined" v-if="registList.is_cancel==1&&registList.status==4">
<!-- 已取消status=4 is_cancel=1未支付取消以前只软删不写 is_cancel这里按状态兜底 -->
<view class="appoined" v-if="registList.status==4 || (registList.is_cancel==1 && registList.status!=7)">
<view class="unappoin">
<text class="iconfont icon-xianshi_quxiao"></text>
已取消成功
@@ -107,6 +107,17 @@
</view>
</view>
<!-- 改期记录有过改期的单展示轨迹谁在什么时候改到什么时段便于患者对账溯源 -->
<view class="title" v-if="rescheduleLogList.length > 0">
改期记录
</view>
<view class="card log-card" v-if="rescheduleLogList.length > 0">
<view class="log-item" v-for="log in rescheduleLogList" :key="log.id">
<view class="log-line">{{log.old_visit_date}} {{log.old_slot_name}} {{log.new_visit_date}} {{log.new_slot_name}}</view>
<view class="log-meta">{{log.operator_label}}{{log.operator_name ? '·' + log.operator_name : ''}} {{log.created_at}}</view>
</view>
</view>
<!-- 就诊人 -->
<view class="title">
就诊人
@@ -159,8 +170,60 @@
<view class="isbtn" @click="opened" v-if="registList.is_cancel==0 && registList.status==0">
去支付
</view>
<!-- 修改时间仅已支付待接诊且未到就诊日可见就诊当天不可自行改期后端双重拦截 -->
<view class="isbtn" @click="openReschedule" v-if="canReschedule">
修改时间
</view>
<view class="btn" @click="goHome" v-if="registList.status>=1">返回首页</view>
</view>
<!-- 改期抽屉page-container 防误退微信手势返回先关抽屉日历+时段选择复用挂号日历接口 -->
<page-container v-if="rescheduleGuard" :show="rescheduleShow" position="bottom" round overlay
custom-style="height:76%;" @clickoverlay="closeReschedule" @afterleave="releaseRescheduleGuard">
<view class="reschedule-popup">
<view class="popup-header">
<text class="popup-title">修改预约时间</text>
<text class="popup-close" @click="closeReschedule">×</text>
</view>
<view class="popup-current" v-if="appointmentText">当前预约{{appointmentText}}</view>
<scroll-view scroll-y class="popup-body">
<view v-if="calendarLoading" class="popup-loading">号源加载中...</view>
<block v-else>
<view v-if="calendarDays.length > 0">
<scroll-view scroll-x class="day-scroll" :show-scrollbar="false">
<view class="day-strip">
<view class="day-item" v-for="(day, idx) in calendarDays" :key="day.date"
:class="{ active: idx === selectedDayIndex, disabled: !day.is_work || day.is_full }"
@click="selectDay(idx)">
<view class="day-week">{{day.week}}</view>
<view class="day-date">{{day.date_text}}</view>
<view class="day-state" :class="{ rest: !day.is_work, full: day.is_work && day.is_full }">{{day.state_text}}</view>
</view>
</view>
</scroll-view>
<view class="slot-list" v-if="selectedDay && selectedDay.is_work && selectedDay.slots.length > 1">
<view class="slot-item" v-for="slot in selectedDay.slots" :key="slot.slot_id"
:class="{ active: slot.slot_id === selectedSlotId, disabled: slot.is_full || slot.is_expired }"
@click="selectSlot(slot)">
<view class="slot-name">{{slot.slot_name}}</view>
<view class="slot-time">{{slot.start_time}}-{{slot.end_time}}</view>
<view class="slot-left" :class="{ full: slot.is_full || slot.is_expired }">{{slot.state_text}}</view>
</view>
</view>
<view class="rest-tip" v-if="selectedDay && !selectedDay.is_work">该日医生休诊请选择其他日期</view>
</view>
<view class="rest-tip" v-else>该医生暂无可约号源</view>
</block>
<view class="reschedule-tip">温馨提示就诊当天不可自行改期如需当天调整请联系医生或诊所</view>
</scroll-view>
<view class="popup-footer">
<view class="popup-selected" v-if="rescheduleSlotLabel">改期至{{rescheduleSlotLabel}}</view>
<view class="popup-btn" :class="{ disabled: !canConfirmReschedule || rescheduleSubmitting }" @click="confirmReschedule">
{{rescheduleSubmitting ? '提交中...' : '确认修改'}}
</view>
</view>
</view>
</page-container>
<!-- 消息提醒中 -->
<u-toast ref="uToast" />
</view>
@@ -176,7 +239,10 @@
registeCancle,
registeInfo,
registePay,
registeUnpay
registeUnpay,
registerCalendar,
registerReschedule,
registerRescheduleLogs
} from '../../request/api/register.js';
export default {
data() {
@@ -187,6 +253,18 @@
register_id: uni.getStorageSync('register_id'),
registList: [],
type: 0,
// 改期记录xk-api reschedule-logs空数组不渲染区块
rescheduleLogList: [],
// 改期抽屉状态pageGuard 与 show 拆开,关抽屉先离场再卸
rescheduleShow: false,
rescheduleGuard: false,
rescheduleReleaseTimer: null,
rescheduleSubmitting: false,
calendarLoading: false,
// 号源日历(与医生详情页同一接口/同一预处理逻辑)
calendarDays: [],
selectedDayIndex: 0,
selectedSlotId: -1,
}
},
computed: {
@@ -200,6 +278,58 @@
}
return text;
},
/** 今天日期字符串 Y-m-d本地时区用于「未到就诊日」判断ISO 字符串可直接比大小) */
todayStr() {
const d = new Date();
const m = d.getMonth() + 1;
const day = d.getDate();
return d.getFullYear() + '-' + (m < 10 ? '0' + m : m) + '-' + (day < 10 ? '0' + day : day);
},
/**
* 是否展示「修改时间」:已支付 + 待接诊 + 未取消 + 有占号信息 + 未到就诊日。
* 就诊当天入口直接隐藏(当天号源已进入医生叫号安排,需联系医生/诊所调整),后端也会拦截
*/
canReschedule() {
const r = this.registList || {};
const appt = r.appointment;
if (!appt || !appt.visit_date) return false;
if (r.is_cancel != 0 || r.status != 1 || r.is_pay != 1) return false;
return appt.visit_date > this.todayStr;
},
/** 当前选中的日期对象 */
selectedDay() {
return this.calendarDays[this.selectedDayIndex] || null;
},
/** 当前选中的时段对象 */
selectedSlot() {
if (!this.selectedDay || this.selectedSlotId < 0) return null;
const list = this.selectedDay.slots || [];
for (let i = 0; i < list.length; i++) {
if (list[i].slot_id === this.selectedSlotId) return list[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);
},
},
onUnload() {
if (this.rescheduleReleaseTimer) {
clearTimeout(this.rescheduleReleaseTimer)
this.rescheduleReleaseTimer = null
}
},
onLoad(e) {
this.register_id = e.id
@@ -283,17 +413,18 @@
title: "加载中"
});
if (!(res.data && (res.data.errcode == 0 || res.data.errcode === '0'))) {
// registePay 为 xk-api按 code/result/message 取值
if (!(res.data && (res.data.code == 0 || res.data.code === '0'))) {
uni.hideLoading();
that.$refs.uToast.show({
title: (res.data && (res.data.message || res.data.msg)) || '支付失败',
title: (res.data && res.data.message) || '支付失败',
type: 'default',
icon: false
});
return;
}
if (res.data.data.is_paid == 1) {
if (res.data.result.is_paid == 1) {
uni.hideLoading();
setTimeout(() => {
uni.showToast({
@@ -310,12 +441,12 @@
}
uni.requestPayment({
provider: res.data.data.appId,
timeStamp: res.data.data.timestamp,
nonceStr: res.data.data.nonceStr,
package: res.data.data.package,
signType: res.data.data.signType,
paySign: res.data.data.paySign,
provider: res.data.result.appId,
timeStamp: res.data.result.timestamp,
nonceStr: res.data.result.nonceStr,
package: res.data.result.package,
signType: res.data.result.signType,
paySign: res.data.result.paySign,
success: () => {
uni.hideLoading();
setTimeout(() => {
@@ -369,10 +500,168 @@
}).then((res) => {
if (res.data.code == 0 && res.data.result) {
this.registList = res.data.result
this.getRescheduleLogs()
}
})
},
// 改期记录xk-api没有记录时不渲染区块失败静默不影响详情主流程
getRescheduleLogs() {
registerRescheduleLogs({
register_id: this.register_id
}).then((res) => {
if (res.data.code == 0 && res.data.result) {
this.rescheduleLogList = res.data.result
}
}).catch(() => {})
},
// 打开改期抽屉并拉取该单医生的号源日历(用挂号单自己的 store_id跨店单不能用登录门店
openReschedule() {
this.rescheduleGuard = true
this.rescheduleShow = true
this.calendarLoading = true
this.calendarDays = []
this.selectedSlotId = -1
registerCalendar({
method: "get",
data: {
doctor_id: this.registList.service_user_id,
store_id: this.registList.store_id || uni.getStorageSync('store_id') || '11001',
}
}).then((res) => {
this.calendarLoading = false
if (res.data.code === 0 || res.data.code === '0') {
const responseData = res.data.result || {}
if (responseData.register_status == 0) {
this.$refs.uToast.show({
title: '该医生暂未开放挂号',
type: 'default',
icon: false
})
return
}
this.applyCalendar(responseData)
}
}).catch(() => {
this.calendarLoading = false
})
},
// 关闭改期抽屉
closeReschedule() {
this.rescheduleShow = false
this.scheduleRescheduleRelease()
},
releaseRescheduleGuard() {
if (this.rescheduleReleaseTimer) {
clearTimeout(this.rescheduleReleaseTimer)
this.rescheduleReleaseTimer = null
}
this.rescheduleGuard = false
},
scheduleRescheduleRelease() {
if (this.rescheduleReleaseTimer) {
clearTimeout(this.rescheduleReleaseTimer)
}
this.rescheduleReleaseTimer = setTimeout(() => {
this.rescheduleGuard = false
this.rescheduleReleaseTimer = null
}, 350)
},
/**
* 预处理日历数据补充展示文案MM-DD、余号/约满/休诊/当前),
* 文案预计算是因为小程序模板禁止 :class/插值调方法;与医生详情页逻辑保持一致,
* 额外把「当前预约时段」标记出来方便用户对照
*/
applyCalendar(calendar) {
const appt = (this.registList && this.registList.appointment) || {}
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 === appt.visit_date && slot.slot_id === appt.slot_id) slotState = '当前'
return Object.assign({}, slot, { state_text: slotState })
})
return Object.assign({}, day, { date_text: dateText, state_text: stateText, slots: 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 list = day.slots || []
for (let i = 0; i < list.length; i++) {
if (!list[i].is_full && !list[i].is_expired) {
this.selectedSlotId = list[i].slot_id
return
}
}
},
// 提交改期:成功后刷新详情与改期记录(新时间即时回显)
confirmReschedule() {
if (!this.canConfirmReschedule || this.rescheduleSubmitting) return
this.rescheduleSubmitting = true
registerReschedule({
register_id: this.register_id,
visit_date: this.selectedDay.date,
slot_id: this.selectedSlotId,
}).then((res) => {
this.rescheduleSubmitting = false
if (res.data.code == 0) {
this.closeReschedule()
uni.showToast({
title: '改期成功',
duration: 1500,
icon: 'success'
})
this.getInfo()
} else {
this.$refs.uToast.show({
title: res.data.message || '改期失败',
type: 'default',
icon: false
})
}
}).catch(() => {
this.rescheduleSubmitting = false
this.$refs.uToast.show({
title: '改期失败,请稍后重试',
type: 'default',
icon: false
})
})
},
},
mounted() {
@@ -540,7 +829,7 @@
.isbtn {
text-align: center;
width: 328rpx;
flex: 1;
height: 86rpx;
line-height: 86rpx;
font-size: 32rpx;
@@ -577,5 +866,234 @@
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
}
// 改期记录卡片:轨迹行 + 操作人/时间(次要信息灰字)
.log-card {
.log-item {
padding: 20rpx 0;
border-bottom: 2rpx solid #F1F5F9;
&:first-child {
padding-top: 0;
}
&:last-child {
border-bottom: none;
padding-bottom: 0;
}
.log-line {
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
line-height: 40rpx;
}
.log-meta {
margin-top: 8rpx;
font-size: 24rpx;
color: #94A3B8;
}
}
}
}
// 改期抽屉page-container 内容区):头部 + 当前预约 + 日历/时段 + 底部确认
.reschedule-popup {
display: flex;
flex-direction: column;
height: 100%;
background: #FFFFFF;
border-radius: 32rpx 32rpx 0 0;
overflow: hidden;
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 32rpx 16rpx;
.popup-title {
font-size: 34rpx;
font-weight: 600;
color: #1E293B;
}
.popup-close {
font-size: 48rpx;
line-height: 48rpx;
color: #94A3B8;
padding: 0 8rpx;
}
}
.popup-current {
margin: 0 32rpx 8rpx;
font-size: 26rpx;
color: #64748B;
}
.popup-body {
flex: 1;
height: 0;
padding: 16rpx 32rpx 0;
box-sizing: border-box;
}
.popup-loading {
padding: 60rpx 0;
text-align: center;
font-size: 26rpx;
color: #94A3B8;
}
.reschedule-tip {
margin: 24rpx 0 32rpx;
font-size: 24rpx;
color: #94A3B8;
line-height: 36rpx;
}
.popup-footer {
padding: 16rpx 32rpx;
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
border-top: 2rpx solid #F1F5F9;
.popup-selected {
font-size: 26rpx;
color: #4175EE;
margin-bottom: 12rpx;
}
.popup-btn {
height: 86rpx;
line-height: 86rpx;
text-align: center;
font-size: 32rpx;
font-weight: 500;
color: #FFFFFF;
background: #4175EE;
border-radius: 64rpx;
&.disabled {
opacity: 0.5;
}
}
}
// 日期横条/时段卡片与医生详情页doctor-detail同款交互样式保持体验一致
.day-scroll {
width: 100%;
white-space: nowrap;
.day-strip {
display: inline-flex;
}
.day-item {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 128rpx;
padding: 16rpx 0;
margin-right: 16rpx;
border-radius: 12rpx;
border: 2rpx solid #E2E8F0;
background: #FFFFFF;
.day-week {
font-size: 26rpx;
font-weight: 600;
color: #1E293B;
}
.day-date {
font-size: 24rpx;
color: #64748B;
margin: 6rpx 0;
}
.day-state {
font-size: 22rpx;
color: #4175EE;
&.rest {
color: #94A3B8;
}
&.full {
color: #F44336;
}
}
&.active {
border-color: #4175EE;
background: rgba(65, 117, 238, 0.08);
box-shadow: 0 0 6px rgba(65, 117, 238, 0.18);
}
&.disabled {
opacity: 0.55;
}
}
}
.slot-list {
display: flex;
flex-wrap: wrap;
margin-top: 20rpx;
.slot-item {
display: flex;
flex-direction: column;
align-items: center;
width: 208rpx;
padding: 14rpx 0;
margin: 0 16rpx 16rpx 0;
border-radius: 12rpx;
border: 2rpx solid #E2E8F0;
background: #FFFFFF;
.slot-name {
font-size: 26rpx;
font-weight: 600;
color: #1E293B;
}
.slot-time {
font-size: 22rpx;
color: #64748B;
margin: 4rpx 0;
}
.slot-left {
font-size: 22rpx;
color: #4175EE;
&.full {
color: #94A3B8;
}
}
&.active {
border-color: #4175EE;
background: rgba(65, 117, 238, 0.08);
box-shadow: 0 0 6px rgba(65, 117, 238, 0.18);
}
&.disabled {
opacity: 0.55;
}
}
}
.rest-tip {
margin-top: 20rpx;
font-size: 26rpx;
color: #94A3B8;
text-align: center;
padding: 24rpx 0;
}
}
</style>

View File

@@ -76,13 +76,8 @@
</template>
<script>
// 引入API接口
import {
CartList,
addCart,
clearAll
} from '../../request/api/api'
import {getCartListApi, saveCartApi} from "@/request/api/cart";
// 引入API接口(购物车域已全量迁 xk-api cart 模块;原 api.js 的 CartList/addCart/clearAll 均为死引用已删)
import {getCartListApi, saveCartApi, clearCartApi} from "@/request/api/cart";
import {genOrderByCartApi} from "@/request/api/order";
export default {
@@ -253,13 +248,10 @@ export default {
})
},
// 调用API清空购物车
// 调用API清空购物车xk-api POST /cart/clear-cart按当前用户+门店硬删)
clearAll() {
clearAll({
method: "post",
data: {
store_id: uni.getStorageSync('store_id') || '11001',
}
clearCartApi({
store_id: uni.getStorageSync('store_id') || '11001',
}).then((res) => {
if (res.data.code == 0) {
uni.showToast({