2025-08-26 17:41:57 +08:00

752 lines
16 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="content">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="患者消息"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#ffffff"
>
<template #right>
<view class="nav-right">
<uni-icons type="search" size="24" color="#8B2316" @click="searchPatients"></uni-icons>
<uni-icons type="staff" size="24" color="#8B2316" @click="managePatients" style="margin-left: 30rpx;"></uni-icons>
</view>
</template>
</uni-nav-bar>
<!-- 消息列表区域 -->
<view class="message-list" v-if="activeTab === 'message'">
<!-- 消息项 -->
<view class="message-item" @click="openMessage">
<view class="message-avatar">
<view class="avatar-placeholder">
<uni-icons type="person" size="32" color="#ffffff"></uni-icons>
</view>
</view>
<view class="message-content">
<view class="message-header">
<text class="patient-name">测试</text>
<text class="message-time">2025-08-11</text>
</view>
<view class="message-preview">
<text class="preview-text">[图片]</text>
</view>
</view>
</view>
<!-- 空状态提示 -->
<view class="empty-state" v-if="messageList.length === 0">
<uni-icons type="chat" size="80" color="#cccccc"></uni-icons>
<text class="empty-text">暂无患者消息</text>
</view>
</view>
<!-- 患者列表区域 -->
<view class="patient-list" v-if="activeTab === 'list'">
<!-- 特殊操作项 -->
<view class="special-actions">
<view class="action-item" @click="addNewPatient">
<view class="action-icon new-patient">
<uni-icons type="person" size="24" color="#ffffff"></uni-icons>
<uni-icons type="plus" size="16" color="#ffffff" style="position: absolute; right: 8rpx; bottom: 8rpx;"></uni-icons>
</view>
<text class="action-text">新的患者</text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
<view class="action-item" @click="managePatientGroups">
<view class="action-icon group-icon">
<view class="grid-icon">
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
</view>
</view>
<text class="action-text">患者分组 (随访5人)</text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="listbox">
<!-- 使用 up-index-list 索引组件数据动态渲染 -->
<up-index-list :index-list="indexList" custom-nav-height="140rpx">
<template v-for="group in patientGroups" :key="group.letter">
<up-index-item >
<up-index-anchor :text="group.letter" />
<view class="group-section">
<view class="patient-item" v-for="item in group.items" :key="item.name" @click="openPatient(item.name)">
<template v-if="item.placeholder">
<view class="patient-avatar-placeholder">
<uni-icons type="person" size="32" color="#ffffff"></uni-icons>
</view>
</template>
<template v-else>
<image class="patient-avatar" :src="item.avatar" mode="aspectFill"></image>
</template>
<view class="patient-info">
<text class="patient-name">{{ item.name }}</text>
<view class="patient-badge" v-if="item.badge">
<text class="badge-text">{{ item.badge }}</text>
</view>
</view>
<view class="patient-status">
<uni-icons type="compose" size="20" color="#8B2316"></uni-icons>
<text class="follow-date">随访于{{ item.date }}</text>
</view>
</view>
</view>
</up-index-item>
</template>
</up-index-list>
</view>
</view>
<view class="plan" v-if="activeTab === 'plan'">
<empty></empty>
<!-- 悬浮添加按钮 -->
<view class="floating-add-btn" @click="showAddMenu">
<uni-icons type="plus" size="24" color="#ffffff"></uni-icons>
<text class="btn-text">添加</text>
</view>
<!-- 添加菜单弹窗 -->
<view class="add-menu-popup" v-if="showAddMenuFlag" @click="hideAddMenu">
<view class="menu-content" @click.stop>
<!-- 添加日程 -->
<view class="menu-item" @click="addSchedule">
<up-image :src="dayImg" width="34rpx" height="34rpx" ></up-image>
<text class="menu-text">添加日程</text>
</view>
<!-- 分割线 -->
<view class="menu-divider"></view>
<!-- 添加随访计划 -->
<view class="menu-item" @click="addFollowUpPlan">
<up-image :src="planImg" width="34rpx" height="34rpx" ></up-image>
<text class="menu-text">添加随访计划</text>
</view>
</view>
</view>
</view>
<!-- 底部标签栏 -->
<view class="tab-bar">
<view class="tab-item active" @click="switchTab('message')">
<text class="tab-text">患者消息</text>
</view>
<view class="tab-item" @click="switchTab('list')">
<text class="tab-text">患者列表</text>
</view>
<view class="tab-item" @click="switchTab('plan')">
<text class="tab-text">随访计划</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, getCurrentInstance } from 'vue';
import { onShow } from "@dcloudio/uni-app";
import dayImg from "@/static/visit_data11.png"
import planImg from "@/static/visitplan.png"
// 消息列表数据
const messageList = ref([
{
id: 1,
patientName: '测试',
messagePreview: '[图片]',
time: '2025-08-11',
avatar: ''
}
]);
// 当前激活的标签
const activeTab = ref('message');
const letters = ref(['A','C','E','L']);
const activeLetter = ref('A');
const scrollIntoViewId = ref('');
let scrollingByClick = false;
// 控制添加菜单弹窗显示
const showAddMenuFlag = ref(false);
// 组件实例(用于 selectorQuery 作用域)
const { proxy } = getCurrentInstance();
// up-index-list 数据源(与当前示例分组一致)
const indexList = ref([]);
// 列表高度(像素):窗口高度 - 顶部导航(rpx转px) - 底部tab(约100rpx->px) - 操作区(约200rpx->px)
const groupsListHeight = ref(0);
const rpxToPx = (rpx) => {
const { screenWidth } = uni.getSystemInfoSync();
return Math.round((screenWidth / 750) * rpx);
};
const computeListHeight = () => {
const { windowHeight } = uni.getSystemInfoSync();
const reserved = rpxToPx(240); // 140rpx(nav) + 100rpx(tab) 近似
groupsListHeight.value = Math.max(0, windowHeight - reserved);
};
// 分组数据(可从接口返回后赋值)
const patientGroups = ref([]);
// 动态生成索引字母
const rebuildIndexList = () => {
indexList.value = patientGroups.value.map(g => g.letter);
};
// 生成模拟数据
const generateMockDate = () => {
const start = new Date(2019, 0, 1).getTime();
const end = new Date().getTime();
const d = new Date(start + Math.random() * (end - start));
const y = d.getFullYear();
const m = `${d.getMonth() + 1}`.padStart(2, '0');
const day = `${d.getDate()}`.padStart(2, '0');
return `${y}-${m}-${day}`;
};
const generateMockGroups = () => {
const lettersPool = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
const avatarSamples = ['/static/avatar-a.png','/static/avatar-l.png','/static/avatar-l2.png'];
const groups = [];
lettersPool.forEach((L) => {
// 随机产生0-3个患者
const count = Math.floor(Math.random() * 3);
if (count === 0) return;
const items = Array.from({length: count}).map((_, i) => {
const usePlaceholder = Math.random() < 0.4;
return {
name: `${L}患者${i+1}`,
avatar: usePlaceholder ? '' : avatarSamples[Math.floor(Math.random()*avatarSamples.length)],
placeholder: usePlaceholder,
badge: Math.random() < 0.2 ? 'GOOD' : '',
date: generateMockDate()
};
});
groups.push({ letter: L, items });
});
return groups;
};
// 加载模拟数据
const loadMockGroups = () => {
uni.showLoading({ title: '加载中' });
setTimeout(() => {
patientGroups.value = generateMockGroups();
rebuildIndexList();
uni.hideLoading();
}, 300);
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 搜索患者
const searchPatients = () => {
uni.showToast({
title: '搜索患者',
icon: 'none'
});
};
// 管理患者
const managePatients = () => {
uni.showToast({
title: '患者管理',
icon: 'none'
});
};
// 打开消息
const openMessage = () => {
uni.showToast({
title: '打开消息',
icon: 'none'
});
};
// 切换标签
const switchTab = (tab) => {
activeTab.value = tab;
switch(tab) {
case 'message':
// 患者消息页面逻辑
break;
case 'list':
// 显示患者列表
break;
case 'plan':
// 跳转到随访计划页面
uni.showToast({
title: '随访计划',
icon: 'none'
});
break;
}
};
// 添加新患者
const addNewPatient = () => {
uni.showToast({
title: '添加新患者',
icon: 'none'
});
};
// 管理患者分组
const managePatientGroups = () => {
uni.showToast({
title: '管理患者分组',
icon: 'none'
});
};
// 打开患者详情
const openPatient = (patientName) => {
uni.showToast({
title: `打开患者: ${patientName}`,
icon: 'none'
});
};
// 添加随访计划
const addFollowUpPlan = () => {
showAddMenuFlag.value = false;
uni.navigateTo({
url: '/pages_app/visit/visit'
});
};
// 添加日程
const addSchedule = () => {
uni.navigateTo({
url: '/pages_app/schedule/schedule'
});
};
// 显示添加菜单
const showAddMenu = () => {
showAddMenuFlag.value = true;
};
// 隐藏添加菜单
const hideAddMenu = () => {
showAddMenuFlag.value = false;
};
// 使用 up-index-list 后不再需要手动滚动联动逻辑
// 页面显示时加载数据
onShow(() => {
loadMessageList();
loadMockGroups();
computeListHeight();
});
// 加载消息列表
const loadMessageList = () => {
// 这里可以调用API获取消息列表
console.log('加载患者消息列表');
};
</script>
<style lang="scss" scoped>
.content {
background-color: #f5f5f5;
min-height: 100vh;
}
// 导航栏右侧按钮样式
.nav-right {
display: flex;
align-items: center;
}
// 消息列表样式
.message-list {
background-color: #ffffff;
margin-top: 20rpx;
.message-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
background-color: #ffffff;
&:last-child {
border-bottom: none;
}
.message-avatar {
margin-right: 20rpx;
.avatar-placeholder {
width: 80rpx;
height: 80rpx;
background-color: #ff6b6b;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.message-content {
flex: 1;
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
}
.patient-name {
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
.message-time {
font-size: 24rpx;
color: #999999;
}
.message-preview {
margin-top: 5rpx;
.preview-text {
font-size: 28rpx;
color: #666666;
}
}
}
}
// 空状态样式
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999999;
}
}
}
// 底部标签栏样式
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
background-color: #f8f8f8;
display: flex;
align-items: center;
border-top: 1rpx solid #e0e0e0;
z-index: 999;
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
height: 100%;
&:nth-child(2) {
border: 2rpx solid #ccc;
border-top: none;
border-bottom: none;
}
.tab-text {
font-size: 28rpx;
color: #999999;
transition: color 0.3s;
}
&.active {
.tab-text {
color: #8B2316;
font-weight: 500;
}
.tab-line {
position: absolute;
bottom: 0;
width: 60rpx;
height: 4rpx;
background-color: #8B2316;
border-radius: 2rpx;
}
}
}
}
// 患者列表样式
.patient-list {
height:calc(100vh - 265rpx);
display: flex;
flex-direction:column;
background-color: #ffffff;
margin-top: 20rpx;
position: relative;
.listbox{
flex:1;
padding-bottom: 100rpx;
}
.groups-scroll {
display: none;
}
.special-actions {
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
.action-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.action-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
position: relative;
&.new-patient {
background-color: #8B2316;
}
&.group-icon {
background-color: #8B2316;
.grid-icon {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 2rpx;
width: 32rpx;
height: 32rpx;
.grid-item {
background-color: #ffffff;
border-radius: 2rpx;
}
}
}
}
.action-text {
flex: 1;
font-size: 32rpx;
color: #333333;
}
}
}
.letter-index {
position: fixed;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
background-color: rgba(255, 255, 255, 0.9);
border-radius: 20rpx;
padding: 10rpx 5rpx;
z-index: 999;
.letter-item {
padding: 8rpx 12rpx;
font-size: 24rpx;
color: #666666;
text-align: center;
border-radius: 10rpx;
&.active {
color: #8B2316;
font-weight: 600;
}
&:active {
background-color: #f0f0f0;
}
}
}
}
.group-section {
.group-header {
padding: 20rpx 30rpx;
background-color: #f8f8f8;
font-size: 36rpx;
color: #666666;
font-weight: bold;
}
.patient-item {
display: flex;
align-items: center;
padding: 30rpx 80rpx 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
background-color: #ffffff;
&:last-child {
border-bottom: none;
}
.patient-avatar,
.patient-avatar-placeholder {
width: 80rpx;
height: 80rpx;
border-radius: 10rpx;
margin-right: 20rpx;
display: flex;
align-items: center;
justify-content: center;
}
.patient-avatar-placeholder {
background-color: #ff6b6b;
}
.patient-info {
flex: 1;
.patient-name {
font-size: 32rpx;
color: #333333;
font-weight: 500;
margin-bottom: 8rpx;
}
.patient-badge {
background-color: #d4a574;
border-radius: 20rpx;
padding: 4rpx 12rpx;
display: inline-block;
.badge-text {
font-size: 20rpx;
color: #ffffff;
font-weight: 500;
}
}
}
.patient-status {
display: flex;
align-items: center;
.follow-date {
font-size: 24rpx;
color: #666666;
margin-left: 10rpx;
}
}
}
}
.floating-add-btn {
position: fixed;
bottom: 140rpx; /* Adjust based on nav-bar height */
right: 30rpx;
background-color: #8B2316;
border-radius: 50%;
width: 100rpx;
height: 100rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 999;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
.btn-text {
font-size: 20rpx;
color: #ffffff;
margin-top: 4rpx;
}
}
.add-menu-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.menu-content {
background-color: #ffffff;
border-radius: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.2);
width: 80%;
max-width: 400rpx;
overflow: hidden;
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx 40rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.menu-text {
font-size: 32rpx;
color: #333333;
margin-left: 20rpx;
}
}
.menu-divider {
height: 1rpx;
background-color: #f0f0f0;
margin: 0 40rpx;
}
</style>