feat(home/chat): 列表骨架屏、聊天室己方头像与协议/医生详情优化
- 新增分包组件 ListSkeleton(doctor/message/product/article/category/zone) - 首页/消息/资讯首次加载改用骨架屏,刷新不再清空旧数据避免闪空 · 去掉 home getList、消息 onShow 中的数组清空 · 三页 pages.json 增加 easycom 与 componentPlaceholder 主包异步引用 - 聊天室己方头像改为读取本地 userinfo.avatarurl(登录后微信头像), mycollection 上传头像成功后同步写回 storage - 修复协议抽屉点不开:page-container 改为 v-if + :show; 协议名单独可点节点,匹配放宽 name/desc 双向 includes - 医生气泡头像可点击进入 doctor-detail?readonly=1 只读医生详情, 隐藏预约挂号区
This commit is contained in:
@@ -59,15 +59,17 @@
|
||||
>
|
||||
<view class="message-list-container" id="msglist" :style="{ paddingBottom: (keyboardHeight > 0 ? keyboardHeight + 10 : 0) + 'px' }">
|
||||
|
||||
<!-- 协议提示信息 -->
|
||||
<!-- 协议提示信息:协议名单独节点绑定点击,避免嵌套 text 吞事件 -->
|
||||
<view class="notice-box" v-if="showNotice">
|
||||
<view class="notice-text">
|
||||
<text v-for="(part, index) in parsedNoticeParts" :key="index">
|
||||
<block v-for="(part, index) in parsedNoticeParts" :key="index">
|
||||
<text v-if="part.type === 'text'">{{ part.text }}</text>
|
||||
<text v-else-if="part.type === 'agreement'" class="notice-link" @click="viewAgreement(part.agreementId)">
|
||||
《{{ part.text }}》
|
||||
</text>
|
||||
</text>
|
||||
<text
|
||||
v-else-if="part.type === 'agreement'"
|
||||
class="notice-link"
|
||||
@click.stop="viewAgreement(part.agreementId)"
|
||||
>《{{ part.text }}》</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -103,6 +105,7 @@
|
||||
@goToOrderMedicine="goToOrderMedicine"
|
||||
@viewOrder="viewOrder"
|
||||
@followUpDrugAnswered="onFollowUpDrugAnswered"
|
||||
@avatarClick="goDoctorDetail(msg)"
|
||||
/>
|
||||
|
||||
</view>
|
||||
@@ -115,9 +118,10 @@
|
||||
<view style="height: 180rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 协议抽屉 -->
|
||||
<!-- 协议抽屉:v-if 控制挂载,:show 控制显隐(微信 page-container 要求) -->
|
||||
<page-container
|
||||
:if="showAgreement"
|
||||
v-if="showAgreement"
|
||||
:show="showAgreement"
|
||||
position="bottom"
|
||||
round
|
||||
overlay
|
||||
@@ -295,6 +299,8 @@ export default {
|
||||
this.doctorUserInfo = option;
|
||||
if (option.avatar) this.doctorAvatar = option.avatar;
|
||||
this.currentUserId = 'user-' + uni.getStorageSync('user_id') || 'user-2512';
|
||||
// 从登录缓存读取己方微信/用户头像,避免一直用硬编码占位图
|
||||
this.syncUserAvatar();
|
||||
|
||||
let doctorId = option.user_id || '';
|
||||
if (doctorId.includes('-type-')) doctorId = doctorId.split('-type-')[0];
|
||||
@@ -352,6 +358,8 @@ export default {
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 从资料页返回时再同步一次,避免改过头像后聊天仍显示旧图
|
||||
this.syncUserAvatar();
|
||||
// 确保房间ID已设置(从ChatManager获取,作为备用)
|
||||
if (!this.doctorUserInfo.room_id && typeof ChatManager !== 'undefined') {
|
||||
const currentRoomId = ChatManager.getCurrentRoomId();
|
||||
@@ -391,6 +399,16 @@ export default {
|
||||
|
||||
methods: {
|
||||
// === 新增辅助方法 ===
|
||||
/**
|
||||
* 从本地 userinfo 同步己方头像(字段为 avatarurl,与登录/我的页一致)
|
||||
* 无头像时回退到与我的页相同的默认图
|
||||
*/
|
||||
syncUserAvatar() {
|
||||
const userinfo = uni.getStorageSync('userinfo') || {};
|
||||
this.userAvatar = userinfo.avatarurl
|
||||
|| 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/mr_tx.png';
|
||||
},
|
||||
|
||||
isMsgMine(msg) {
|
||||
return this.getSenderIdWithoutPrefix(msg.sender_user_id) === this.getCurrentUserIdWithoutPrefix();
|
||||
},
|
||||
@@ -495,22 +513,20 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
// 查找匹配的协议
|
||||
// 查找匹配的协议:名称/描述双向 includes,仍无匹配则用列表第一条保证可点
|
||||
const agreementName = match[1];
|
||||
const agreement = this.agreements.find(ag => ag.name === agreementName || ag.name.includes(agreementName));
|
||||
|
||||
const agreement = this.findAgreementByName(agreementName);
|
||||
|
||||
if (agreement) {
|
||||
// 找到匹配的协议,添加为可点击的链接
|
||||
parts.push({
|
||||
type: 'agreement',
|
||||
text: agreementName,
|
||||
agreementId: agreement.id
|
||||
});
|
||||
} else {
|
||||
// 没找到匹配的协议,作为普通文本显示
|
||||
parts.push({
|
||||
type: 'text',
|
||||
text: match[0] // 《协议名称》
|
||||
text: match[0]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -527,6 +543,24 @@ export default {
|
||||
|
||||
this.parsedNoticeParts = parts;
|
||||
},
|
||||
/**
|
||||
* 按文案中的协议名匹配协议列表(放宽 name/desc 双向包含)
|
||||
* 匹配不到但列表非空时回退第一条,避免《》无法点击
|
||||
*/
|
||||
findAgreementByName(agreementName) {
|
||||
const list = this.agreements || [];
|
||||
if (!list.length) return null;
|
||||
const name = String(agreementName || '');
|
||||
const hit = list.find((ag) => {
|
||||
const n = String(ag.name || '');
|
||||
const d = String(ag.desc || '');
|
||||
return n === name
|
||||
|| n.includes(name)
|
||||
|| name.includes(n)
|
||||
|| (d && (d === name || d.includes(name) || name.includes(d)));
|
||||
});
|
||||
return hit || list[0];
|
||||
},
|
||||
// 查看协议
|
||||
async viewAgreement(agreementId) {
|
||||
if (!agreementId) {
|
||||
@@ -541,6 +575,7 @@ export default {
|
||||
const agreement = this.agreements.find(ag => ag.id === agreementId);
|
||||
this.currentAgreementId = agreementId;
|
||||
this.currentAgreementName = agreement ? agreement.name : '协议详情';
|
||||
this.agreementContent = '';
|
||||
this.showAgreement = true;
|
||||
|
||||
try {
|
||||
@@ -569,6 +604,26 @@ export default {
|
||||
this.showAgreement = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击气泡医生头像进入医生详情(只读,不展示挂号)
|
||||
* 己方/助理头像不跳转
|
||||
*/
|
||||
goDoctorDetail(msg) {
|
||||
if (!msg || this.isMsgMine(msg)) return;
|
||||
const senderId = msg.sender_user_id || msg.senderId || '';
|
||||
if (senderId === 'doctor_assistant') return;
|
||||
const cleanId = String(this.doctorId || '')
|
||||
.replace(/^doctor-/, '')
|
||||
.replace(/-type-.*$/, '');
|
||||
if (!cleanId) {
|
||||
uni.showToast({ title: '医生信息缺失', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/doctor/doctor-detail?id=${cleanId}&readonly=1`
|
||||
});
|
||||
},
|
||||
|
||||
// === 音频播放逻辑 ===
|
||||
playAudio(msg) {
|
||||
const audioUrl = getParsedContent(msg.message_type, msg.message_content);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view class="bubble-container" :class="{ 'mine': isMine, 'other': !isMine }">
|
||||
<!-- 头像 -->
|
||||
<image class="avatar" :src="avatar" mode="aspectFill"></image>
|
||||
<!-- 头像:非己方可点进医生详情 -->
|
||||
<image class="avatar" :src="avatar" mode="aspectFill" @click.stop="onAvatarClick"></image>
|
||||
|
||||
<!-- 消息内容包裹 -->
|
||||
<view class="content-wrapper">
|
||||
@@ -146,13 +146,22 @@
|
||||
<text class="block-label">主诉</text>
|
||||
<text class="block-content">{{ parsedContent.chief_complaint }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="registerCardDrugNamesLine">
|
||||
<text class="label">所选药品</text>
|
||||
<text class="value">{{ registerCardDrugNamesLine }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="parsedContent.number != null && parsedContent.number !== ''">
|
||||
<text class="label">数量</text>
|
||||
<text class="value">各 {{ parsedContent.number }} 盒</text>
|
||||
<view v-if="registerCardDrugRows.length" class="drug-list-block">
|
||||
<text class="block-label">所选药品</text>
|
||||
<view v-for="row in registerCardDrugRows" :key="row._wxKey" class="info-row drug-spec-row">
|
||||
<image
|
||||
v-if="row.image"
|
||||
class="drug-thumb"
|
||||
:src="row.image"
|
||||
mode="aspectFill"
|
||||
@click.stop="$emit('previewImage', row.image)"
|
||||
/>
|
||||
<view class="drug-name-wrap">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<text v-if="row.specification" class="spec-text">{{ row.specification }}</text>
|
||||
</view>
|
||||
<text class="value">×{{ row.quantity }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer"><button class="action-btn" @click="$emit('viewRegister', parsedContent.id)">查看详情</button></view>
|
||||
@@ -314,17 +323,38 @@ export default {
|
||||
}
|
||||
return message_content;
|
||||
},
|
||||
/** 挂号卡片:多选西药名称串联,无则单药 drug.name */
|
||||
registerCardDrugNamesLine() {
|
||||
if (this.msg.message_type !== 10) return '';
|
||||
/**
|
||||
* 预约挂号卡片药品行:药名 + 规格 + 数量(与医生端一致,去掉底部「各 X 盒」)
|
||||
*/
|
||||
registerCardDrugRows() {
|
||||
if (this.msg.message_type !== 10) return [];
|
||||
const pc = this.parsedContent;
|
||||
if (!pc || typeof pc !== 'object') return '';
|
||||
if (!pc || typeof pc !== 'object') return [];
|
||||
const fallbackQty = pc.number != null && pc.number !== '' ? Number(pc.number) : 1;
|
||||
const arr = pc.selected_western_drugs;
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
return arr.map((d) => d && d.name).filter(Boolean).join('、');
|
||||
return arr.map((d, idx) => {
|
||||
const q = d && d.quantity != null ? Number(d.quantity) : fallbackQty;
|
||||
const id = d && (d.drug_id || d.id);
|
||||
return {
|
||||
_wxKey: 'rd-' + (id != null ? id : idx),
|
||||
name: (d && d.name) || '',
|
||||
specification: (d && (d.specification || d.spec || d.drug_spec)) || '',
|
||||
image: (d && (d.image || d.drug_image)) || '',
|
||||
quantity: q >= 1 ? q : 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (pc.drug && pc.drug.name) return pc.drug.name;
|
||||
return '';
|
||||
if (pc.drug && pc.drug.name) {
|
||||
return [{
|
||||
_wxKey: 'rd-' + (pc.drug.drug_id || pc.drug.id || 0),
|
||||
name: pc.drug.name,
|
||||
specification: pc.drug.specification || pc.drug.spec || '',
|
||||
image: pc.drug.image || pc.drug.drug_image || '',
|
||||
quantity: fallbackQty >= 1 ? fallbackQty : 1,
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
followUpAnsweredLabel() {
|
||||
const pc = this.parsedContent;
|
||||
@@ -336,6 +366,13 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 非己方头像点击:通知父页跳转医生详情;己方头像不响应
|
||||
*/
|
||||
onAvatarClick() {
|
||||
if (this.isMine) return;
|
||||
this.$emit('avatarClick');
|
||||
},
|
||||
// 计算语音条宽度 (Min: 100rpx, Max: 350rpx, Unit: 10rpx/sec)
|
||||
getAudioWidth(duration) {
|
||||
const min = 120;
|
||||
@@ -609,6 +646,40 @@ $success-color: #059669;
|
||||
.price { color: #ef4444; font-size: 28rpx; font-weight: bold; }
|
||||
.small { font-size: 22rpx; color: #999; }
|
||||
}
|
||||
.drug-spec-row {
|
||||
align-items: flex-start;
|
||||
.drug-thumb {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #f3f4f6;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.drug-name-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.spec-text {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.drug-list-block {
|
||||
.block-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
.desc-text { font-size: 26rpx; color: #333; }
|
||||
.text-block {
|
||||
background: #f5f5f5; padding: 16rpx; border-radius: 8rpx;
|
||||
|
||||
Reference in New Issue
Block a user