Files
xk-doctor-wx/subPackages/sub_online_reception/pages/list.vue
李琦 6d93314222 1. 优化了一些交互,中药药品编辑交互
2. dev和生产环境统一鉴别
2026-05-30 13:52:44 +08:00

498 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<u-navbar class="navbar" :is-back="true" title="在线接诊" title-color="#1a1a1a" :border-bottom="true"></u-navbar>
<view class="toolbar">
<view class="store" @click="storePickerOpen = true">
<text class="store-name">{{ currentStoreName || '选择门店' }}</text>
<u-icon name="arrow-down-fill" size="20" color="#666"></u-icon>
</view>
<text class="store-hint">在线复诊列表与 PC 一致不按门店筛选</text>
</view>
<view class="tabs-wrap white">
<u-tabs
bar-width="40"
:height="70"
:list="listTabs"
:current="listTabIndex"
:is-scroll="false"
active-color="#6ACDBB"
inactive-color="#8A92A3"
bg-color="#fff"
@change="onListTabChange"
></u-tabs>
</view>
<view class="list-wrap">
<view v-if="patientList.length" class="list">
<view
v-for="(row, idx) in patientList"
:key="row._wxKey"
class="ol-card flex-row flex-ali-center"
:data-idx="idx"
@tap="onPatientCardTap"
>
<view class="ol-avatar-wrap">
<u-image width="80rpx" height="80rpx" shape="circle" :src="rowAvatar(row)">
<u-loading slot="loading"></u-loading>
</u-image>
<u-badge
v-if="Number(row.unread_count) > 0"
:count="Number(row.unread_count)"
:offset="[-4, -4]"
bgColor="#F44336"
:overflow-count="99"
></u-badge>
</view>
<view class="ol-card-body flex-col flex-1">
<!-- 第一行姓名基本信息手机号转诊标签 | 右侧时间 -->
<view class="flex-row flex-jus-sp flex-ali-center">
<view class="flex-row flex-ali-center flex-1 text-hide p-r-20">
<text class="ol-name">{{ displayName(row) }}</text>
<text class="ol-meta">{{ patientSexAgeText(row) }}</text>
<text v-if="displayMobile(row)" class="ol-meta m-l-12">{{ displayMobile(row) }}</text>
<u-tag
v-if="Number(row.is_from_transfer) === 1"
text="转诊"
type="warning"
size="mini"
class="ol-tag"
></u-tag>
</view>
<text class="ol-time">{{ formatRowMsgTime(row) }}</text>
</view>
<!-- 第二行最新消息预览 | 右侧状态 -->
<view class="flex-row flex-jus-sp flex-ali-center m-t-12">
<view class="ol-preview text-hide flex-1 p-r-20">
<text>{{ lastMessageDisplayLine(row) }}</text>
</view>
<view class="ol-status">
<d-text
:text="statusText(row.status)"
:color="rowStatusColor(row.status)"
size="24"
></d-text>
</view>
</view>
<!-- 单号暂时隐藏 -->
<!--
<view v-if="row.order_no" class="flex-row m-t-8">
<text class="ol-sub">单号{{ row.order_no }}</text>
</view>
-->
</view>
</view>
</view>
<view v-else class="empty">
<d-empty text="暂无在线接诊患者"></d-empty>
</view>
</view>
<u-popup v-model="storePickerOpen" mode="bottom" border-radius="24" length="55%" :safe-area-inset-bottom="true">
<view class="popup-inner">
<view class="popup-title">选择诊所</view>
<scroll-view scroll-y class="popup-scroll">
<view
v-for="item in storeList"
:key="item.id"
class="store-row"
@click="changeStore(item.id)"
>
<text :class="{'active-store-text': String(storeId) === String(item.id)}">{{ item.name || item.store_name }}</text>
<u-icon v-if="String(storeId) === String(item.id)" name="checkmark-circle-fill" color="#6ACDBB" size="36"></u-icon>
</view>
</scroll-view>
<view class="popup-footer">
<view class="u-btn qx" @click="storePickerOpen = false">取消</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import { getOnlineConsultationPatientListApi, getMyStoreListApi, switchStoreApi } from '@/api/reception.js';
import { normalizeRoomId } from '@/utils/chat/chatRoomManager.js';
import { formatSessionRelativeTime } from '@/utils/chat/sessionListFormat.js';
export default {
data() {
return {
patientList: [],
listType: 1,
listTabs: [{ name: '当前接诊' }, { name: '历史接诊' }],
storeList: [],
currentStoreName: '',
storePickerOpen: false,
storeId: uni.getStorageSync('store_id') || '',
loading: false
};
},
computed: {
listTabIndex() {
return this.listType === 1 ? 0 : 1;
}
},
onShow() {
this.bootstrap();
},
onLoad() {
this.__doctorImRoomUpdateHandler = this.applyDoctorImRoomUpdate.bind(this);
uni.$on('doctor-im-room-update', this.__doctorImRoomUpdateHandler);
},
onUnload() {
if (this.__doctorImRoomUpdateHandler) {
uni.$off('doctor-im-room-update', this.__doctorImRoomUpdateHandler);
this.__doctorImRoomUpdateHandler = null;
}
},
methods: {
applyDoctorImRoomUpdate(payload) {
const rid = normalizeRoomId(payload && payload.roomId);
if (!rid) return;
const idx = this.patientList.findIndex((r) => normalizeRoomId(r.room_id) === rid);
if (idx === -1) return;
const row = this.patientList[idx];
const copy = { ...row };
if (payload.last_message_preview != null && String(payload.last_message_preview).trim() !== '') {
copy.last_message_preview = payload.last_message_preview;
}
if (payload.last_message_time != null && payload.last_message_time !== '') {
copy.last_message_time = payload.last_message_time;
}
if (payload.incrementUnread) {
if (payload.unreadForRoom != null) {
copy.unread_count = Number(payload.unreadForRoom);
} else {
copy.unread_count = Number(copy.unread_count || 0) + 1;
}
}
const next = [...this.patientList];
next.splice(idx, 1, copy);
this.patientList = this.mapPatientRows(this.sortPatientList(next));
},
unwrap(res) {
if (res == null) return null;
if (res.result !== undefined && res.result !== null) return res.result;
if (res.data && res.data.result !== undefined) return res.data.result;
return res;
},
mapPatientRows(rows) {
return (rows || []).map((row, idx) => {
const room = row.room_id != null && row.room_id !== '' ? String(row.room_id) : 'r0';
const reg = row.register_id != null ? row.register_id : row.id;
const ord = row.order_no != null && row.order_no !== '' ? String(row.order_no) : '';
const ub = Number(row.unread_count || 0);
return {
...row,
_wxKey: `${room}-${reg != null ? reg : 'x'}-${ord}-${ub}`,
};
});
},
lastMessageSortTs(row) {
const t = row.last_message_time || row.updated_at || row.register_time;
if (!t) return 0;
const n = new Date(typeof t === 'number' && t < 1e12 ? t * 1000 : t).getTime();
return Number.isNaN(n) ? 0 : n;
},
sortPatientList(rows) {
if (!Array.isArray(rows)) return [];
return [...rows].sort((a, b) => this.lastMessageSortTs(b) - this.lastMessageSortTs(a));
},
lastMessagePreview(row) {
const p = row.last_message_preview;
if (p != null && String(p).trim() !== '') return String(p);
return '暂无消息';
},
patientSexAgeText(row) {
const up = row && row.user_patient;
if (!up) return '';
const sex = Number(up.sex) % 2 !== 0 ? '男' : '女';
const age = up.age != null && up.age !== '' ? `${up.age}` : '';
const parts = [sex, age].filter(Boolean);
return parts.length ? parts.join(' ') : '';
},
lastMessageDisplayLine(row) {
return this.lastMessagePreview(row);
},
formatRowMsgTime(row) {
return formatSessionRelativeTime(row);
},
rowAvatar(row) {
const up = row && row.user_patient;
if (up && up.avatar) return up.avatar;
const sex = up ? Number(up.sex) : 0;
return require(`@/static/image/${sex % 2 === 0 ? 'nv' : 'nan'}.png`);
},
rowStatusColor(st) {
const s = Number(st);
if (s === 1) return '#F44336';
if (s === 2) return '#00C853';
return '#C4C7CC';
},
onPatientCardTap(e) {
const idx = Number(e?.currentTarget?.dataset?.idx);
if (Number.isNaN(idx) || idx < 0) return;
const row = this.patientList[idx];
if (!row) return;
this.goChat(row);
},
async bootstrap() {
await this.loadStores();
await this.loadPatientList();
},
async loadStores() {
try {
const res = await getMyStoreListApi();
const raw = this.unwrap(res);
this.storeList = Array.isArray(raw) ? raw : raw?.list || raw?.stores || [];
this.storeId = uni.getStorageSync('store_id') || this.storeId;
const cur = this.storeList.find((s) => String(s.id) === String(this.storeId));
this.currentStoreName = cur ? (cur.name || cur.store_name) : '';
} catch (e) {
console.error(e);
}
},
async changeStore(id) {
try {
await switchStoreApi({ store_id: id });
uni.setStorageSync('store_id', id);
this.storeId = id;
const cur = this.storeList.find((s) => String(s.id) === String(id));
this.currentStoreName = cur ? (cur.name || cur.store_name) : '';
this.storePickerOpen = false;
} catch (err) {
uni.showToast({ title: '切换门店失败', icon: 'none' });
}
},
onListTabChange(index) {
const next = index === 0 ? 1 : 2;
if (this.listType === next) return;
this.listType = next;
this.loadPatientList();
},
async loadPatientList() {
this.loading = true;
try {
const res = await getOnlineConsultationPatientListApi({ type: this.listType });
const raw = this.unwrap(res);
const arr = Array.isArray(raw) ? raw : [];
this.patientList = this.mapPatientRows(this.sortPatientList(arr));
} catch (e) {
console.error(e);
this.patientList = [];
uni.showToast({ title: '加载失败', icon: 'none' });
} finally {
this.loading = false;
}
},
displayName(row) {
return row.user_patient?.name || row.user?.nick_name || row.name || '患者';
},
displayMobile(row) {
return row.user_patient?.mobile || row.user?.mobile || row.mobile || '';
},
statusText(st) {
const m = { 1: '待接诊', 2: '接诊中', 3: '已结束' };
return m[st] || '其他';
},
goChat(row) {
if (!row || typeof row !== 'object') {
return;
}
const regId = row.register_id != null ? row.register_id : row.id;
const name = encodeURIComponent(this.displayName(row));
const room = row.room_id || '';
const up = row.user_patient_id || row.user_patient?.id || '';
uni.navigateTo({
url: `/subPackages/sub_online_reception/pages/chat?register_id=${regId}&room_id=${encodeURIComponent(
String(room || '')
)}&patient_name=${name}&user_patient_id=${up}`
});
}
}
};
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #f7f8fa;
}
.white {
background: #fff;
}
.toolbar {
padding: 20rpx 24rpx;
background: #fff;
}
.tabs-wrap {
padding: 0 16rpx;
}
.store {
display: inline-flex;
align-items: center;
margin-bottom: 8rpx;
}
.store-name {
font-size: 28rpx;
color: #333;
margin-right: 8rpx;
}
.store-hint {
display: block;
font-size: 22rpx;
color: #999;
line-height: 1.4;
margin-top: 4rpx;
}
.list-wrap {
padding: 20rpx;
}
.list {
width: 100%;
}
/* 扁平、紧凑的卡片样式 */
.ol-card {
background: #ffffff;
border-radius: 16rpx;
padding: 24rpx 20rpx;
margin-bottom: 16rpx;
position: relative;
}
.ol-avatar-wrap {
width: 80rpx;
position: relative;
flex-shrink: 0;
margin-right: 20rpx;
}
.ol-card-body {
width: auto !important;
height: auto !important;
min-width: 0;
}
.ol-name {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-right: 16rpx;
}
.ol-meta {
font-size: 24rpx;
color: #888;
}
.ol-tag {
margin-left: 12rpx;
transform: scale(0.9);
transform-origin: left center;
}
.ol-time {
flex-shrink: 0;
font-size: 22rpx;
color: #999;
margin-left: 16rpx;
}
.ol-sub {
font-size: 24rpx;
color: #666;
}
.ol-status {
flex-shrink: 0;
margin-left: 16rpx;
}
/* 消息预览只保留纯文本,去除臃肿背景 */
.ol-preview {
font-size: 26rpx;
color: #666;
line-height: 1.4;
}
.text-hide {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.m-t-8 { margin-top: 8rpx; }
.m-t-12 { margin-top: 12rpx; }
.m-l-12 { margin-left: 12rpx; }
.p-r-20 { padding-right: 20rpx; }
.empty {
padding: 120rpx 0;
display: flex;
justify-content: center;
width: 100%;
}
/* 弹窗精简 */
.popup-inner {
padding: 24rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 24rpx;
}
.popup-scroll {
max-height: 50vh;
}
.store-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 26rpx 16rpx;
border-bottom: 1rpx solid #f5f6f8;
font-size: 28rpx;
color: #333;
&:last-child {
border-bottom: none;
}
}
.active-store-text {
color: #6ACDBB;
font-weight: 500;
}
.popup-footer {
padding-top: 24rpx;
}
.qx {
text-align: center;
padding: 20rpx;
color: #666;
background: #f7f8fa;
border-radius: 40rpx;
font-size: 28rpx;
}
</style>