fix: 首页UI重构设计
This commit is contained in:
70
App.vue
70
App.vue
@@ -3,6 +3,7 @@
|
||||
getVersion
|
||||
} from './request/api/api'
|
||||
import { webSocketManager } from '@/utils/ws/websocket.js';
|
||||
import { ChatManager } from '@/store/chat/chat.js';
|
||||
import {checkDev} from "@/utils/utils";
|
||||
import {bindUserApi} from "@/request/api/salesperson";
|
||||
export default {
|
||||
@@ -151,7 +152,76 @@
|
||||
methods: {
|
||||
initWebSocket() {
|
||||
webSocketManager.connect();
|
||||
|
||||
// 注册全局消息处理器
|
||||
webSocketManager.addMessageHandler((data) => {
|
||||
// 只处理聊天消息(包含 room_id 的消息)
|
||||
if (data.room_id) {
|
||||
// 获取当前页面路径
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const currentPath = currentPage ? currentPage.route : '';
|
||||
|
||||
// 如果用户在聊天页面,不调用 ChatManager(chat.vue 会自己处理)
|
||||
// 避免消息重复推送
|
||||
if (currentPath && currentPath.includes('chat/chat')) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChatManager.handleIncomingMessage(data);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听全局通知事件
|
||||
uni.$on('show-global-notification', this.handleShowNotification);
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理显示通知
|
||||
* @param {Object} message 消息对象
|
||||
*/
|
||||
handleShowNotification(message) {
|
||||
// 获取当前页面路径
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const currentPath = currentPage ? currentPage.route : '';
|
||||
|
||||
// 如果在聊天页面,不显示顶部通知
|
||||
if (currentPath && currentPath.includes('chat/chat')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取消息预览文本
|
||||
const messagePreview = ChatManager.getMessagePreview(message);
|
||||
|
||||
// 从消息中提取发送者信息
|
||||
let senderName = '新消息';
|
||||
let senderAvatar = '/static/xx/ysxx.png';
|
||||
|
||||
// 尝试解析发送者ID获取更多信息
|
||||
const senderId = message.sender_user_id || '';
|
||||
if (senderId.startsWith('doctor-')) {
|
||||
senderName = '医生消息';
|
||||
}
|
||||
|
||||
// 触发通知组件显示
|
||||
uni.$emit('show-message-notification', {
|
||||
avatar: senderAvatar,
|
||||
title: senderName,
|
||||
message: messagePreview,
|
||||
roomId: message.room_id,
|
||||
roomData: {
|
||||
nick_name: senderName,
|
||||
user_id: senderId,
|
||||
avatar: senderAvatar
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 移除全局通知事件监听
|
||||
uni.$off('show-global-notification', this.handleShowNotification);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
236
components/MessageNotification/MessageNotification.vue
Normal file
236
components/MessageNotification/MessageNotification.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<view v-if="visible" class="notification-wrapper" :class="{ 'slide-in': visible }" :style="{ paddingTop: notificationTop + 'px' }">
|
||||
<view class="notification" @click="handleClick">
|
||||
<!-- 头像 -->
|
||||
<image class="avatar" :src="avatar || '/static/xx/ysxx.png'" mode="aspectFill" />
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="content">
|
||||
<text class="title">{{ title || '新消息' }}</text>
|
||||
<text class="message">{{ displayMessage }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<view class="close" @click.stop="handleClose">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MessageNotification',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
avatar: '',
|
||||
title: '',
|
||||
message: '',
|
||||
roomId: '',
|
||||
roomData: null,
|
||||
autoHideTimer: null,
|
||||
notificationTop: 50 // 默认值
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 获取胶囊按钮位置,计算通知的安全位置
|
||||
this.calculateSafeTop();
|
||||
},
|
||||
computed: {
|
||||
displayMessage() {
|
||||
// 限制消息长度,超过30个字符显示省略号
|
||||
if (this.message && this.message.length > 30) {
|
||||
return this.message.substring(0, 30) + '...';
|
||||
}
|
||||
return this.message || '您有一条新消息';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 监听显示通知事件
|
||||
uni.$on('show-message-notification', this.showNotification);
|
||||
// 监听隐藏通知事件
|
||||
uni.$on('hide-message-notification', this.hideNotification);
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 移除事件监听
|
||||
uni.$off('show-message-notification', this.showNotification);
|
||||
uni.$off('hide-message-notification', this.hideNotification);
|
||||
// 清除定时器
|
||||
if (this.autoHideTimer) {
|
||||
clearTimeout(this.autoHideTimer);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 计算通知的安全顶部位置(避开胶囊按钮)
|
||||
*/
|
||||
calculateSafeTop() {
|
||||
try {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 获取胶囊按钮的位置信息
|
||||
const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
|
||||
// 通知顶部 = 胶囊底部 + 10px 安全边距
|
||||
this.notificationTop = menuButtonInfo.bottom + 10;
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
// 非微信小程序环境,使用状态栏高度 + 安全边距
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.notificationTop = (systemInfo.statusBarHeight || 44) + 10;
|
||||
// #endif
|
||||
} catch (e) {
|
||||
// 获取失败时使用默认值
|
||||
this.notificationTop = 50;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示通知
|
||||
* @param {Object} data 通知数据
|
||||
* @param {string} data.avatar 头像
|
||||
* @param {string} data.title 标题(发送者昵称)
|
||||
* @param {string} data.message 消息内容
|
||||
* @param {string} data.roomId 房间ID
|
||||
* @param {Object} data.roomData 房间数据(用于跳转)
|
||||
*/
|
||||
showNotification(data) {
|
||||
// 清除之前的定时器
|
||||
if (this.autoHideTimer) {
|
||||
clearTimeout(this.autoHideTimer);
|
||||
}
|
||||
|
||||
// 设置通知数据
|
||||
this.avatar = data.avatar || '';
|
||||
this.title = data.title || '新消息';
|
||||
this.message = data.message || '您有一条新消息';
|
||||
this.roomId = data.roomId || '';
|
||||
this.roomData = data.roomData || null;
|
||||
|
||||
// 显示通知
|
||||
this.visible = true;
|
||||
|
||||
// 3秒后自动隐藏
|
||||
this.autoHideTimer = setTimeout(() => {
|
||||
this.hideNotification();
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 隐藏通知
|
||||
*/
|
||||
hideNotification() {
|
||||
this.visible = false;
|
||||
if (this.autoHideTimer) {
|
||||
clearTimeout(this.autoHideTimer);
|
||||
this.autoHideTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击通知,跳转到聊天页面
|
||||
*/
|
||||
handleClick() {
|
||||
this.hideNotification();
|
||||
|
||||
if (this.roomId && this.roomData) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/chat/chat?room_id=${this.roomId}&nick_name=${this.roomData.nick_name || ''}&user_id=${this.roomData.user_id || ''}&avatar=${encodeURIComponent(this.roomData.avatar || '')}`
|
||||
});
|
||||
} else if (this.roomId) {
|
||||
// 如果只有 roomId,也尝试跳转
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/chat/chat?room_id=${this.roomId}`
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭通知
|
||||
*/
|
||||
handleClose() {
|
||||
this.hideNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notification-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
// padding-top 通过 :style 动态设置,避开胶囊按钮
|
||||
transform: translateY(-100%);
|
||||
transition: transform 0.3s ease-out;
|
||||
|
||||
&.slide-in {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15);
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
flex-shrink: 0;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 16rpx;
|
||||
|
||||
.close-icon {
|
||||
font-size: 40rpx;
|
||||
color: #999999;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
// 下载安装方式
|
||||
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
|
||||
// npm安装方式
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
|
||||
// 消息通知组件
|
||||
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue"
|
||||
},
|
||||
"pages": [
|
||||
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 搜索医生 -->
|
||||
<view class="search">
|
||||
<u-search placeholder="请输入关键字" bg-color="#F5f5f5" color="#999" border-color="#F5f5f5" :show-action="false"
|
||||
@@ -234,16 +235,19 @@
|
||||
},
|
||||
// 医生id
|
||||
goLookDoctor(id) {
|
||||
// type=3 (在线复诊) 跳转医生详情页走挂号流程
|
||||
if (this.type === '3') {
|
||||
return uni.navigateTo({
|
||||
url: '/subPackages/doctor/doctor-detail?id=' + id + '&r_type=3'
|
||||
})
|
||||
}
|
||||
// type=2 保持原有逻辑
|
||||
if (this.type === '2') {
|
||||
return uni.navigateTo({
|
||||
url: '/subPackages/product/product?doctor_id=' + id + '®ister_type=2'
|
||||
})
|
||||
} else if (this.type === '3') {
|
||||
console.log(this.type, 'sssssssssssssssssssss')
|
||||
return uni.navigateTo({
|
||||
url: '/subPackages/product/product?doctor_id=' + id + '®ister_type=3'
|
||||
})
|
||||
}
|
||||
// 默认跳转医生详情
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/doctor/doctor-detail?id=' + id
|
||||
})
|
||||
|
||||
@@ -476,10 +476,10 @@
|
||||
height: 100%;
|
||||
border-radius: 8rpx;
|
||||
|
||||
// 处方药图片模糊处理
|
||||
// 处方药图片模糊处理(暂时禁用)
|
||||
&.prescription-blur {
|
||||
filter: blur(10px);
|
||||
-webkit-filter: blur(10px);
|
||||
/* filter: blur(10px);
|
||||
-webkit-filter: blur(10px); */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 搜索 -->
|
||||
<view class="search">
|
||||
<u-search placeholder="输入文章标题" bg-color="#F5f5f5" color="#DDDDDD" border-color="#F5f5f5"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
|
||||
<view class="_html" v-if="content">
|
||||
<u-parse :html="content"></u-parse>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<!-- 全局消息通知组件 -->
|
||||
<MessageNotification />
|
||||
|
||||
<!-- 1. 自定义吸顶导航栏 -->
|
||||
<view class="custom-nav"
|
||||
:class="[
|
||||
@@ -69,7 +72,7 @@
|
||||
</view>
|
||||
<image src="../../static/home/bg1.png" class="nav-icon-img-64" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="nav-card" @click="golist(3)">
|
||||
<view class="nav-card" @click="goYard(3)">
|
||||
<view class="nav-content">
|
||||
<view class="nav-title" style="color:#00B578">在线复诊</view>
|
||||
<view class="nav-desc">在线选药 极速达</view>
|
||||
@@ -208,8 +211,12 @@ import {
|
||||
import { getPlatformQualificationsApi } from '../../request/api/platform'
|
||||
import { getStoreDrugListBySalespersonApi, getHomeTopProductsApi, getHomeZonesApi, checkOnlineConsultationConfigApi } from "@/request/api/product";
|
||||
import request from "@/request/api/request";
|
||||
import MessageNotification from "@/components/MessageNotification/MessageNotification.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MessageNotification
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
storeId: 0,
|
||||
@@ -293,27 +300,10 @@ export default {
|
||||
},
|
||||
async golist(type) {
|
||||
if (type === 3) {
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id');
|
||||
const res = await checkOnlineConsultationConfigApi({ store_id: storeId });
|
||||
if (!res.data?.result?.can_use) {
|
||||
uni.showToast({
|
||||
title: res.data?.result?.message || '该药店暂不支持在线复诊功能',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
const doctorId = res.data.result.doctor_id || this.defaultDoctorId;
|
||||
const delegateStoreId = res.data.result.delegate_store_id || storeId;
|
||||
return uni.navigateTo({
|
||||
url: '/subPackages/product/product?doctor_id=' + doctorId + '®ister_type=3&delegate_store_id=' + delegateStoreId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('检查在线复诊配置失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// 药店模式直接进入商品页,不检查配置
|
||||
return uni.navigateTo({
|
||||
url: '/subPackages/product/product?register_type=3'
|
||||
});
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/Inquiries/Inquiries?type=' + type
|
||||
@@ -365,6 +355,7 @@ export default {
|
||||
}).then((res) => {
|
||||
if (res.data.errcode == 0) {
|
||||
this.storeType = res.data.data.store['type']
|
||||
uni.setStorageSync('store_type', res.data.data.store['type']) // 存储门店类型
|
||||
this.infoList = res.data.data
|
||||
this.headerTitle = res.data.data.store['name']
|
||||
this.getBannered();
|
||||
@@ -669,19 +660,18 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* --- 5. 药品卡片 (双列瀑布流) --- */
|
||||
.product-list-container {
|
||||
padding: 0 20rpx; /* 减小左右边距 */
|
||||
padding: 0 24rpx; /* 左右边距 */
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx; /* 卡片间隙 */
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: #fff;
|
||||
/* 动态计算宽度,保留中间20rpx间隙 (750 - 20*2 - 20) / 2 = 345rpx */
|
||||
width: calc((100% - 20rpx) / 2);
|
||||
margin-bottom: 24rpx;
|
||||
/* 动态计算宽度,保留中间16rpx间隙 */
|
||||
width: calc((100% - 16rpx) / 2);
|
||||
padding: 0;
|
||||
border-radius: $card-radius;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: $card-shadow;
|
||||
@@ -690,7 +680,7 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.prod-img-box {
|
||||
width: 100%;
|
||||
height: 345rpx; /* 保持正方形 */
|
||||
height: 280rpx; /* 缩小图片高度:原345rpx,减小约19% */
|
||||
background: #f8f8f8;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
@@ -698,21 +688,21 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.prod-img { width: 100%; height: 100%; border-radius: 0; &.prescription-blur { filter: blur(4px); } }
|
||||
.prod-img { width: 100%; height: 100%; border-radius: 0; /* &.prescription-blur { filter: blur(4px); } 暂时禁用模糊 */ }
|
||||
}
|
||||
|
||||
.prod-info {
|
||||
flex: 1;
|
||||
display: flex; flex-direction: column; justify-content: space-between;
|
||||
padding: 16rpx;
|
||||
padding: 12rpx; /* 减小内边距:原16rpx */
|
||||
|
||||
.prod-title {
|
||||
font-size: 28rpx;
|
||||
font-size: 26rpx; /* 减小字号:原28rpx */
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 12rpx;
|
||||
height: 80rpx; /* 限制两行高度 */
|
||||
margin-bottom: 8rpx;
|
||||
height: 72rpx; /* 减小高度:原80rpx */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
@@ -720,19 +710,19 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.tag-row { display: flex; margin-bottom: 12rpx; .otc-tag { font-size: 18rpx; padding: 0rpx 6rpx; border-radius: 4rpx; font-weight: 700; border: 1rpx solid transparent; &.otc-green { color: $secondary-color; background: #E6F7F0; border-color: #A3E6CD; } &.otc-red { color: #FF4D4F; background: #FFF0F0; border-color: #FFCDCD; } } }
|
||||
.prod-spec { font-size: 20rpx; color: #999; margin-bottom: 16rpx;}
|
||||
.tag-row { display: flex; margin-bottom: 8rpx; .otc-tag { font-size: 18rpx; padding: 0rpx 6rpx; border-radius: 4rpx; font-weight: 700; border: 1rpx solid transparent; &.otc-green { color: $secondary-color; background: #E6F7F0; border-color: #A3E6CD; } &.otc-red { color: #FF4D4F; background: #FFF0F0; border-color: #FFCDCD; } } }
|
||||
.prod-spec { font-size: 18rpx; color: #999; margin-bottom: 12rpx;} /* 减小字号:原20rpx */
|
||||
|
||||
.prod-bottom {
|
||||
display: flex; justify-content: space-between; align-items: flex-end;
|
||||
.price-box { color: #FF4D4F; font-weight: 800; font-size: 34rpx; .price-symbol { font-size: 22rpx; margin-right: 2rpx; } }
|
||||
.buy-btn { background: linear-gradient(135deg, #4175EE 0%, #298DFF 100%); color: #fff; width: 56rpx; height: 56rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 12rpx rgba(65, 117, 238, 0.3); }
|
||||
.price-box { color: #FF4D4F; font-weight: 800; font-size: 30rpx; .price-symbol { font-size: 20rpx; margin-right: 2rpx; } } /* 减小字号:原34rpx/22rpx */
|
||||
.buy-btn { background: linear-gradient(135deg, #4175EE 0%, #298DFF 100%); color: #fff; width: 52rpx; height: 52rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 12rpx rgba(65, 117, 238, 0.3); } /* 减小尺寸:原56rpx */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qualifications-section {
|
||||
padding: 0 32rpx 40rpx;
|
||||
padding: 40rpx 32rpx 40rpx;
|
||||
.qualifications-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom" @click="onclick">
|
||||
<MessageNotification />
|
||||
<view class="imgs">
|
||||
<image class="img"
|
||||
:src="id==1?'https://p3.maiyaole.com/img/971/971752/org_org.jpg?v=1':'https://p2.maiyaole.com/img/item/202210/24/202210241042513.jpg'"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="head" @click="gologin">
|
||||
<!-- 门店 -->
|
||||
<view class="store">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 描述 -->
|
||||
<view class="card" v-for="item in list" :key="item.id" @click="toInfo(item.id)">
|
||||
<image :src="item.content['avatar']||'/static/mine/avatar_1.png'" mode=""></image>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<!-- 全局消息通知组件 -->
|
||||
<MessageNotification />
|
||||
|
||||
<!-- 标题 -->
|
||||
<view class="title">
|
||||
<text>全部消息</text>
|
||||
@@ -155,8 +158,12 @@ import {
|
||||
import {getSystemNoticeIndexApi} from '../../request/api/systemNotice'
|
||||
import {getChatFriendsListApi} from "@/request/api/im";
|
||||
import {checkDev} from "@/utils/utils";
|
||||
import MessageNotification from "@/components/MessageNotification/MessageNotification.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MessageNotification
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
order: [],
|
||||
@@ -169,10 +176,25 @@ export default {
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
// 监听聊天列表更新事件
|
||||
uni.$on('chat-list-update', this.handleChatListUpdate);
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 移除事件监听
|
||||
uni.$off('chat-list-update', this.handleChatListUpdate);
|
||||
},
|
||||
methods: {
|
||||
checkDev,
|
||||
|
||||
/**
|
||||
* 处理聊天列表更新事件
|
||||
* 收到新消息时自动刷新列表
|
||||
*/
|
||||
handleChatListUpdate() {
|
||||
this.getMyChatFriendsList();
|
||||
},
|
||||
|
||||
// 清除未读信息
|
||||
getClearInfo() {
|
||||
getseesionRead({
|
||||
@@ -237,11 +259,8 @@ export default {
|
||||
})
|
||||
},
|
||||
goChat(item, index) {
|
||||
console.log(this.imChat, 'ssssssssss')
|
||||
console.log(item, 'ssssssssss')
|
||||
console.log(index, 'ssssssssss')
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/chat/chat?room_id=${item.room_id}&nick_name=${item.nick_name}&user_id=${item.id}&avatar=${item.avatar}`
|
||||
url: `/subPackages/chat/chat?room_id=${item.room_id}&nick_name=${item.nick_name}&user_id=${item.id}&avatar=${encodeURIComponent(item.avatar || '')}&room_status=${item.room_status || 0}`
|
||||
})
|
||||
// getseesionRead({
|
||||
// method: "post",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 描述 -->
|
||||
<view class="card" v-for="item in list" :key="item.id">
|
||||
<image src="/static/xx/cpdd.png" mode=""></image>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 资质证明 -->
|
||||
<view class="section">
|
||||
<view class="section-title">{{ storeName || (isPlatform ? '萧康云医' : '店铺') }} - 资质证明</view>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 描述 -->
|
||||
<view class="info" v-for="item in list" :key="item.id" @click="next(item.base_type,item.id,item.data)">
|
||||
<view class="infos_title">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="none">
|
||||
<!-- 授权登录 -->
|
||||
<view class="conter">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
|
||||
<view class="title">
|
||||
欢迎注册新账号
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 头部 -->
|
||||
<view class="head">
|
||||
<view class="head_img">
|
||||
|
||||
@@ -69,4 +69,13 @@ export function upLoadChatFileApi(params) {
|
||||
*/
|
||||
export async function getDoctorInfoApi(params) {
|
||||
return await get('/chat-friends/get-doctor-info', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间状态
|
||||
* @param params { room_id: string }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getRoomStatusApi(params) {
|
||||
return await get('/chat-friends/get-room-status', params, 3)
|
||||
}
|
||||
@@ -56,4 +56,13 @@ export async function updateIntroductionImagesApi(params) {
|
||||
*/
|
||||
export async function checkOnlineConsultationConfigApi(params) {
|
||||
return await get('/store/check-online-consultation-config', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取药品分类列表
|
||||
* @param params { zone_type: string }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getDrugCategoriesApi(params) {
|
||||
return await get('/product/drug-categories', params, 3)
|
||||
}
|
||||
@@ -23,7 +23,6 @@ const ChatManager = {
|
||||
enterRoom(roomId) {
|
||||
this.currentRoomId = roomId;
|
||||
uni.setStorageSync('currentRoomId', roomId);
|
||||
console.log(`进入房间`);
|
||||
// 重置当前房间的未读消息
|
||||
this.resetUnreadCount(roomId);
|
||||
},
|
||||
@@ -32,7 +31,6 @@ const ChatManager = {
|
||||
leaveRoom() {
|
||||
this.currentRoomId = null;
|
||||
uni.removeStorageSync('currentRoomId');
|
||||
console.log('离开当前房间');
|
||||
},
|
||||
|
||||
// 获取当前房间ID
|
||||
@@ -46,7 +44,6 @@ const ChatManager = {
|
||||
if (this.getCurrentRoomId() !== roomId) {
|
||||
this.unreadCounts[roomId] = (this.unreadCounts[roomId] || 0) + 1;
|
||||
this.notifyUnreadChange();
|
||||
console.log(`房间 ${roomId} 未读消息 +1, 当前: ${this.unreadCounts[roomId]}`);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -55,7 +52,6 @@ const ChatManager = {
|
||||
if (this.unreadCounts[roomId]) {
|
||||
this.unreadCounts[roomId] = 0;
|
||||
this.notifyUnreadChange();
|
||||
console.log(`重置房间 ${roomId} 未读消息数`);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -83,7 +79,6 @@ const ChatManager = {
|
||||
this.roomMessages[roomId] = [];
|
||||
}
|
||||
this.roomMessages[roomId].push(message);
|
||||
console.log('addMessageToRoom', this.roomMessages[roomId])
|
||||
},
|
||||
|
||||
// 获取房间消息
|
||||
@@ -96,7 +91,6 @@ const ChatManager = {
|
||||
}).then((res) => {
|
||||
that.roomMessages[roomId] = that.checkMessage(res.data.result.list);
|
||||
that.lastMessageId = res.data.result.last_message_id;
|
||||
console.log(that.roomMessages[roomId], 'roomMessages')
|
||||
|
||||
uni.$emit('load-room-message', 1);
|
||||
return this.roomMessages[roomId] || [];
|
||||
@@ -190,13 +184,7 @@ const ChatManager = {
|
||||
getChatRegisterInfoApi({
|
||||
id: item.id
|
||||
}).then((res) => {
|
||||
console.log(that.roomMessages, 'ssssssssssss1')
|
||||
console.log(that.currentRoomId, 'ssssssssssss2')
|
||||
console.log(index, 'ssssssssssss3')
|
||||
console.log(that.roomMessages[that.currentRoomId][index], 'ssssssssssss4')
|
||||
that.roomMessages[that.currentRoomId][index].content = res.data.result;
|
||||
console.log('getChatRegisterInfo', that.roomMessages[that.currentRoomId][index].content)
|
||||
// return res.data.result
|
||||
})
|
||||
},
|
||||
|
||||
@@ -297,6 +285,61 @@ const ChatManager = {
|
||||
} else {
|
||||
// 不在当前房间,缓存消息并增加未读消息数
|
||||
this.increaseUnreadCount(roomId);
|
||||
|
||||
// 触发顶部通知(传递原始消息,由 App.vue 处理通知显示)
|
||||
uni.$emit('show-global-notification', message);
|
||||
}
|
||||
|
||||
// 通知聊天列表页刷新
|
||||
uni.$emit('chat-list-update');
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取消息预览文本
|
||||
* @param {Object} message 消息对象
|
||||
* @returns {string} 预览文本
|
||||
*/
|
||||
getMessagePreview(message) {
|
||||
const messageType = message.message_type;
|
||||
const content = message.message_content;
|
||||
|
||||
switch (messageType) {
|
||||
case 0: // 文本
|
||||
return content;
|
||||
case 1: // 图片
|
||||
return '[图片]';
|
||||
case 2: // 音频
|
||||
return '[语音消息]';
|
||||
case 3: // 视频
|
||||
return '[视频]';
|
||||
case 4: // 处方
|
||||
return '[处方单]';
|
||||
case 5: // 文件
|
||||
return '[文件]';
|
||||
case 6: // 视频通话
|
||||
return '[视频通话]';
|
||||
case 7: // 音频通话
|
||||
return '[语音通话]';
|
||||
case 9: // 系统消息
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
if (parsed.type === 'price_update') {
|
||||
return parsed.message || '[价格更新]';
|
||||
}
|
||||
return '[系统消息]';
|
||||
} catch (e) {
|
||||
return '[系统消息]';
|
||||
}
|
||||
case 10: // 挂号
|
||||
return '[挂号信息]';
|
||||
case 11: // 患者就诊经历
|
||||
return '[就诊信息]';
|
||||
case 12: // 产品卡片
|
||||
return '[商品推荐]';
|
||||
case 13: // 结束问诊
|
||||
return '[问诊已结束]';
|
||||
default:
|
||||
return '您有一条新消息';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 复杂卡片类消息容器 (挂号、处方、档案等) -->
|
||||
<view v-else-if="[4, 10, 11, 12, 13].includes(msg.message_type)" class="card-bubble">
|
||||
<view v-else-if="[4, 9, 10, 11, 12, 13].includes(msg.message_type)" class="card-bubble">
|
||||
|
||||
<!-- 类型10: 挂号信息卡片 -->
|
||||
<block v-if="msg.message_type === 10">
|
||||
@@ -117,9 +117,6 @@
|
||||
<u-icon name="calendar-fill" size="18" color="#059669"></u-icon>
|
||||
<text class="card-title">预约挂号</text>
|
||||
</view>
|
||||
<text class="status-tag" :class="'status-' + getParsedContent(msg.message_type, msg.message_content).status">
|
||||
{{ getRegisterStatusText(getParsedContent(msg.message_type, msg.message_content).status) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
@@ -225,6 +222,23 @@
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型9: 系统消息 - 价格变更通知 -->
|
||||
<block v-else-if="msg.message_type === 9">
|
||||
<view class="system-message-card" v-if="getParsedContent(msg.message_type, msg.message_content).type === 'price_update'">
|
||||
<view class="price-update-notice">
|
||||
<u-icon name="info-circle-fill" size="18" color="#fa8c16"></u-icon>
|
||||
<text class="notice-text">{{ getParsedContent(msg.message_type, msg.message_content).message }}</text>
|
||||
</view>
|
||||
<view class="price-detail">
|
||||
<text>订单号:{{ getParsedContent(msg.message_type, msg.message_content).order_no }}</text>
|
||||
<text>应付金额:¥{{ getParsedContent(msg.message_type, msg.message_content).total_pay_price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="system-message">
|
||||
<text>{{ msg.message_content }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型13: 结束问诊卡片 -->
|
||||
<block v-else-if="msg.message_type === 13">
|
||||
<view class="card-header">
|
||||
@@ -276,7 +290,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 卡片: 我发送的卡片 -->
|
||||
<view v-else-if="[4, 10, 11, 12, 13].includes(msg.message_type)" class="card-bubble">
|
||||
<view v-else-if="[4, 9, 10, 11, 12, 13].includes(msg.message_type)" class="card-bubble">
|
||||
|
||||
<!-- 类型10: 挂号 -->
|
||||
<block v-if="msg.message_type === 10">
|
||||
@@ -285,9 +299,6 @@
|
||||
<u-icon name="calendar-fill" size="18" color="#059669"></u-icon>
|
||||
<text class="card-title">预约挂号</text>
|
||||
</view>
|
||||
<text class="status-tag" :class="'status-' + getParsedContent(msg.message_type, msg.message_content).status">
|
||||
{{ getRegisterStatusText(getParsedContent(msg.message_type, msg.message_content).status) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
@@ -434,71 +445,80 @@
|
||||
|
||||
<!-- ================= 底部输入与工具栏 (重构版) ================= -->
|
||||
<view class="chat-footer safe-area-inset-bottom">
|
||||
<!-- 上半部分:快捷工具栏 -->
|
||||
<view class="toolbar-area">
|
||||
<view class="action-grid">
|
||||
<!-- 相册按钮 -->
|
||||
<view class="action-item" @click="openMediaPicker('image')">
|
||||
<view class="icon-circle"><u-icon name="photo-fill" size="20" color="#606266"></u-icon></view>
|
||||
<text>相册</text>
|
||||
</view>
|
||||
<!-- <!– 拍照按钮 –>-->
|
||||
<!-- <view class="action-item" @click="openCamera">-->
|
||||
<!-- <view class="icon-circle"><u-icon name="camera-fill" size="20" color="#606266"></u-icon></view>-->
|
||||
<!-- <text>拍摄</text>-->
|
||||
<!-- </view>-->
|
||||
<!-- <!– 视频按钮 –>-->
|
||||
<!-- <view class="action-item" @click="openMediaPicker('video')">-->
|
||||
<!-- <view class="icon-circle"><u-icon name="play-circle-fill" size="20" color="#606266"></u-icon></view>-->
|
||||
<!-- <text>视频</text>-->
|
||||
<!-- </view>-->
|
||||
</view>
|
||||
<!-- 问诊已结束提示 -->
|
||||
<view v-if="isConsultationEnded" class="consultation-ended-notice">
|
||||
<u-icon name="checkmark-circle-fill" size="20" color="#999"></u-icon>
|
||||
<text>本次问诊已结束</text>
|
||||
</view>
|
||||
|
||||
<!-- 下半部分:输入框行 -->
|
||||
<view class="input-bar">
|
||||
<!-- 语音/键盘切换 -->
|
||||
<!-- <view class="voice-switch" @click="toggleVoiceInput">-->
|
||||
<!-- <u-icon :name="voiceInputActive ? 'keyboard' : 'mic'" size="28" color="#333"></u-icon>-->
|
||||
<!-- </view>-->
|
||||
|
||||
<!-- 输入区域容器 -->
|
||||
<view class="input-field-wrapper">
|
||||
<!-- 文本输入框 -->
|
||||
<input
|
||||
v-if="!voiceInputActive"
|
||||
v-model="newMessage"
|
||||
class="main-input"
|
||||
placeholder="描述您的症状或问题..."
|
||||
confirm-type="send"
|
||||
@confirm="sendTextMessage"
|
||||
:adjust-position="false"
|
||||
cursor-spacing="20"
|
||||
/>
|
||||
<!-- 语音按住按钮 -->
|
||||
<view v-else class="voice-btn"
|
||||
:class="{ 'recording': recording }"
|
||||
@touchstart="startVoiceRecording"
|
||||
@touchend="stopVoiceRecording"
|
||||
@touchcancel="stopVoiceRecording">
|
||||
<text>{{ recording ? '松开 发送' : '按住 说话' }}</text>
|
||||
|
||||
<!-- 正常输入区域 -->
|
||||
<template v-else>
|
||||
<!-- 上半部分:快捷工具栏 -->
|
||||
<view class="toolbar-area">
|
||||
<view class="action-grid">
|
||||
<!-- 相册按钮 -->
|
||||
<view class="action-item" @click="openMediaPicker('image')">
|
||||
<view class="icon-circle"><u-icon name="photo-fill" size="20" color="#606266"></u-icon></view>
|
||||
<text>相册</text>
|
||||
</view>
|
||||
<!-- <!– 拍照按钮 –>-->
|
||||
<!-- <view class="action-item" @click="openCamera">-->
|
||||
<!-- <view class="icon-circle"><u-icon name="camera-fill" size="20" color="#606266"></u-icon></view>-->
|
||||
<!-- <text>拍摄</text>-->
|
||||
<!-- </view>-->
|
||||
<!-- <!– 视频按钮 –>-->
|
||||
<!-- <view class="action-item" @click="openMediaPicker('video')">-->
|
||||
<!-- <view class="icon-circle"><u-icon name="play-circle-fill" size="20" color="#606266"></u-icon></view>-->
|
||||
<!-- <text>视频</text>-->
|
||||
<!-- </view>-->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发送按钮 (仅在有内容或语音模式下显示) -->
|
||||
<view class="send-btn-wrapper" v-if="newMessage.trim() || voiceInputActive">
|
||||
<view class="send-btn" @click="sendTextMessage">
|
||||
<!-- 文本发送图标 -->
|
||||
<image src="/static/chat/send2.png" style="width: 30rpx; height: 30rpx;" />
|
||||
<!-- <uni-icons type="paperplane-filled"></uni-icons>-->
|
||||
<!-- <u-icon name="arrow-up" size="18" color="#fff" v-if="!voiceInputActive"></u-icon>-->
|
||||
<!-- <!– 语音状态图标 –>-->
|
||||
<!-- <view class="voice-wave" v-else>-->
|
||||
<!-- <u-icon name="mic-fill" size="16" color="#fff"></u-icon>-->
|
||||
<!-- </view>-->
|
||||
<!-- 下半部分:输入框行 -->
|
||||
<view class="input-bar">
|
||||
<!-- 语音/键盘切换 -->
|
||||
<!-- <view class="voice-switch" @click="toggleVoiceInput">-->
|
||||
<!-- <u-icon :name="voiceInputActive ? 'keyboard' : 'mic'" size="28" color="#333"></u-icon>-->
|
||||
<!-- </view>-->
|
||||
|
||||
<!-- 输入区域容器 -->
|
||||
<view class="input-field-wrapper">
|
||||
<!-- 文本输入框 -->
|
||||
<input
|
||||
v-if="!voiceInputActive"
|
||||
v-model="newMessage"
|
||||
class="main-input"
|
||||
placeholder="描述您的症状或问题..."
|
||||
confirm-type="send"
|
||||
@confirm="sendTextMessage"
|
||||
:adjust-position="false"
|
||||
cursor-spacing="20"
|
||||
/>
|
||||
<!-- 语音按住按钮 -->
|
||||
<view v-else class="voice-btn"
|
||||
:class="{ 'recording': recording }"
|
||||
@touchstart="startVoiceRecording"
|
||||
@touchend="stopVoiceRecording"
|
||||
@touchcancel="stopVoiceRecording">
|
||||
<text>{{ recording ? '松开 发送' : '按住 说话' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发送按钮 (仅在有内容或语音模式下显示) -->
|
||||
<view class="send-btn-wrapper" v-if="newMessage.trim() || voiceInputActive">
|
||||
<view class="send-btn" @click="sendTextMessage">
|
||||
<!-- 文本发送图标 -->
|
||||
<image src="/static/chat/send2.png" style="width: 30rpx; height: 30rpx;" />
|
||||
<!-- <uni-icons type="paperplane-filled"></uni-icons>-->
|
||||
<!-- <u-icon name="arrow-up" size="18" color="#fff" v-if="!voiceInputActive"></u-icon>-->
|
||||
<!-- <!– 语音状态图标 –>-->
|
||||
<!-- <view class="voice-wave" v-else>-->
|
||||
<!-- <u-icon name="mic-fill" size="16" color="#fff"></u-icon>-->
|
||||
<!-- </view>-->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- ================= 录音全屏蒙层 ================= -->
|
||||
@@ -524,7 +544,7 @@
|
||||
*/
|
||||
import { webSocketManager } from '@/utils/ws/websocket';
|
||||
import { ChatManager } from '@/store/chat/chat.js';
|
||||
import {getChatRegisterInfoApi, sendToUserApi, upLoadChatFileApi, getDoctorInfoApi} from "@/request/api/im";
|
||||
import {getChatRegisterInfoApi, sendToUserApi, upLoadChatFileApi, getDoctorInfoApi, getRoomStatusApi} from "@/request/api/im";
|
||||
import { checkDev } from '@/utils/utils';
|
||||
|
||||
// 根据环境获取上传URL
|
||||
@@ -537,7 +557,7 @@ const uploadBaseUrl = isDev ? 'http://127.0.0.1:18001/api/mobile' : 'https://api
|
||||
*/
|
||||
function getParsedContent(messageType, messageContent) {
|
||||
// 需要解析的类型:处方(4), 挂号(10), 经历(11), 商品(12), 结束(13)
|
||||
if ([4, 10, 11, 12, 13].includes(messageType)) {
|
||||
if ([4, 9, 10, 11, 12, 13].includes(messageType)) {
|
||||
try {
|
||||
// 如果已经是对象则直接返回,否则解析字符串
|
||||
return typeof messageContent === 'object' ? messageContent : JSON.parse(messageContent);
|
||||
@@ -627,7 +647,10 @@ export default {
|
||||
express_name: "李测试",
|
||||
total_pay_price: "11.94"
|
||||
},
|
||||
registerData: {}
|
||||
registerData: {},
|
||||
|
||||
// 问诊是否已结束
|
||||
isConsultationEnded: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -655,9 +678,22 @@ export default {
|
||||
ChatManager.resetUnreadCount(option.room_id);
|
||||
// 获取缓存的房间消息
|
||||
this.messages = ChatManager.getRoomMessages(option.room_id);
|
||||
|
||||
// 检查房间状态是否为结束问诊(优先使用 option 参数)
|
||||
if (option.room_status === '1' || option.room_status === 1) {
|
||||
this.isConsultationEnded = true;
|
||||
} else if (this.messages.some(msg => msg.message_type === 13)) {
|
||||
// 兼容:检查历史消息中是否有结束问诊消息
|
||||
this.isConsultationEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 检查医生信息是否完整,如果不完整则从API获取
|
||||
// 5. 从 API 获取最新房间状态(确保状态最新)
|
||||
if (option.room_id) {
|
||||
this.fetchRoomStatus(option.room_id);
|
||||
}
|
||||
|
||||
// 6. 检查医生信息是否完整,如果不完整则从API获取
|
||||
const nickName = this.doctorUserInfo.nick_name;
|
||||
if (!nickName || nickName === 'undefined' || nickName === 'null') {
|
||||
this.fetchDoctorInfo(doctorId);
|
||||
@@ -665,13 +701,11 @@ export default {
|
||||
// 设置导航栏标题
|
||||
uni.setNavigationBarTitle({
|
||||
title: `与${nickName}医生的对话`,
|
||||
success: () => {
|
||||
console.log('修改标题成功');
|
||||
}
|
||||
success: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
// 6. 初次渲染滚动到底部
|
||||
// 7. 初次渲染滚动到底部
|
||||
// Fix: 页面加载时延时触发滚动到锚点
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
@@ -753,9 +787,7 @@ export default {
|
||||
// 更新导航栏标题
|
||||
uni.setNavigationBarTitle({
|
||||
title: `与${this.doctorUserInfo.nick_name}医生的对话`,
|
||||
success: () => {
|
||||
console.log('修改标题成功');
|
||||
}
|
||||
success: () => {}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -767,6 +799,22 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取房间状态
|
||||
* 判断问诊是否已结束
|
||||
*/
|
||||
async fetchRoomStatus(roomId) {
|
||||
try {
|
||||
const res = await getRoomStatusApi({ room_id: roomId });
|
||||
if (res.data && res.data.result) {
|
||||
// 根据房间状态设置问诊结束状态(status=1结束,status=0正常)
|
||||
this.isConsultationEnded = res.data.result.status === 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取房间状态失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查隐私授权
|
||||
* 微信小程序从2023年9月起要求在使用敏感API前获取用户隐私授权
|
||||
@@ -819,13 +867,6 @@ export default {
|
||||
|
||||
// 设置样式(使用 px 单位,因为系统信息返回的是 px)
|
||||
this.scrollViewStyle = `height: ${scrollHeight}px;`;
|
||||
|
||||
console.log('Scroll-view height calculated:', {
|
||||
windowHeight,
|
||||
headerHeight,
|
||||
footerHeight,
|
||||
scrollHeight
|
||||
});
|
||||
}).exec();
|
||||
}).exec();
|
||||
});
|
||||
@@ -890,17 +931,93 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 异步获取挂号详情并更新消息内容 (保留原逻辑)
|
||||
* 批量加载挂号卡片信息
|
||||
* 遍历消息列表,找出需要加载的挂号卡片并请求后端
|
||||
*/
|
||||
loadRegisterCards() {
|
||||
let that = this;
|
||||
this.messages.forEach((msg, index) => {
|
||||
if (msg.message_type === 10) {
|
||||
const parsedContent = getParsedContent(msg.message_type, msg.message_content);
|
||||
// 如果已加载或没有 id,跳过
|
||||
if (parsedContent._loaded || !parsedContent.id) {
|
||||
return;
|
||||
}
|
||||
// 调用 API 获取完整信息
|
||||
that.fetchRegisterCardInfo(msg, index, parsedContent.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取单个挂号卡片的详细信息
|
||||
* @param {Object} msg - 消息对象
|
||||
* @param {Number} index - 消息在列表中的索引(仅作为备用)
|
||||
* @param {Number} registerId - 挂号 ID
|
||||
*/
|
||||
fetchRegisterCardInfo(msg, index, registerId) {
|
||||
let that = this;
|
||||
// 保存消息 ID,可能是数字或字符串
|
||||
const messageId = msg.id;
|
||||
|
||||
// 调试日志:确认 msg.id 的实际值
|
||||
console.log('=== fetchRegisterCardInfo ===');
|
||||
console.log('msg:', msg);
|
||||
console.log('msg.id:', msg.id, typeof msg.id);
|
||||
console.log('registerId:', registerId);
|
||||
|
||||
getChatRegisterInfoApi({
|
||||
id: registerId,
|
||||
message_id: messageId || '' // 确保传递,即使为空
|
||||
}).then((res) => {
|
||||
if (res.data && res.data.result) {
|
||||
// 使用消息 ID 重新查找索引,而不是使用传入的 index
|
||||
// 原因:异步请求期间消息列表可能发生变化,index 可能已失效
|
||||
let targetIndex = index;
|
||||
if (messageId) {
|
||||
const foundIndex = that.messages.findIndex(m =>
|
||||
m.id === messageId || String(m.id) === String(messageId)
|
||||
);
|
||||
if (foundIndex !== -1) {
|
||||
targetIndex = foundIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 _loaded 标识,避免重复请求
|
||||
const resultWithFlag = {
|
||||
...res.data.result,
|
||||
_loaded: true
|
||||
};
|
||||
|
||||
// 使用 $set 确保响应式更新
|
||||
that.$set(that.messages, targetIndex, {
|
||||
...that.messages[targetIndex],
|
||||
message_content: JSON.stringify(resultWithFlag)
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error('获取挂号信息失败:', err);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 异步获取挂号详情并更新消息内容(兼容旧调用方式)
|
||||
* 后端会更新数据库中的 message_content,下次直接使用缓存
|
||||
*/
|
||||
getChatRegisterInfo(item, index) {
|
||||
let that = this;
|
||||
const parsedContent = getParsedContent(item.message_type, item.message_content);
|
||||
getChatRegisterInfoApi({
|
||||
id: parsedContent.id
|
||||
}).then((res) => {
|
||||
that.messages[index].message_content = JSON.stringify(res.data.result);
|
||||
console.log('getChatRegisterInfo', that.messages[index]);
|
||||
})
|
||||
|
||||
// 如果已经加载过完整信息,直接返回不再请求
|
||||
if (parsedContent._loaded) {
|
||||
return item;
|
||||
}
|
||||
|
||||
// 检查是否有 id
|
||||
if (!parsedContent.id) {
|
||||
return item;
|
||||
}
|
||||
|
||||
this.fetchRegisterCardInfo(item, index, parsedContent.id);
|
||||
return item;
|
||||
},
|
||||
|
||||
@@ -923,6 +1040,9 @@ export default {
|
||||
this.messages = ChatManager.roomMessages[this.doctorUserInfo.room_id] || [];
|
||||
}
|
||||
|
||||
// 加载完消息后,检查是否有需要动态获取的挂号卡片
|
||||
this.loadRegisterCards();
|
||||
|
||||
// 修复:如果是触顶加载(isLoadHistory=true),强制 type 为 0,保持滚动位置
|
||||
// 只有在初始化加载时才滚动到底部(type=1 且 isLoadHistory=false)
|
||||
const isHistoryLoad = this.isLoadHistory;
|
||||
@@ -956,10 +1076,13 @@ export default {
|
||||
|
||||
/**
|
||||
* WebSocket 消息处理
|
||||
* 直接处理当前房间消息,不走 ChatManager 避免重复
|
||||
*/
|
||||
handleSocketMessage(data) {
|
||||
if (typeof ChatManager !== 'undefined') {
|
||||
ChatManager.handleIncomingMessage(data);
|
||||
// 只处理当前房间的消息
|
||||
if (data.room_id === this.doctorUserInfo.room_id) {
|
||||
this.addMessage(data);
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -967,7 +1090,6 @@ export default {
|
||||
* 监听新消息事件
|
||||
*/
|
||||
handleNewRoomMessage(message) {
|
||||
console.log('handle', message);
|
||||
this.addMessage(message);
|
||||
this.scrollToBottom();
|
||||
},
|
||||
@@ -995,7 +1117,6 @@ export default {
|
||||
if (existingIndexById !== -1) {
|
||||
// 如果 ID 相同,更新现有消息(服务器返回的消息替换临时消息)
|
||||
this.messages[existingIndexById] = message;
|
||||
console.log('update message by id', message.id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1041,7 +1162,6 @@ export default {
|
||||
if (tempMessageIndex !== -1) {
|
||||
// 找到临时消息,用服务器返回的消息替换
|
||||
this.messages[tempMessageIndex] = message;
|
||||
console.log('replace temporary message with server message', message.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1066,13 +1186,30 @@ export default {
|
||||
if (existingIndexByContent !== -1) {
|
||||
// 如果找到相似消息,用服务器返回的消息替换临时消息(保留服务器 ID)
|
||||
this.messages[existingIndexByContent] = message;
|
||||
console.log('update message by content', message.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 新消息,添加到列表
|
||||
console.log('push new message', message.id);
|
||||
this.messages.push(message);
|
||||
|
||||
// 如果是挂号卡片消息,检查是否需要加载完整信息
|
||||
if (message.message_type === 10) {
|
||||
const parsedContent = getParsedContent(message.message_type, message.message_content);
|
||||
if (!parsedContent._loaded && parsedContent.id) {
|
||||
const newIndex = this.messages.length - 1;
|
||||
this.fetchRegisterCardInfo(message, newIndex, parsedContent.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 检测结束问诊消息,立即设置问诊结束状态
|
||||
if (message.message_type === 13) {
|
||||
this.isConsultationEnded = true;
|
||||
}
|
||||
|
||||
// 收到任何消息后,都从 API 确认房间状态
|
||||
if (this.doctorUserInfo.room_id) {
|
||||
this.fetchRoomStatus(this.doctorUserInfo.room_id);
|
||||
}
|
||||
},
|
||||
|
||||
toggleDoctorDetail() {
|
||||
@@ -1134,7 +1271,7 @@ export default {
|
||||
});
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log(res, '上传失败');
|
||||
console.error('上传失败', res);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1195,7 +1332,7 @@ export default {
|
||||
// 构造请求数据 (发送给服务器)
|
||||
const requestData = {
|
||||
room_id: this.doctorUserInfo.room_id,
|
||||
sender_user_id: this.currentUserId,
|
||||
sender_user_id: `user-${this.currentUserId}`,
|
||||
receiver_user_id: receiverUserId,
|
||||
message_type: messageType,
|
||||
message_content: message.message_content || message.content || '',
|
||||
@@ -1289,7 +1426,6 @@ export default {
|
||||
},
|
||||
|
||||
playAudio(msg) {
|
||||
console.log('播放音频:', getParsedContent(msg.message_type, msg.message_content));
|
||||
// TODO: 实际音频播放逻辑,可使用 uni.createInnerAudioContext
|
||||
// 设置 this.currentPlayingAudio = msg.id 实现动画效果
|
||||
},
|
||||
@@ -1317,7 +1453,7 @@ export default {
|
||||
this.uploadAndSendFile(file, 'image');
|
||||
});
|
||||
},
|
||||
fail: (e) => { console.log('错误', e); },
|
||||
fail: (e) => { console.error('选择图片错误', e); },
|
||||
});
|
||||
} else if (type === 'video') {
|
||||
// 选择视频
|
||||
@@ -1328,7 +1464,7 @@ export default {
|
||||
success: res => {
|
||||
this.uploadAndSendFile(res.tempFilePath, 'video');
|
||||
},
|
||||
fail: (e) => { console.log('选择视频错误', e); },
|
||||
fail: (e) => { console.error('选择视频错误', e); },
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -1363,7 +1499,7 @@ export default {
|
||||
},
|
||||
fail: (res) => {
|
||||
uni.hideLoading();
|
||||
console.log(res, '上传失败');
|
||||
console.error('上传失败', res);
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
@@ -1780,7 +1916,7 @@ $radius-bubble: 20rpx; // 气泡圆角
|
||||
|
||||
// 图片、视频、卡片类型去除默认气泡样式 (背景、阴影、内边距)
|
||||
&.type-1, &.type-3,
|
||||
&.type-4, &.type-10, &.type-11, &.type-12, &.type-13 {
|
||||
&.type-4, &.type-9, &.type-10, &.type-11, &.type-12, &.type-13 {
|
||||
padding: 0;
|
||||
overflow: visible; // 允许卡片阴影溢出
|
||||
background: transparent;
|
||||
@@ -1932,6 +2068,17 @@ $radius-bubble: 20rpx; // 气泡圆角
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
|
||||
// 问诊已结束提示
|
||||
.consultation-ended-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.toolbar-area {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
@@ -2082,4 +2229,43 @@ $radius-bubble: 20rpx; // 气泡圆角
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
// ================= 系统消息卡片样式 =================
|
||||
.system-message-card {
|
||||
background: #fffbe6;
|
||||
border: 1px solid #ffe58f;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin: 20rpx auto;
|
||||
max-width: 80%;
|
||||
|
||||
.price-update-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.notice-text {
|
||||
color: #d48806;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.price-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
padding-left: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.system-message {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
|
||||
<view class="score">
|
||||
<u-rate :count="count" v-model="value" active-color="#FB8C00" inactive-color="#E2E8F0" size="40">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 名片 -->
|
||||
<view class="card">
|
||||
<!-- 姓名 -->
|
||||
@@ -118,6 +119,7 @@
|
||||
userList: [],
|
||||
registList: [],
|
||||
peopleList: [],
|
||||
rType: '0', // 挂号类型
|
||||
}
|
||||
},
|
||||
onLoad(e) {
|
||||
@@ -153,6 +155,7 @@
|
||||
} else {
|
||||
// 点击详情
|
||||
this.doctorID = e.id
|
||||
this.rType = e.r_type || '0' // 接收挂号类型参数
|
||||
const id = e.sts_id || uni.getStorageSync('store_id')
|
||||
if (id!='undefined' && id) {
|
||||
console.log('idddd');
|
||||
@@ -187,7 +190,7 @@
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: "/subPackages/doctor/doctor-userinfo?id=" + this.doctorID,
|
||||
url: "/subPackages/doctor/doctor-userinfo?id=" + this.doctorID + "&r_type=" + this.rType,
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 按钮 -->
|
||||
<view class="head">
|
||||
<view class="head_left">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 个人介绍 -->
|
||||
<view class="card">
|
||||
<view class="card_left">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="title">
|
||||
请描述您的病情
|
||||
</view>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 标题 -->
|
||||
<view class="title">
|
||||
您需要为谁{{ registerType === '3'? '购药' : '挂号' }}
|
||||
@@ -25,23 +26,15 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新增:挂号类型选择 -->
|
||||
<view class="card" style="padding-bottom: 0" v-if="registerType !== '3'">
|
||||
<!-- 挂号类型展示(只读) -->
|
||||
<view class="card" style="padding-bottom: 0">
|
||||
<view class="card_title">
|
||||
挂号类型
|
||||
</view>
|
||||
<view class="card_names">
|
||||
<u-radio-group
|
||||
v-model="registerType"
|
||||
size="44rpx"
|
||||
active-color="#2979FF"
|
||||
@change="handleTypeChange"
|
||||
>
|
||||
<u-radio v-if="registerType === '0'" name="0" label="线下问诊">线下问诊</u-radio>
|
||||
<u-radio v-if="checkDev('open-im')" name="1" label="线上咨询">线上咨询</u-radio>
|
||||
<u-radio v-if="checkDev('open-im')" name="2" label="线上咨询">在线复诊</u-radio>
|
||||
<u-radio v-if="checkDev('open-im')" name="3" label="线上咨询">预约购药</u-radio>
|
||||
</u-radio-group>
|
||||
<view class="register-type-display">
|
||||
<text class="type-label">{{ registerTypeLabel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -229,6 +222,16 @@ export default {
|
||||
}
|
||||
})
|
||||
return list
|
||||
},
|
||||
// 挂号类型标签
|
||||
registerTypeLabel() {
|
||||
const typeMap = {
|
||||
'0': '线下就诊',
|
||||
'1': '在线问诊',
|
||||
'2': '预约购药',
|
||||
'3': '在线复诊'
|
||||
};
|
||||
return typeMap[this.registerType] || '未知类型';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -637,6 +640,21 @@ export default {
|
||||
::v-deep .u-input {
|
||||
width: 380rpx;
|
||||
}
|
||||
|
||||
.register-type-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type-label {
|
||||
display: inline-block;
|
||||
padding: 8rpx 24rpx;
|
||||
background: linear-gradient(135deg, #4175EE 0%, #298dff 100%);
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card_history {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 描述 -->
|
||||
<view class="card">
|
||||
{{list.content['content']}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 物流提示信息 -->
|
||||
<view class="order-text-tip" v-if="orderText">
|
||||
<text>{{ orderText }}</text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 搜索框 -->
|
||||
<!-- <view class="search">
|
||||
<u-search placeholder="请输入药品名称" bg-color="#F1F5F9" border-color="#F1F5F9" :show-action="false"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 中转页 -->
|
||||
<view class="none">
|
||||
<!-- 订单未生成 -->
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<MessageNotification />
|
||||
<!-- 头部 -->
|
||||
<view class="head">
|
||||
<image src="@/static/empty/login.png" mode=""></image>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="head" @click="toChoose">
|
||||
<view class="head_name">{{infoUserList[0].name || '请选择就诊人'}}</view>
|
||||
<view class="head_qh">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 头部 -->
|
||||
<view class="head">
|
||||
<view class="title">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 基础信息 -->
|
||||
<view class="card">
|
||||
<view class="card_title">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="card-box">
|
||||
<!-- 基础信息 -->
|
||||
<view class="card">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="none" v-if="showList==false">
|
||||
<!-- 暂未添加就诊人 -->
|
||||
<view class="conter">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="all" v-if="status==1||status==4">
|
||||
<!-- 处方 -->
|
||||
<view class="head">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="head" @click="toChoose">
|
||||
<view class="head_name">{{infoUserList[0].name || '请选择就诊人'}}</view>
|
||||
<view class="head_qh">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 头部 -->
|
||||
<view class="head" @click="show = true">
|
||||
<view class="head_sign">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="card">
|
||||
<image src="/static/empty/yhq.png" mode=""></image>
|
||||
<view class="store">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 快递——自提 -->
|
||||
<view class="_choose">
|
||||
<text>配送方式</text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="content safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 待支付 -->
|
||||
<view class="unpaid" v-if="orderInfo.order_status.status==1">
|
||||
<view class="title">
|
||||
|
||||
@@ -1,123 +1,119 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部吸顶区域:自定义紧凑布局 -->
|
||||
<view class="header-sticky-box">
|
||||
<view class="header-row">
|
||||
<!-- 1. 左侧:自定义下拉筛选触发器 -->
|
||||
<view class="category-trigger" @click.stop="toggleCategoryMenu">
|
||||
<text class="trigger-text">{{ currentCategoryLabel }}</text>
|
||||
<u-icon
|
||||
:name="showCategoryMenu ? 'arrow-up' : 'arrow-down'"
|
||||
size="24"
|
||||
color="#333"
|
||||
style="margin-left: 6rpx;"
|
||||
></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 2. 右侧:搜索栏 (带搜索按钮) -->
|
||||
<view class="search-box">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索药品/症状"
|
||||
:show-action="true"
|
||||
action-text="搜索"
|
||||
bg-color="#F5F7FA"
|
||||
placeholder-color="#9CA3AF"
|
||||
shape="round"
|
||||
height="64"
|
||||
:input-style="{fontSize: '26rpx'}"
|
||||
@custom="handleSearch"
|
||||
@search="handleSearch"
|
||||
@clear="clearSearch"
|
||||
></u-search>
|
||||
</view>
|
||||
<MessageNotification />
|
||||
<!-- 1. 顶部搜索栏 -->
|
||||
<view class="header-section">
|
||||
<view class="search-box">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索药品/症状"
|
||||
:show-action="true"
|
||||
action-text="搜索"
|
||||
bg-color="#F5F7FA"
|
||||
placeholder-color="#9CA3AF"
|
||||
shape="round"
|
||||
height="64"
|
||||
:input-style="{fontSize: '26rpx'}"
|
||||
@custom="handleSearch"
|
||||
@search="handleSearch"
|
||||
@clear="clearSearch"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 自定义下拉菜单面板 (绝对定位) -->
|
||||
<view
|
||||
class="category-popup"
|
||||
v-if="showCategoryMenu"
|
||||
@click.stop=""
|
||||
>
|
||||
<view
|
||||
class="cat-item"
|
||||
v-for="(opt, index) in categoryOptions"
|
||||
:key="opt.value"
|
||||
:class="{ active: selectedCategory === opt.value }"
|
||||
@click="selectCategory(opt)"
|
||||
>
|
||||
<text>{{ opt.label }}</text>
|
||||
<u-icon v-if="selectedCategory === opt.value" name="checkmark" color="#2B85E4" size="28"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 下拉菜单遮罩层 -->
|
||||
<view class="mask-layer" v-if="showCategoryMenu" @click="closeCategoryMenu"></view>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表区域 -->
|
||||
<view class="list-section">
|
||||
<!-- 列表项卡片 -->
|
||||
<view
|
||||
v-for="(item, index) in products"
|
||||
:key="item.id"
|
||||
class="list-item"
|
||||
@click="handClickProduct(item)"
|
||||
>
|
||||
<!-- 左侧:药品图片 -->
|
||||
<view class="image-wrapper">
|
||||
<image
|
||||
class="item-image"
|
||||
:class="{ 'prescription-blur': item.drug.is_otc === 0 && item.drug.type == 2 }"
|
||||
:src="item.drug.image || '/static/mine/avatar_1.png'"
|
||||
mode="aspectFill"
|
||||
lazy-load
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:详细内容 -->
|
||||
<view class="item-content">
|
||||
<view class="top-info">
|
||||
<!-- 标题 -->
|
||||
<view class="item-title">{{ item.drug.drug_name }}</view>
|
||||
|
||||
<!-- 标签行 -->
|
||||
<view class="tags-row">
|
||||
<!-- 动态标签:保健食品/OTC/Rx -->
|
||||
<text
|
||||
class="tag-label"
|
||||
:class="getProductTag(item.drug).className"
|
||||
>
|
||||
{{ getProductTag(item.drug).label }}
|
||||
</text>
|
||||
|
||||
<!-- 规格 -->
|
||||
<text class="tag-spec">{{ item.drug.specification || '标准规格' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 主治功能 -->
|
||||
<view class="item-function" v-if="item.drug.function">
|
||||
主治:{{ item.drug.function }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部价格与操作 -->
|
||||
<view class="item-bottom">
|
||||
<view class="price-section">
|
||||
<text class="currency">¥</text>
|
||||
<text class="price-val">{{ item.price }}</text>
|
||||
</view>
|
||||
|
||||
<view class="cart-btn" @click.stop="showCartPopup(item)">
|
||||
<u-icon name="plus" color="#ffffff" size="30"></u-icon>
|
||||
</view>
|
||||
<!-- 2. 顶部专区Tab -->
|
||||
<view class="zone-tabs-wrapper">
|
||||
<scroll-view scroll-x class="zone-tabs-scroll">
|
||||
<view class="zone-tabs">
|
||||
<view
|
||||
v-for="(zone, index) in homeZones"
|
||||
:key="zone.id || index"
|
||||
class="zone-tab-item"
|
||||
:class="{ active: selectedZoneIndex === index }"
|
||||
@click="onZoneChange(index)"
|
||||
>
|
||||
<text>{{ zone.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 主体内容区:左侧分类 + 右侧商品 -->
|
||||
<view class="main-content">
|
||||
<!-- 左侧分类栏 -->
|
||||
<view class="category-sidebar">
|
||||
<scroll-view scroll-y class="category-scroll">
|
||||
<view
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategoryId === 0 }"
|
||||
@click="selectCategory(0)"
|
||||
>
|
||||
<text>全部</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="cat in categoryList"
|
||||
:key="cat.id"
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategoryId === cat.id }"
|
||||
@click="selectCategory(cat.id)"
|
||||
>
|
||||
<text class="u-line-1">{{ cat.category_name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="products.length === 0" class="empty-state">
|
||||
<u-empty text="暂无相关药品" mode="search" margin-top="100"></u-empty>
|
||||
<!-- 右侧商品列表区 -->
|
||||
<view class="product-area">
|
||||
<scroll-view scroll-y class="product-scroll" @scrolltolower="loadMore">
|
||||
<!-- 商品列表 - 两列布局 -->
|
||||
<view class="product-grid" v-if="products.length > 0">
|
||||
<view
|
||||
v-for="(item, index) in products"
|
||||
:key="item.id"
|
||||
class="product-card"
|
||||
@click="handClickProduct(item)"
|
||||
>
|
||||
<!-- 商品图片 -->
|
||||
<view class="product-img-box">
|
||||
<image
|
||||
class="product-img"
|
||||
:class="{ 'prescription-blur': item.drug && item.drug.is_otc === 0 && item.drug.type == 2 }"
|
||||
:src="(item.drug && item.drug.image) ? item.drug.image : '/static/mine/avatar_1.png'"
|
||||
mode="aspectFill"
|
||||
lazy-load
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="product-info">
|
||||
<view class="product-title u-line-2">{{ item.drug ? item.drug.drug_name : '' }}</view>
|
||||
<view class="product-tags">
|
||||
<text
|
||||
class="tag-label"
|
||||
:class="getProductTag(item.drug).className"
|
||||
>
|
||||
{{ getProductTag(item.drug).label }}
|
||||
</text>
|
||||
<text class="tag-spec u-line-1">{{ (item.drug && item.drug.specification) ? item.drug.specification : '标准规格' }}</text>
|
||||
</view>
|
||||
<view class="product-bottom">
|
||||
<view class="price-box">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-val">{{ item.price }}</text>
|
||||
</view>
|
||||
<view class="cart-btn" @click.stop="showCartPopup(item)">
|
||||
<u-icon name="plus" color="#ffffff" size="26"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="products.length === 0" class="empty-state">
|
||||
<u-empty text="暂无相关药品" mode="search" margin-top="100"></u-empty>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -176,7 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getStoreDrugListBySalespersonApi, getHomeZonesApi } from "@/request/api/product";
|
||||
import { getStoreDrugListBySalespersonApi, getHomeZonesApi, getDrugCategoriesApi } from "@/request/api/product";
|
||||
import { getCartCountsApi, saveCartApi } from "@/request/api/cart";
|
||||
|
||||
export default {
|
||||
@@ -185,14 +181,18 @@ export default {
|
||||
products: [],
|
||||
searchKeyword: "",
|
||||
|
||||
// 自定义下拉菜单相关
|
||||
selectedCategory: 'otc',
|
||||
showCategoryMenu: false, // 控制下拉菜单显示
|
||||
categoryOptions: [], // 从后端获取分类选项
|
||||
// 专区相关
|
||||
homeZones: [],
|
||||
selectedZoneIndex: 0,
|
||||
selectedZoneType: 'otc',
|
||||
|
||||
// 分类相关
|
||||
categoryList: [],
|
||||
selectedCategoryId: 0,
|
||||
|
||||
doctorId: 0,
|
||||
r_type: 0,
|
||||
delegateStoreId: '', // 委托诊所ID
|
||||
delegateStoreId: '',
|
||||
cartCount: 0,
|
||||
cartPosition: { x: 0, y: 0 },
|
||||
cartMove: { startX: 0, startY: 0, moving: false },
|
||||
@@ -202,93 +202,113 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 获取当前选中的分类标签
|
||||
currentCategoryLabel() {
|
||||
const target = this.categoryOptions.find(item => item.value === this.selectedCategory);
|
||||
return target ? target.label : '分类';
|
||||
},
|
||||
getProductImage() {
|
||||
return this.currentProduct.drug && this.currentProduct.drug.image ? this.currentProduct.drug.image : '/static/mine/avatar_1.png';
|
||||
return (this.currentProduct.drug && this.currentProduct.drug.image) ? this.currentProduct.drug.image : '/static/mine/avatar_1.png';
|
||||
},
|
||||
getProductName() {
|
||||
return this.currentProduct.drug && this.currentProduct.drug.drug_name ? this.currentProduct.drug.drug_name : '';
|
||||
return (this.currentProduct.drug && this.currentProduct.drug.drug_name) ? this.currentProduct.drug.drug_name : '';
|
||||
},
|
||||
getProductSpec() {
|
||||
return this.currentProduct.drug && this.currentProduct.drug.specification ? this.currentProduct.drug.specification : '标准规格';
|
||||
return (this.currentProduct.drug && this.currentProduct.drug.specification) ? this.currentProduct.drug.specification : '标准规格';
|
||||
}
|
||||
},
|
||||
onLoad(e) {
|
||||
this.doctorId = e.doctor_id;
|
||||
this.r_type = e.register_type;
|
||||
// 获取委托诊所ID(如果有)
|
||||
this.delegateStoreId = e.delegate_store_id || '';
|
||||
// 根据首页传入的专区类型初始化分类(默认OTC)
|
||||
|
||||
// 根据首页传入的专区类型初始化
|
||||
if (e.zone_type) {
|
||||
this.selectedCategory = e.zone_type;
|
||||
this.selectedZoneType = e.zone_type;
|
||||
} else {
|
||||
this.selectedCategory = 'otc';
|
||||
this.selectedZoneType = 'otc';
|
||||
}
|
||||
// 获取分类选项
|
||||
this.getCategoryOptions();
|
||||
this.getList();
|
||||
|
||||
// 获取专区列表
|
||||
this.getHomeZones();
|
||||
},
|
||||
methods: {
|
||||
// 获取分类选项(从后端获取专区配置)
|
||||
getCategoryOptions() {
|
||||
// 获取专区列表
|
||||
getHomeZones() {
|
||||
getHomeZonesApi({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
const zones = res.data.result || res.data.data || [];
|
||||
// 将专区数据转换为分类选项格式
|
||||
this.categoryOptions = zones.map(zone => ({
|
||||
label: zone.title || this.getCategoryLabelByType(zone.type),
|
||||
value: zone.type
|
||||
}));
|
||||
// 如果当前选中的分类不在选项中,重置为第一个选项
|
||||
if (this.categoryOptions.length > 0) {
|
||||
const hasCurrentCategory = this.categoryOptions.some(opt => opt.value === this.selectedCategory);
|
||||
if (!hasCurrentCategory) {
|
||||
this.selectedCategory = this.categoryOptions[0].value;
|
||||
}
|
||||
this.homeZones = res.data.result || res.data.data || [];
|
||||
|
||||
// 根据传入的zone_type找到对应的索引
|
||||
if (this.homeZones.length > 0) {
|
||||
const index = this.homeZones.findIndex(z => z.type === this.selectedZoneType);
|
||||
this.selectedZoneIndex = index >= 0 ? index : 0;
|
||||
this.selectedZoneType = this.homeZones[this.selectedZoneIndex]?.type || 'otc';
|
||||
}
|
||||
|
||||
// 获取分类列表
|
||||
this.getCategoryList();
|
||||
}
|
||||
}).catch(() => {
|
||||
// 如果接口失败,使用默认分类选项
|
||||
this.categoryOptions = [
|
||||
{ label: 'OTC', value: 'otc' },
|
||||
{ label: '处方药', value: 'prescription' },
|
||||
{ label: '保健食品', value: 'health_food' }
|
||||
// 使用默认专区
|
||||
this.homeZones = [
|
||||
{ id: 1, title: 'OTC', type: 'otc' },
|
||||
{ id: 2, title: '保健食品', type: 'health_food' }
|
||||
];
|
||||
this.getCategoryList();
|
||||
});
|
||||
},
|
||||
// 根据type获取分类标签(备用)
|
||||
getCategoryLabelByType(type) {
|
||||
const typeMap = {
|
||||
'otc': 'OTC',
|
||||
'prescription': '处方药',
|
||||
'health_food': '保健食品',
|
||||
'service_package': '产品服务包'
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
|
||||
// 获取分类列表
|
||||
getCategoryList() {
|
||||
getDrugCategoriesApi({
|
||||
zone_type: this.selectedZoneType
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
// 将树形结构扁平化为一级分类列表
|
||||
const categories = res.data.result || [];
|
||||
this.categoryList = this.flattenCategories(categories);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.categoryList = [];
|
||||
}).finally(() => {
|
||||
// 重置分类选择并获取商品列表
|
||||
this.selectedCategoryId = 0;
|
||||
this.getList();
|
||||
});
|
||||
},
|
||||
// 切换下拉菜单显示
|
||||
toggleCategoryMenu() {
|
||||
this.showCategoryMenu = !this.showCategoryMenu;
|
||||
|
||||
// 扁平化分类树(只取一级分类,或展开所有层级)
|
||||
flattenCategories(categories, result = []) {
|
||||
for (const cat of categories) {
|
||||
result.push({
|
||||
id: cat.id,
|
||||
category_name: cat.category_name,
|
||||
level: cat.level,
|
||||
parent_id: cat.parent_id
|
||||
});
|
||||
if (cat.children && cat.children.length > 0) {
|
||||
this.flattenCategories(cat.children, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
// 关闭下拉菜单
|
||||
closeCategoryMenu() {
|
||||
this.showCategoryMenu = false;
|
||||
|
||||
// 专区切换
|
||||
onZoneChange(index) {
|
||||
this.selectedZoneIndex = index;
|
||||
this.selectedZoneType = this.homeZones[index]?.type || 'otc';
|
||||
this.selectedCategoryId = 0;
|
||||
// 重新获取分类和商品
|
||||
this.getCategoryList();
|
||||
},
|
||||
// 选择分类
|
||||
selectCategory(item) {
|
||||
this.selectedCategory = item.value;
|
||||
this.closeCategoryMenu();
|
||||
this.getList(); // 触发列表更新
|
||||
|
||||
// 分类选择
|
||||
selectCategory(categoryId) {
|
||||
this.selectedCategoryId = categoryId;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 核心标签判断逻辑
|
||||
getProductTag(drug) {
|
||||
if (!drug) return { label: 'OTC', className: 'tag-green' };
|
||||
if (drug.type === 3) {
|
||||
return { label: '保健食品', className: 'tag-orange' };
|
||||
}
|
||||
@@ -303,34 +323,36 @@ export default {
|
||||
const params = {
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
page: 1,
|
||||
limit: 100
|
||||
limit: 100,
|
||||
category: this.selectedZoneType
|
||||
};
|
||||
if (this.selectedCategory !== 'all') {
|
||||
params.category = this.selectedCategory;
|
||||
|
||||
if (this.selectedCategoryId > 0) {
|
||||
params.category_id = this.selectedCategoryId;
|
||||
}
|
||||
|
||||
if (this.searchKeyword) {
|
||||
params.keyword = this.searchKeyword;
|
||||
}
|
||||
|
||||
getStoreDrugListBySalespersonApi(params).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
this.products = res.data.result.records || [];
|
||||
this.cartCount = res.data.result.cart_count || 0;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// 执行搜索(点击按钮或回车)
|
||||
handleSearch() {
|
||||
this.getList();
|
||||
},
|
||||
// 清空搜索
|
||||
|
||||
clearSearch() {
|
||||
this.searchKeyword = "";
|
||||
this.getList();
|
||||
},
|
||||
|
||||
handClickProduct(item) {
|
||||
// 传递委托诊所ID(如果有)
|
||||
let url = `/subPackages/product/symptoms?id=${item.drug.id}&drug_name=${item.drug.drug_name}&is_otc=${item.drug.is_otc}&image=${item.drug.image}&price=${item.price}`;
|
||||
if (this.delegateStoreId) {
|
||||
url += `&delegate_store_id=${this.delegateStoreId}`;
|
||||
@@ -397,27 +419,29 @@ export default {
|
||||
this.updateCartCount();
|
||||
this.closePopup();
|
||||
this.$refs.uToast.show({ title: '已加入购物车', type: 'success' });
|
||||
})
|
||||
});
|
||||
},
|
||||
updateCartCount() {
|
||||
let that = this;
|
||||
getCartCountsApi({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
page: 1,
|
||||
limit: 100
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
that.cartCount = res.data.result.cart_count;
|
||||
this.cartCount = res.data.result.cart_count;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
loadMore() {
|
||||
// 可扩展加载更多功能
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
// 如果分类选项为空,重新获取
|
||||
if (this.categoryOptions.length === 0) {
|
||||
this.getCategoryOptions();
|
||||
if (this.homeZones.length === 0) {
|
||||
this.getHomeZones();
|
||||
} else {
|
||||
this.getList();
|
||||
}
|
||||
this.getList();
|
||||
this.initCartPosition();
|
||||
this.updateCartCount();
|
||||
},
|
||||
@@ -434,210 +458,229 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
background: linear-gradient(180deg, #f0f5ff 0%, #ffffff 100%);
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #F5F7FA;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部吸顶盒子 */
|
||||
.header-sticky-box {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||||
padding: 20rpx 24rpx;
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 自定义分类触发器 */
|
||||
.category-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20rpx;
|
||||
padding: 10rpx 0;
|
||||
|
||||
.trigger-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
max-width: 140rpx;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索框容器 */
|
||||
/* 1. 顶部搜索栏 */
|
||||
.header-section {
|
||||
background: #fff;
|
||||
padding: 16rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义下拉弹窗 */
|
||||
.category-popup {
|
||||
position: absolute;
|
||||
top: 100%; /* 吸附在 header 下方 */
|
||||
left: 24rpx; /* 对齐左边距 */
|
||||
width: 240rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.1);
|
||||
z-index: 101;
|
||||
padding: 10rpx 0;
|
||||
|
||||
.cat-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
/* 2. 顶部专区Tab */
|
||||
.zone-tabs-wrapper {
|
||||
background: #fff;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
|
||||
.zone-tabs-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.zone-tabs {
|
||||
display: inline-flex;
|
||||
padding: 0 24rpx;
|
||||
|
||||
.zone-tab-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
justify-content: center;
|
||||
padding: 24rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
&:active {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
color: #666;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.active {
|
||||
color: #2B85E4;
|
||||
font-weight: bold;
|
||||
background: #F0F7FF;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
background: linear-gradient(90deg, #5CADFF, #2B85E4);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 遮罩层 */
|
||||
.mask-layer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0); /* 透明遮罩,只用于点击关闭 */
|
||||
z-index: 99;
|
||||
}
|
||||
}
|
||||
|
||||
.list-section {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 商品卡片 */
|
||||
.list-item {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(65, 117, 238, 0.08);
|
||||
transition: transform 0.1s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
margin-right: 24rpx;
|
||||
.item-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: #f8f8f8;
|
||||
|
||||
// 处方药图片模糊处理
|
||||
&.prescription-blur {
|
||||
filter: blur(10px);
|
||||
-webkit-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
/* 3. 主体内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.top-info {
|
||||
.item-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin-bottom: 12rpx;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
/* 左侧分类栏 */
|
||||
.category-sidebar {
|
||||
width: 180rpx;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
|
||||
.category-scroll {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
padding: 0 12rpx;
|
||||
|
||||
text {
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #F5F7FA;
|
||||
color: #2B85E4;
|
||||
font-weight: bold;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6rpx;
|
||||
height: 48rpx;
|
||||
background: #2B85E4;
|
||||
border-radius: 0 3rpx 3rpx 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tags-row {
|
||||
/* 右侧商品区域 */
|
||||
.product-area {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.product-scroll {
|
||||
height: 100%;
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
/* 商品网格 - 两列布局 */
|
||||
.product-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
width: calc((100% - 16rpx) / 2);
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04);
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.product-img-box {
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
background: #f8f8f8;
|
||||
|
||||
.product-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&.prescription-blur {
|
||||
/* 暂时禁用模糊
|
||||
filter: blur(8px);
|
||||
-webkit-filter: blur(8px);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 16rpx;
|
||||
|
||||
.product-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
height: 72rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.product-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
|
||||
/* 通用标签基础样式 */
|
||||
gap: 8rpx;
|
||||
|
||||
.tag-label {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 12rpx;
|
||||
border: 1px solid;
|
||||
font-size: 18rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
font-weight: 500;
|
||||
|
||||
/* 颜色变种 */
|
||||
border: 1px solid;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.tag-green { color: #10B981; border-color: #10B981; background: #ECFDF5; }
|
||||
&.tag-red { color: #EF4444; border-color: #EF4444; background: #FEF2F2; }
|
||||
&.tag-orange { color: #F59E0B; border-color: #F59E0B; background: #FFFBEB; }
|
||||
}
|
||||
|
||||
|
||||
.tag-spec {
|
||||
font-size: 22rpx;
|
||||
color: #9CA3AF;
|
||||
background: #F3F4F6;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.item-function {
|
||||
font-size: 22rpx;
|
||||
color: #9CA3AF;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
|
||||
.price-section {
|
||||
color: #EF4444;
|
||||
font-weight: bold;
|
||||
.currency { font-size: 24rpx; margin-right: 2rpx; }
|
||||
.price-val { font-size: 36rpx; font-family: 'DIN Alternate', sans-serif; }
|
||||
}
|
||||
|
||||
.cart-btn {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #4A90E2, #5B8FF9);
|
||||
|
||||
.product-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 10rpx rgba(74, 144, 226, 0.3);
|
||||
|
||||
.price-box {
|
||||
color: #EF4444;
|
||||
font-weight: bold;
|
||||
|
||||
.price-symbol { font-size: 22rpx; }
|
||||
.price-val { font-size: 32rpx; font-family: 'DIN Alternate', sans-serif; }
|
||||
}
|
||||
|
||||
.cart-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #4A90E2, #5B8FF9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 8rpx rgba(74, 144, 226, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -646,6 +689,7 @@ export default {
|
||||
.cart-float {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
|
||||
.cart-icon-inner {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
@@ -656,6 +700,7 @@ export default {
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 20rpx rgba(74, 144, 226, 0.4);
|
||||
position: relative;
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: -6rpx;
|
||||
@@ -679,6 +724,7 @@ export default {
|
||||
.product-header-row {
|
||||
display: flex;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.popup-img {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
@@ -686,11 +732,13 @@ export default {
|
||||
margin-right: 24rpx;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.popup-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
.price-row {
|
||||
color: #EF4444;
|
||||
font-size: 28rpx;
|
||||
@@ -719,6 +767,7 @@ export default {
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
|
||||
&.add-cart {
|
||||
background: linear-gradient(90deg, #4A90E2, #5B8FF9);
|
||||
color: #fff;
|
||||
@@ -728,5 +777,20 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state { margin-top: 100rpx; }
|
||||
</style>
|
||||
.empty-state {
|
||||
margin-top: 100rpx;
|
||||
}
|
||||
|
||||
.u-line-1 {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.u-line-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<MessageNotification />
|
||||
<!-- 1. 顶部轮播图 -->
|
||||
<view class="swiper-section">
|
||||
<swiper
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom" v-if="registList.doctor">
|
||||
<MessageNotification />
|
||||
<u-navbar class="navbar" :is-back="true" title="挂号详情" :custom-back="customBack" title-color="#000">
|
||||
</u-navbar>
|
||||
<!-- 已预约 -->
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 预约成功 -->
|
||||
<view class="paided">
|
||||
<text class="iconfont icon-emichenggong"></text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 预约信息 -->
|
||||
<view class="title">
|
||||
预约信息
|
||||
@@ -158,8 +159,8 @@
|
||||
registerMap: {
|
||||
0: '挂号',
|
||||
1: '咨询',
|
||||
2: '复诊',
|
||||
3: '购药',
|
||||
2: '购药',
|
||||
3: '复诊',
|
||||
},
|
||||
rInfo: {},
|
||||
}
|
||||
@@ -227,29 +228,43 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 准备患者就诊经历数据
|
||||
const patientExperienceData = {
|
||||
symptom_description: that.rInfo.illness_info || '',
|
||||
has_visited: that.rInfo.has_visited || '0',
|
||||
has_used_drug: that.rInfo.has_used_drug || '0',
|
||||
drug_name: that.rInfo.drug_name || '',
|
||||
illness_info: that.rInfo.illness_info || '',
|
||||
doctor_id: doctorId,
|
||||
user_id: patientId,
|
||||
register_id: that.register_id,
|
||||
created_at: new Date().toLocaleString('zh-CN')
|
||||
};
|
||||
|
||||
// 5. 发送患者就诊经历消息(消息类型11)
|
||||
// 使用纯数字ID格式,与房间创建时的格式保持一致
|
||||
await sendToUserApi({
|
||||
room_id: roomId,
|
||||
sender_user_id: patientId, // 纯数字ID:'252'
|
||||
receiver_user_id: doctorId, // 纯数字ID:'39'
|
||||
message_type: 11, // 患者就诊经历卡片
|
||||
message_content: JSON.stringify(patientExperienceData),
|
||||
duration: 0
|
||||
});
|
||||
// 4. 仅药店模式(store_type='1')发送患者就诊经历卡片
|
||||
const storeType = uni.getStorageSync('store_type')
|
||||
if (storeType === '1' || storeType === 1) {
|
||||
const patientExperienceData = {
|
||||
symptom_description: that.rInfo.illness_info || '',
|
||||
has_visited: that.rInfo.has_visited || '0',
|
||||
has_used_drug: that.rInfo.has_used_drug || '0',
|
||||
drug_name: that.rInfo.drug_name || '',
|
||||
illness_info: that.rInfo.illness_info || '',
|
||||
doctor_id: doctorId,
|
||||
user_id: patientId,
|
||||
register_id: that.register_id,
|
||||
created_at: new Date().toLocaleString('zh-CN')
|
||||
};
|
||||
|
||||
// 5. 发送患者就诊经历消息(消息类型11)
|
||||
await sendToUserApi({
|
||||
room_id: roomId,
|
||||
sender_user_id: `user-${patientId}`,
|
||||
receiver_user_id: `doctor-${doctorId}`,
|
||||
message_type: 11, // 患者就诊经历卡片
|
||||
message_content: JSON.stringify(patientExperienceData),
|
||||
duration: 0
|
||||
});
|
||||
} else {
|
||||
// 诊所模式(store_type='0')发送挂号信息卡片(message_type=10)
|
||||
await sendToUserApi({
|
||||
room_id: roomId,
|
||||
sender_user_id: `user-${patientId}`,
|
||||
receiver_user_id: `doctor-${doctorId}`,
|
||||
message_type: 10, // 挂号信息卡片
|
||||
message_content: JSON.stringify({
|
||||
id: that.register_id,
|
||||
}),
|
||||
duration: 0
|
||||
});
|
||||
}
|
||||
|
||||
// 6. 生成订单(如果需要)
|
||||
genOrderApi({
|
||||
@@ -306,8 +321,8 @@
|
||||
}).then(result => {
|
||||
sendToUserApi({
|
||||
room_id: result.data.result.room_id,
|
||||
sender_user_id: that.currentUserId,
|
||||
receiver_user_id: that.currentDoctorId,
|
||||
sender_user_id: `user-${that.currentUserId}`,
|
||||
receiver_user_id: `doctor-${that.currentDoctorId}`,
|
||||
message_type: 10,
|
||||
message_content: JSON.stringify({
|
||||
id: that.register_id,
|
||||
@@ -358,7 +373,7 @@
|
||||
that.flag = false
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
this.$refs.uToast.show({
|
||||
that.$refs.uToast.show({
|
||||
title: res.data.msg || '支付失败',
|
||||
type: 'default',
|
||||
icon: false
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="none" v-if="actionList.length == 0">
|
||||
<!-- 暂无收货地址 -->
|
||||
<view class="conter">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="card" v-if="editShow==false">
|
||||
<view class="title">
|
||||
<u-field v-model="name" label="收货人" placeholder="请填写收货人名字" :field-style="{ background: '#F6F6F6',fontSize: '28rpx',paddingLeft:'30rpx',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 隐私内容 -->
|
||||
<view class="card">
|
||||
<view class="box">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<MessageNotification />
|
||||
<view class="header">
|
||||
<text class="title">开发者选项</text>
|
||||
<text class="subtitle">仅供开发测试使用</text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 头部 -->
|
||||
<view class="head">
|
||||
<image src="/static/empty/login.png" mode=""></image>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 隐私内容 -->
|
||||
<view class="card">
|
||||
<view class="box">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 隐私内容 -->
|
||||
<view class="card">
|
||||
<view class="box">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 隐私内容 -->
|
||||
<view class="card">
|
||||
<view class="box">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 退款原因 -->
|
||||
<view class="row-box">
|
||||
<text>退款原因</text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 收货地址 -->
|
||||
<view class="head" v-if="logisticsList.express_name!=''">
|
||||
<image src="/static/mine/mr_tx.png" mode=""></image>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="imgs">
|
||||
<image class="img" :src="drugDetailsList.image" mode=""></image>
|
||||
<text class="cf">{{drugDetailsList.is_otc=='0'?'处方药':'非处方药'}}</text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 搜索 -->
|
||||
<view class="search">
|
||||
<u-search placeholder="输入药品名称" bg-color="#F6F6F6" color="#999" border-color="#F6F6F6" :show-action="false"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 待付款 -->
|
||||
<view class="p_state">
|
||||
<view class="state">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 付款成功 -->
|
||||
<view class="head">
|
||||
<view class="paided">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 搜索 -->
|
||||
<view class="search">
|
||||
<u-search placeholder="输入商品名称" bg-color="#F5f5f5" color="#999999" border-color="#F5f5f5"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 搜索 -->
|
||||
<view class="search">
|
||||
<u-search placeholder="输入商品名称" bg-color="#F5f5f5" color="#999999" border-color="#F5f5f5"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 头部区域:显示数量和清空按钮 -->
|
||||
<view class="head">
|
||||
<view class="num">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 收货地址 未添加地址 -->
|
||||
<view class="area" @click="toArea" v-if="addList.length==0">
|
||||
<view class="state">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<view class="detail">
|
||||
<u-field v-model="txt" :focus="true" label-width="0" placeholder="请在此填写备注信息"
|
||||
placeholder-style="color:#94A3B8" type="textarea" @input="sumfontnum" :auto-height="false">
|
||||
|
||||
Reference in New Issue
Block a user