9.2 zjd提交

This commit is contained in:
zoujiandong 2025-09-02 19:03:45 +08:00
parent 1cc611d68d
commit f89d0e6295
16 changed files with 1338 additions and 301 deletions

View File

@ -261,7 +261,27 @@ const api = {
return request('/expertAPI/newsTagList', data, 'post', false);
},
meetingListV2U(data){
return request('/expertAPI/meetingListV2U', data, 'post', false);
},
applyList(data){
return request('/expertAPI/applyList', data, 'post', false);
},
patientListByGBK(data){
return request('/expertAPI/patientListByGBK', data, 'post', false);
},
followUpList(data){
return request('/expertAPI/followUpList', data, 'post', false);
},
relationRecordLately(data){
return request('/expertAPI/relationRecordLately', data, 'post', false);
},
applyListOperate(data){
return request('/expertAPI/applyListOperate', data, 'post', false);
},
groupList(data){
return request('/expertAPI/groupListU', data, 'post', false);
},
}
export default api

View File

@ -12,6 +12,7 @@
"dayjs": "^1.11.18",
"js-base64": "^3.7.8",
"js-md5": "^0.8.3",
"pinyin": "^4.0.0",
"uview-plus": "^3.4.73"
}
}

View File

@ -218,6 +218,47 @@
}
}
},
{
"path": "patientSetting/patientSetting",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "patientRemark/patientRemark",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "groupEdit/groupEdit",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "selectPatient/selectPatient",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "videoDetail/videoDetail",
"style": {

View File

@ -36,8 +36,8 @@
<view class="time-header">2025年08月</view>
<!-- 会议列表 -->
<view class="meeting-list">
<view class="meeting-item" v-for="(item, index) in meetingList" :key="index">
<view class="meeting-list" v-if="meetingList.length > 0">
<view class="meeting-item" v-for="(item, index) in meetingList" :key="item.id || index">
<!-- 左侧日期标识 -->
<view class="date-tag" :style="{backgroundColor: item.tagColor}">
<text class="date-text">{{ item.date }}</text>
@ -47,11 +47,20 @@
<view class="meeting-content">
<view class="meeting-title">{{ item.title }}</view>
<view class="meeting-poster" @click="playVideo(item)">
<image :src="item.poster" class="poster-image"></image>
<image
:src="docUrl+item.liveimg"
class="poster-image"
mode="aspectFill"
@error="onImageError"
@load="onImageLoad"
:data-item-id="item.id"
></image>
<view class="play-btn">
<up-image :src="playImg" width="108rpx" height="108rpx" ></up-image>
</view>
<view class="preview-tag">预告</view>
<view class="preview-tag" v-if="item.status === 'upcoming'">预告</view>
<view class="live-tag" v-else-if="item.status === 'live'">直播中</view>
<view class="replay-tag" v-else-if="item.status === 'replay'">回放</view>
</view>
<view class="meeting-info">
<view class="info-item">
@ -69,6 +78,15 @@
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else-if="!isRefreshing">
<view class="empty-icon">
<uni-icons type="calendar" size="80" color="#ccc"></uni-icons>
</view>
<text class="empty-text">暂无会议数据</text>
<text class="empty-subtext">请稍后再试或调整筛选条件</text>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="showLoadMore">
<view class="load-more-content" v-if="isLoadingMore">
@ -138,10 +156,13 @@
import { ref,nextTick} from 'vue';
import { onShow } from "@dcloudio/uni-app";
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import api from '@/api/api.js';
import select from "@/static/triangle_normal.png"
import selectOn from "@/static/triangle_normal.png"
import playImg from "@/static/bofang.png"
import timeImg from "@/static/play_long.png"
import docUrl from "@/utils/docUrl"
//
const isTimePopupShow = ref(false);
const isLocationPopupShow = ref(false);
@ -214,32 +235,153 @@
]);
//
const meetingList = ref([
{
date: '13',
tagColor: '#FF4444',
title: '"天山论·见"—疑难危重病患维训练营',
poster: '/static/meeting-poster-1.jpg',
time: '2025.08.13',
location: '线上'
},
{
date: '13',
tagColor: '#FFA500',
title: '护肝新声大咖谈',
poster: '/static/meeting-poster-2.jpg',
time: '2025.08.13',
location: '线上'
},
{
date: '15',
tagColor: '#00BCD4',
title: '小罐医生讲HIV和感染专题二:抗菌药物-抗真菌药物特性解读',
poster: '/static/meeting-poster-3.jpg',
time: '2025.08.15',
location: '线上'
const meetingList = ref([]);
//
onShow(() => {
getMeetingList(true);
});
//
const getMeetingList = async (isRefresh = false) => {
if (isRefresh) {
currentPage.value = 1;
hasMoreData.value = true;
}
]);
const params = {
page: currentPage.value,
pageSize: pageSize.value,
month: selectedMonth.value !== 'all' ? selectedMonth.value : '',
province: selectedProvince.value !== 'all' ? selectedProvince.value : ''
};
try {
console.log('获取会议列表参数:', params);
const response = await api.meetingListV2U(params);
console.log('会议列表API响应:', response);
if (response && response.code === 200 && response.data) {
let newItems = [];
let totalCount = 0;
//
if (response.data.list && Array.isArray(response.data.list)) {
newItems = response.data.list;
totalCount = response.data.total || response.data.totalRow || 0;
console.log('使用 res.data.list 结构');
} else if (response.data && Array.isArray(response.data)) {
newItems = response.data;
totalCount = response.total || response.totalRow || newItems.length;
console.log('使用 res.data 结构');
} else if (Array.isArray(response)) {
newItems = response;
totalCount = newItems.length;
console.log('使用 res 数组结构');
}
console.log('解析后的数据:', { newItems, totalCount });
console.log('图片字段映射示例:', newItems.slice(0, 2).map(item => ({
liveimg: item.liveimg || item.live_image || item.live_img,
poster: item.poster || item.cover_image || item.image
})));
if (Array.isArray(newItems) && newItems.length > 0) {
//
const processedItems = newItems.map(item => ({
id: item.id || item.meeting_id || Math.random().toString(36).substr(2, 9),
date: item.date || item.meeting_date || item.start_time || '13',
tagColor: getTagColor(item.status || item.meeting_status || 'upcoming'),
title: item.title || item.meeting_title || item.name || '会议标题',
liveimg: item.liveimg || item.live_image || item.live_img || '',
poster: item.poster || item.cover_image || item.image || '/static/meeting-poster-1.jpg',
time: formatMeetingTime(item.start_time || item.meeting_time || item.time),
location: item.location || item.address || item.venue || '线上',
status: item.status || item.meeting_status || 'upcoming',
description: item.description || item.content || '',
organizer: item.organizer || item.host || '',
speakers: item.speakers || item.experts || []
}));
if (isRefresh) {
meetingList.value = processedItems;
} else {
meetingList.value.push(...processedItems);
}
//
if (meetingList.value.length >= totalCount) {
hasMoreData.value = false;
}
console.log('会议列表更新成功,当前总数:', meetingList.value.length);
console.log('图片字段详情:', processedItems.slice(0, 2).map(item => ({
id: item.id,
liveimg: item.liveimg,
poster: item.poster,
finalImage: item.liveimg || item.poster
})));
} else {
console.log('API返回的数据为空');
if (isRefresh) {
meetingList.value = [];
}
}
} else {
console.log('API响应格式不正确:', response);
if (isRefresh) {
meetingList.value = [];
}
}
} catch (error) {
console.error('获取会议列表失败:', error);
if (isRefresh) {
meetingList.value = [];
}
uni.showToast({
title: '获取会议列表失败',
icon: 'error',
duration: 2000
});
}
};
//
const getTagColor = (status) => {
const colorMap = {
'upcoming': '#FF4444', //
'live': '#00BCD4', //
'replay': '#9C27B0', //
'finished': '#4CAF50', //
'cancelled': '#FF9800' //
};
return colorMap[status] || '#FF4444';
};
//
const formatMeetingTime = (timeStr) => {
if (!timeStr) return '2025.08.13';
try {
//
if (typeof timeStr === 'number') {
const date = new Date(timeStr);
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
}
//
const date = new Date(timeStr);
if (!isNaN(date.getTime())) {
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
}
//
return timeStr;
} catch (error) {
console.error('时间格式化失败:', error);
return timeStr;
}
};
//
const showTimePopup = () => {
@ -255,7 +397,8 @@
const selectMonth = (month) => {
selectedMonth.value = month.value;
console.log('选择月份:', month.label);
//
//
getMeetingList(true);
hideTimePopup();
};
@ -273,7 +416,8 @@
const selectProvince = (province) => {
selectedProvince.value = province.code;
console.log('选择省份:', province.name);
//
//
getMeetingList(true);
hideLocationPopup();
};
@ -283,102 +427,64 @@
//
};
//
const onImageError = (e) => {
const itemId = e.currentTarget.dataset.itemId;
console.log('图片加载失败项目ID:', itemId);
//
const itemIndex = meetingList.value.findIndex(item => item.id === itemId);
if (itemIndex !== -1) {
// liveimg使poster
if (meetingList.value[itemIndex].liveimg && meetingList.value[itemIndex].liveimg !== meetingList.value[itemIndex].poster) {
console.log('liveimg加载失败切换到poster');
meetingList.value[itemIndex].liveimg = meetingList.value[itemIndex].poster;
} else {
// poster使
console.log('设置默认图片');
meetingList.value[itemIndex].poster = '/static/meeting-poster-1.jpg';
}
}
};
//
const onImageLoad = (e) => {
const itemId = e.currentTarget.dataset.itemId;
console.log('图片加载成功项目ID:', itemId, '图片地址:', e.currentTarget.src);
};
//
const onRefresh = () => {
isRefreshing.value = true;
currentPage.value = 1;
hasMoreData.value = true;
//
setTimeout(() => {
//
meetingList.value = [
{
date: '13',
tagColor: '#FF4444',
title: '"天山论·见"—疑难危重病患维训练营',
poster: '/static/meeting-poster-1.jpg',
time: '2025.08.13',
location: '线上'
},
{
date: '13',
tagColor: '#FFA500',
title: '护肝新声大咖谈',
poster: '/static/meeting-poster-2.jpg',
time: '2025.08.13',
location: '线上'
},
{
date: '15',
tagColor: '#00BCD4',
title: '小罐医生讲HIV和感染专题二:抗菌药物-抗真菌药物特性解读',
poster: '/static/meeting-poster-3.jpg',
time: '2025.08.15',
location: '线上'
}
];
isRefreshing.value = false;
uni.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
});
}, 1500);
//
getMeetingList(true).finally(() => {
//
setTimeout(() => {
isRefreshing.value = false;
uni.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
});
}, 500);
});
};
//
const onLoadMore = () => {
console.log('上拉加载');
console.log('上拉加载更多');
if (isLoadingMore.value || !hasMoreData.value) return;
isLoadingMore.value = true;
currentPage.value++;
//
setTimeout(() => {
//
const newMeetings = [
{
date: '16',
tagColor: '#9C27B0',
title: '肝胆外科微创技术研讨会',
poster: '/static/meeting-poster-4.jpg',
time: '2025.08.16',
location: '北京'
},
{
date: '17',
tagColor: '#FF9800',
title: '胆囊疾病诊疗新进展',
poster: '/static/meeting-poster-5.jpg',
time: '2025.08.17',
location: '上海'
},
{
date: '18',
tagColor: '#4CAF50',
title: '肝移植术后管理专题讲座',
poster: '/static/meeting-poster-6.jpg',
time: '2025.08.18',
location: '广州'
}
];
meetingList.value.push(...newMeetings);
// 3
if (currentPage.value >= 3) {
hasMoreData.value = false;
}
// API
getMeetingList(false).finally(() => {
isLoadingMore.value = false;
// scroll-view
nextTick(() => {
console.log('数据加载完成,列表长度:', meetingList.value.length);
});
}, 1000);
});
};
</script>
@ -701,7 +807,29 @@ $shadow: 0 2px 8px rgba(0,0,0,0.1);
position: absolute;
top: 16rpx;
right: 16rpx;
border: 4rpx solid #fff;
background-color: #FF4444;
color: $white;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.live-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
background-color: #00BCD4;
color: $white;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.replay-tag {
position: absolute;
top: 16rpx;
right: 16rpx;
background-color: #9C27B0;
color: $white;
font-size: 24rpx;
padding: 4rpx 16rpx;
@ -730,6 +858,31 @@ $shadow: 0 2px 8px rgba(0,0,0,0.1);
}
}
//
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 30rpx;
.empty-icon {
margin-bottom: 30rpx;
}
.empty-text {
font-size: 32rpx;
color: $gray;
margin-bottom: 16rpx;
}
.empty-subtext {
font-size: 26rpx;
color: $gray-text;
text-align: center;
}
}
//
.load-more {
padding: 30rpx;

View File

@ -0,0 +1,196 @@
<template>
<view class="group-edit-page">
<uni-nav-bar
left-icon="left"
title="编辑分组"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eee"
>
<template #right>
<text class="save-text" @click="saveGroup">保存</text>
</template>
</uni-nav-bar>
<!-- 分组名称 -->
<view class="section-header">分组名称</view>
<view class="name-row">
<input class="name-input" v-model.trim="groupName" placeholder="请输入分组名称" maxlength="20" />
<view class="icon-btn" v-if="groupName" @click="clearName">
<up-image :src="delImg" width="48rpx" height="48rpx" />
</view>
</view>
<!-- 分组成员 -->
<view class="section-header">分组成员</view>
<view class="add-member" @click="addMember">
<view class="add-circle">
<up-icon name="plus" size="34" color="#bfbfbf" />
</view>
<text class="add-text">添加组患者</text>
</view>
<view class="member-list">
<view class="member-item" v-for="(m, idx) in members" :key="m.uuid || idx">
<image class="avatar" :src="docUrl + (m.photo || '')" mode="aspectFill" />
<text class="member-name">{{ m.realName || '未知' }}</text>
<view class="remove-btn" @click="removeMember(idx)">
<view class="remove-circle">
<up-icon name="minus" color="#fff" size="28rpx" bold></up-icon>
</view>
</view>
</view>
</view>
<!-- 底部删除按钮 -->
<view class="bottom-danger">
<button class="danger-btn" @click="deleteGroup">删除分组</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import navTo from '@/utils/navTo.js'
import docUrl from '@/utils/docUrl.js'
import delImg from "@/static/iv_delete.png"
const groupUuid = ref('')
const groupName = ref('')
const members = ref([])
onLoad((query) => {
groupUuid.value = query?.uuid || ''
// TODO: uuid
//
groupName.value = '看看'
members.value = [
{ uuid: 'u1', realName: '测试', photo: '' }
]
})
onShow(() => {
//
try {
const cached = uni.getStorageSync('patientsSelectedPayload')
if (cached && Array.isArray(cached.list) && cached.list.length) {
mergeSelected(cached.list)
uni.removeStorageSync('patientsSelectedPayload')
}
} catch (e) {}
})
const goBack = () => uni.navigateBack()
const clearName = () => { groupName.value = '' }
const saveGroup = () => {
// TODO: : { uuid: groupUuid, name: groupName, members }
uni.showToast({ title: '已保存', icon: 'success' })
}
const addMember = () => {
//
uni.navigateTo({
url: '/pages_app/selectPatient/selectPatient',
events: {
onPatientsSelected: ({ ids, list }) => {
if (Array.isArray(list)) mergeSelected(list)
}
}
})
}
const removeMember = (idx) => {
members.value.splice(idx, 1)
}
const deleteGroup = () => {
uni.showModal({
title: '删除分组',
content: '确定要删除该分组吗?',
success: (res) => {
if (res.confirm) {
// TODO:
uni.showToast({ title: '已删除', icon: 'success' })
setTimeout(() => goBack(), 700)
}
}
})
}
// uuid
const mergeSelected = (selectedList) => {
const existIds = new Set(members.value.map(m => m.uuid))
selectedList.forEach(s => {
if (!existIds.has(s.uuid)) {
existIds.add(s.uuid)
members.value.push({ uuid: s.uuid, realName: s.realName, photo: s.photo || '' })
}
})
}
</script>
<style lang="scss" scoped>
.group-edit-page{
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 160rpx;
}
.save-text{ color:#8B2316; font-size: 30rpx; }
.section-header{
background:#d9d9d9;
color:#333;
padding: 22rpx 30rpx;
font-size: 30rpx;
}
.name-row{
background:#fff;
display:flex;
align-items:center;
justify-content:space-between;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #eee;
.name-input{
flex:1;
font-size: 32rpx;
color:#333;
}
.icon-btn{ padding-left: 20rpx; }
}
.add-member{
background:#fff;
display:flex;
align-items:center;
gap:20rpx;
padding: 26rpx 30rpx;
border-bottom: 1rpx solid #eee;
.add-circle{
width: 96rpx; height: 96rpx; border-radius: 50%;
border: 4rpx solid #e5e5e5;
display:flex; align-items:center; justify-content:center;
background:#fff;
}
.add-text{ font-size: 32rpx; color:#666; }
}
.member-list{
background:#fff;
.member-item{
display:flex; align-items:center; justify-content:space-between;
padding: 26rpx 30rpx; border-bottom: 1rpx solid #eee;
.avatar{ width: 100rpx; height: 100rpx; border-radius: 16rpx; background:#ffe; }
.member-name{ flex:1; margin-left: 20rpx; font-size: 32rpx; color:#333; }
.remove-btn{ padding-left: 20rpx; }
.remove-circle{ width: 48rpx; height:48rpx; background:#8B2316; border-radius:50%; display:flex; align-items:center; justify-content:center; }
}
}
.bottom-danger{
position: fixed; left:30rpx; right:30rpx; bottom: 30rpx;
background:#fff; border-top: 1rpx solid #eee;
.danger-btn{ width:100%; height: 96rpx; background:#8B2316; color:#fff; border:none; border-radius: 12rpx; font-size: 32rpx;display: flex; align-items: center; justify-content: center; }
}
</style>

View File

@ -24,20 +24,20 @@
</view>
<!-- 随访申请 -->
<view class="follow-up-section">
<view class="follow-up-section" v-if="applyList.length > 0">
<view class="section-title">随访申请</view>
<view class="pending-request" v-if="pendingRequest">
<view class="pending-request" v-for="(item, index) in applyList" :key="index">
<view class="request-item">
<view class="avatar">
<view class="avatar-icon"></view>
<up-image :src="docUrl+item.photo" radius="10rpx" width="80rpx" height="80rpx" ></up-image>
</view>
<view class="request-content">
<view class="request-time">2025-08-18 15:55:03</view>
<view class="request-text">我是陈新华,在线上和您沟通过,请您同意我作为您的随访患者</view>
<view class="request-time">{{ item.createDate }}</view>
<view class="request-text">{{ item.content }}</view>
<view class="action-buttons">
<button class="reject-btn" @click="rejectRequest">拒绝</button>
<button class="agree-btn" @click="agreeRequest">同意</button>
<button class="reject-btn" @click="applyListOperate(item.uuid,3)">拒绝</button>
<button class="agree-btn" @click="applyListOperate(item.uuid,2)">同意</button>
</view>
</view>
</view>
@ -45,22 +45,22 @@
</view>
<!-- 申请记录 -->
<view class="history-section">
<view class="history-section" v-if="historyList.length > 0">
<view class="section-title">申请记录(近一月)</view>
<view class="history-list">
<view class="history-item" v-for="(item, index) in historyList" :key="index">
<view class="avatar">
<view class="avatar-icon"></view>
<up-image v-if="docUrl+item.patient_photo" :src="docUrl + item.patient_photo" radius="10rpx" width="80rpx" height="80rpx"></up-image>
</view>
<view class="history-content">
<view class="history-time">{{ item.time }}</view>
<view class="nickname">昵称: {{ item.nickname }}</view>
<view class="history-text">{{ item.message }}</view>
<view class="history-time">{{ formatDate(item.createDate) }}</view>
<view class="nickname">{{ item.nickname || item.patient_name }}</view>
<view class="history-text">{{ item.content}}</view>
<view class="status-info">
<up-image :src="goImg" width="30rpx" height="30rpx" ></up-image>
<text class="status-text">已同意</text>
<up-image :src="goImg" width="30rpx" height="30rpx" v-if="item.status==2"></up-image>
<text class="status-text">{{ getStatusText(item.status) }}</text>
</view>
</view>
</view>
</view>
@ -75,36 +75,19 @@
<script setup>
import { ref } from 'vue';
import { onLoad,onShow} from '@dcloudio/uni-app';
import goImg from "@/static/go_big.png"
import api from '@/api/api.js'
import docUrl from "@/utils/docUrl"
import navTo from "@/utils/navTo.js"
//
const pendingRequest = ref({
name: '陈新华',
message: '我是陈新华,在线上和您沟通过,请您同意我作为您的随访患者',
time: '2025-08-18 15:55:03'
});
const historyList = ref([
{
nickname: '韩夫臣',
message: '我是韩夫臣,在线上和您沟通过,请您...',
time: '2025-08-19 22:41:35'
},
{
nickname: '鲁保山',
message: '我是鲁保山,在线上和您沟通过,请您...',
time: '2025-08-19 22:41:27'
},
{
nickname: '蒋宁宁',
message: '我是蒋宁宁,在线上和您沟通过,请您...',
time: '2025-08-19 11:25:46'
},
{
nickname: '蒋宁',
message: '我是蒋宁,在线上和您沟通过,请您同...',
time: '2025-08-19 11:25:39'
}
]);
const applyList = ref([]);
const historyList = ref([]);
//
const goBack = () => {
@ -142,11 +125,90 @@ const agreeRequest = () => {
}
});
};
const getRelationRecordLately = async () => {
const res = await api.relationRecordLately({
page:1,
pageSize:100
});
console.log('随访记录API响应:', res);
if(res.code === 200){
historyList.value = res.data.list;
}
};
const getApplyList = async () => { //
try {
let userInfo=uni.getStorageSync('userInfo')
const res = await api.applyList();
console.log('申请列表API响应:', res);
if (res && res.code === 200) {
applyList.value = res.data;
} else {
console.log('申请列表API响应异常:', res);
uni.showToast({
title: res.message || '获取申请列表失败',
icon: 'error',
duration: 2000
});
}
} catch (error) {
console.error('获取申请列表失败:', error);
uni.showToast({
title: '网络请求失败',
icon: 'error',
duration: 2000
});
}
};
const applyListOperate = async (uuid,status) => {
let data = {
uuid: uuid,
status: status
}
const res = await api.applyListOperate(data);
if(res.code === 200){
uni.showToast({
title: '操作成功',
icon: 'none',
duration: 1500
});
getApplyList();
getRelationRecordLately();
}
};
onShow(() => {
getApplyList();
getRelationRecordLately();
});
//
//
const formatDate = (dateString) => {
if (!dateString) return '';
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
//
const getStatusText = (status) => {
switch (status) {
case 1: return '待审核';
case 2: return '已同意';
case 3: return '已拒绝';
}
};
const addPatient = () => {
uni.showToast({
title: '跳转到添加患者页面',
icon: 'none'
navTo({
url:'/pages_app/myCode/myCode'
});
//
};
@ -378,7 +440,7 @@ const addPatient = () => {
}
.history-list {
padding-bottom: 100rpx;
margin-bottom: 100rpx;
.history-item {
display: flex;
gap: 20rpx;

View File

@ -12,7 +12,7 @@
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-right" @click="editPatient">
<view class="nav-right" @click.stop="editPatient">
<uni-icons type="compose" size="22" color="#8B2316"></uni-icons>
</view>
</template>

View File

@ -17,14 +17,14 @@
<!-- 筛选排序栏 -->
<view class="filter-sort-bar">
<view class="sort-section" @click="toggleGroupSort">
<text class="sort-label">分组排序</text>
<text class="sort-label">{{ groupSortTitle }}</text>
<view class="imgbox">
<up-image :src="upImg" width="26rpx" height="26rpx" ></up-image>
</view>
</view>
<view class="divider"></view>
<view class="current-sort" @click="toggleInnerSort">
<text class="sort-text">按首字母</text>
<text class="sort-text">{{ innerSortTitle }}</text>
<view class="imgbox">
<up-image :src="upImg" width="26rpx" height="26rpx" ></up-image>
</view>
@ -33,12 +33,12 @@
<!-- 分组排序弹窗 -->
<view v-if="showGroupSort" class="popup-panel">
<view class="popup-item" :class="{ active: selectedGroupSort==='letter' }" @click.stop="chooseGroupSort('letter')">
<view class="popup-item" :class="{ active: group_sort==0 }" @click.stop="chooseGroupSort('letter')">
<text class="item-text">按首字母</text>
<uni-icons v-if="selectedGroupSort==='letter'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
<view class="popup-divider"></view>
<view class="popup-item" :class="{ active: selectedGroupSort==='count' }" @click.stop="chooseGroupSort('count')">
<view class="popup-item" :class="{ active:group_sort==1}" @click.stop="chooseGroupSort('count')">
<text class="item-text">分组人数</text>
<uni-icons v-if="selectedGroupSort==='count'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
@ -47,13 +47,13 @@
<!-- 组内排序弹窗 -->
<view v-if="showInnerSort" class="popup-panel">
<view class="popup-item" :class="{ active: selectedInnerSort==='letter' }" @click.stop="chooseInnerSort('letter')">
<view class="popup-item" :class="{ active:list_sort==0 }" @click.stop="chooseInnerSort('letter')">
<text class="item-text">按首字母</text>
<uni-icons v-if="selectedInnerSort==='letter'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
<view class="popup-divider"></view>
<view class="popup-item" :class="{ active: selectedInnerSort==='count' }" @click.stop="chooseInnerSort('count')">
<text class="item-text">分组人数</text>
<view class="popup-item" :class="{ active:list_sort==1 }" @click.stop="chooseInnerSort('count')">
<text class="item-text">随访时间</text>
<uni-icons v-if="selectedInnerSort==='count'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
</view>
@ -61,22 +61,29 @@
<!-- 患者列表 -->
<scroll-view class="patient-list-section" scroll-y="true" :style="{ height: scrollViewHeight }">
<view class="groupcell">
<view class="section-title">
<view class="imgbox">
<up-image :src="groupRightImg" width="19rpx" height="32rpx" ></up-image>
<!-- 分组循环渲染 -->
<view class="groupcell" v-for="(group, gi) in groups" :key="group.uuid || gi">
<view class="section-title" @click="toggleGroup(gi)">
<view class="left">
<view class="imgbox">
<up-image v-if="!openGroups[gi]" :src="groupRightImg" width="19rpx" height="32rpx" ></up-image>
<up-image v-else :src="groupDownImg" width="32rpx" height="19rpx" ></up-image>
</view>
<view class="title">{{ group.name || '未命名分组' }} | {{ group.patientNum || (group.patientList ? group.patientList.length : 0) }}</view>
</view>
<view class="right-edit" @click.stop="editGroup(group)">
<text class="edit-text">编辑</text>
</view>
<view class="title">待分组患者 | 5</view>
</view>
<view class="patient-list" >
<view class="patient-item" v-for="(patient, index) in patientList" :key="index">
<view class="patient-list" v-if="openGroups[gi]">
<view class="patient-item" v-for="(patient, index) in (group.patientList || [])" :key="patient.uuid || index">
<view class="patient-avatar">
<up-image :src="patient.avatar" width="80rpx" height="80rpx" mode="aspectFill"></up-image>
<up-image :src="docUrl + patient.photo" width="80rpx" height="80rpx" mode="aspectFill"></up-image>
</view>
<view class="patient-info">
<view class="patient-name">{{ patient.name }}</view>
<view class="follow-up-time">随访于{{ patient.lastFollowUp }}</view>
<view class="patient-name">{{ patient.realName || patient.nickname || '-' }}</view>
<view class="follow-up-time">随访于{{ formatYMD(patient.join_date) }}</view>
</view>
</view>
</view>
@ -87,53 +94,59 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { onShow } from "@dcloudio/uni-app";
import upImg from "@/static/triangle_green_theme.png"
import downImg from "@/static/triangle_normal.png"
import groupRightImg from "@/static/groupright_big.png"
import groupDownImg from "@/static/groupup_big.png"
//
const patientList = ref([
{
name: 'aa',
lastFollowUp: '2020-12-02',
avatar: '/static/avatar1.png' //
},
{
name: '测试',
lastFollowUp: '2023-10-08',
avatar: '/static/avatar2.png' //
},
{
name: '刘三多',
lastFollowUp: '2021-12-16',
avatar: '/static/avatar3.png' // 绿
},
{
name: '路测试',
lastFollowUp: '2019-10-28',
avatar: '/static/avatar4.png' //
},
{
name: '哦哦哦',
lastFollowUp: '2023-10-08',
avatar: '/static/avatar5.png' //
}
]);
import api from '@/api/api.js';
import docUrl from '@/utils/docUrl'
import dayjs from 'dayjs'
const list_sort = ref(0);
const group_sort = ref(0);
//
const scrollViewHeight = computed(() => {
//
// (140rpx) + (80rpx) + (80rpx) = 300rpx
// px
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const navHeight = 140 / 2; // rpxpx
const filterHeight = 80 / 2;
const titleHeight = 80 / 2;
const availableHeight = windowHeight - navHeight - filterHeight - titleHeight;
return `${availableHeight}px`;
//
const groups = ref([]);
const openGroups = ref({});
const formatYMD = (val) => {
if (!val) return '';
const d = dayjs(val);
return d.isValid() ? d.format('YYYY-MM-DD') : '';
};
//
const groupSortTitle = computed(() => group_sort.value === 0 ? '按首字母' : '分组人数');
const innerSortTitle = computed(() => list_sort.value === 0 ? '按首字母' : '随访时间');
onShow(() => {
fetchGroupList();
});
const fetchGroupList = async () => {
const res = await api.groupList({
list_sort:list_sort.value,
page:1,
pageSize:10,
group_sort:group_sort.value
});
if(res.code === 200){
groups.value = Array.isArray(res.data) ? res.data : [];
//
openGroups.value = {};
groups.value.forEach((_, idx) => openGroups.value[idx] = false);
}
};
const toggleGroup = (idx) => {
openGroups.value[idx] = !openGroups.value[idx];
};
const editGroup = (group) => {
// TODO:
uni.showToast({ title: `编辑:${group.name || '未命名分组'}`, icon: 'none' });
};
//
const showGroupSort = ref(false);
const selectedGroupSort = ref('letter'); // letter | count
@ -142,18 +155,23 @@ const selectedGroupSort = ref('letter'); // letter | count
const showInnerSort = ref(false);
const selectedInnerSort = ref('letter');
// /
const showPending = ref(false);
const toggleGroupSort = () => {
showGroupSort.value = !showGroupSort.value;
if (showGroupSort.value) showInnerSort.value = false;
};
const closeGroupSort = () => {
showGroupSort.value = false;
};
const chooseGroupSort = (type) => {
selectedGroupSort.value = type;
closeGroupSort();
selectedGroupSort.value = type;
// =0=1
group_sort.value = type === 'letter' ? 0 : 1;
closeGroupSort();
fetchGroupList();
};
const toggleInnerSort = () => {
@ -166,8 +184,11 @@ const closeInnerSort = () => {
};
const chooseInnerSort = (type) => {
selectedInnerSort.value = type;
closeInnerSort();
selectedInnerSort.value = type;
// =0访=1
list_sort.value = type === 'letter' ? 0 : 1;
closeInnerSort();
fetchGroupList();
};
//
@ -182,6 +203,7 @@ const createNew = () => {
});
//
};
</script>
<style lang="scss" scoped>
@ -356,6 +378,7 @@ const createNew = () => {
z-index: 9;
border-bottom: 1rpx solid #f0f0f0;
.imgbox{
width: 32rpx;
margin-top: -10rpx;
}
.sort-section {
@ -418,14 +441,28 @@ const createNew = () => {
.section-title {
padding: 30rpx 30rpx 20rpx;
font-size: 32rpx;
width: 100%;
box-sizing: border-box;
font-weight: normal;
display: flex;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
align-items: center;
justify-content: space-between;
.left{
display:flex;
align-items:center;
}
.imgbox{
margin-top: 4rpx;
margin-right: 8rpx;
}
.right-edit{
display:flex;
align-items:center;
gap: 8rpx;
.edit-text{ font-size: 28rpx; color:#8B2316; }
}
}
.patient-list {

View File

@ -9,7 +9,7 @@
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#ffffff"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
@ -20,31 +20,43 @@
</uni-nav-bar>
<!-- 消息列表区域 -->
<view class="message-list" v-if="activeTab === 'message'">
<scroll-view
class="message-list"
v-if="activeTab === 'message'"
scroll-y="true"
refresher-enabled="true"
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh"
>
<!-- 消息项 -->
<view class="message-item" @click="openMessage">
<view class="message-item" v-for="(item, index) in messageList" :key="item.id || index" @click="openMessage(item)">
<view class="message-avatar">
<view class="avatar-placeholder">
<view class="avatar-placeholder" v-if="!item.avatar">
<uni-icons type="person" size="32" color="#ffffff"></uni-icons>
</view>
<image v-else :src="item.avatar" class="patient-avatar" mode="aspectFill"></image>
</view>
<view class="message-content">
<view class="message-header">
<text class="patient-name">测试</text>
<text class="message-time">2025-08-11</text>
<text class="patient-name">{{ item.patientName }}</text>
<text class="message-time">{{ item.time }}</text>
</view>
<view class="message-preview">
<text class="preview-text">[图片]</text>
<text class="preview-text">{{ item.messagePreview }}</text>
</view>
</view>
</view>
<!-- 空状态提示 -->
<view class="empty-state" v-if="messageList.length === 0">
<view class="empty-state" v-if="messageList.length === 0 && !isRefreshing">
<uni-icons type="chat" size="80" color="#cccccc"></uni-icons>
<text class="empty-text">暂无患者消息</text>
<text class="empty-subtext">下拉刷新获取最新申请</text>
<view class="debug-actions">
<button class="debug-btn" @click="getApplyList">测试API调用</button>
</view>
</view>
</view>
</scroll-view>
<!-- 患者列表区域 -->
<view class="patient-list" v-if="activeTab === 'list'">
@ -55,7 +67,7 @@
<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>
<text class="action-text">新的患者<text class="new-patient-count" v-if="applyList.length > 0">(待审核{{ applyList.length }})</text></text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
@ -68,7 +80,7 @@
<view class="grid-item"></view>
</view>
</view>
<text class="action-text">患者分组 (随访5人)</text>
<text class="action-text">患者分组 <text class="new-patient-count" v-if="patientList.length > 0">(随访{{ patientList.length }})</text></text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
@ -81,24 +93,24 @@
<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)">
<view class="patient-item" v-for="item in group.items" :key="item.uuid || item.id" >
<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>
<image class="patient-avatar" :src="docUrl+item.photo" mode="aspectFill"></image>
</template>
<view class="patient-info">
<text class="patient-name">{{ item.name }}</text>
<view class="patient-info" @click="goPatientDetail(item.uuid)">
<text class="patient-name">{{ item.realName }}</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>
<uni-icons type="compose" size="20" color="#8B2316" @click.stop="editPatient(item.uuid)"></uni-icons>
<text class="follow-date">随访于{{ formatYMD(item.join_date) }}</text>
</view>
</view>
</view>
@ -108,7 +120,15 @@
</view>
</view>
<view class="plan" v-if="activeTab === 'plan'">
<empty></empty>
<view class="apply-list" v-if="applyList.length > 0">
<view class="apply-item" v-for="item in applyList" :key="item.id">
<view class="apply-content">
<text class="apply-text">{{ item.messagePreview }}</text>
</view>
</view>
</view>
<empty v-else></empty>
<!-- 悬浮添加按钮 -->
<view class="floating-add-btn" @click="showAddMenu">
@ -159,8 +179,30 @@
import { onShow } from "@dcloudio/uni-app";
import dayImg from "@/static/visit_data11.png"
import planImg from "@/static/visitplan.png"
//
import api from '@/api/api.js';
import navTo from '@/utils/navTo.js';
import docUrl from '@/utils/docUrl.js';
const patientList = ref([]);
import pinyin from 'pinyin';
import dayjs from 'dayjs'
const goPatientDetail = (uuid) => {
navTo({
url: `/pages_app/patientDetail/patientDetail?uuid=${uuid}`
})
}
const editPatient = (uuid) => {
console.log(uuid)
navTo({
url: `/pages_app/patientSetting/patientSetting?uuid=${uuid}`
})
}
//
const formatYMD = (input) => {
if (!input) return '';
const d = dayjs(input);
return d.isValid() ? d.format('YYYY-MM-DD') : '';
}
//
const messageList = ref([
{
id: 1,
@ -184,8 +226,9 @@
// selectorQuery
const { proxy } = getCurrentInstance();
// up-index-list
const indexList = ref([]);
// up-index-list 26
// A-Z
const indexList = ref('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''));
// - (rpxpx) - tab(100rpx->px) - (200rpx->px)
const groupsListHeight = ref(0);
const rpxToPx = (rpx) => {
@ -201,59 +244,117 @@
//
const patientGroups = ref([]);
//
// A-Z
const rebuildIndexList = () => {
// A-Z
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 });
//
const generateMockDate = () => '';
const getFirstLetter = (chineseName) => {
//
const firstChar = chineseName.charAt(0); //
const pinyinArray = pinyin(firstChar, { style: pinyin.STYLE_NORMAL }); //
return pinyinArray[0][0].charAt(0); //
}
// patientList
const buildGroupsFromPatients = () => {
const map = new Map();
patientList.value.forEach((p) => {
const name = p.realName;
const first = getFirstLetter(name).toUpperCase();
const letter = /^[A-Z]$/.test(first) ? first : '#';
if (!map.has(letter)) map.set(letter, []);
map.get(letter).push(p);
});
return groups;
};
//
const loadMockGroups = () => {
uni.showLoading({ title: '加载中' });
setTimeout(() => {
patientGroups.value = generateMockGroups();
rebuildIndexList();
uni.hideLoading();
}, 300);
const letters = Array.from(map.keys()).sort((a,b) => a.localeCompare(b));
patientGroups.value = letters.map(l => ({ letter: l, items: map.get(l) }));
rebuildIndexList();
};
//
const goBack = () => {
uni.navigateBack();
};
//
const applyList = ref([]);
//
const isRefreshing = ref(false);
const getApplyList = async () => { //
try {
let userInfo=uni.getStorageSync('userInfo')
const res = await api.applyList();
if (res && res.code === 200) {
applyList.value = res.data;
} else {
uni.showToast({
title: res.message || '获取申请列表失败',
icon: 'error',
duration: 2000
});
}
} catch (error) {
console.error('获取申请列表失败:', error);
uni.showToast({
title: '网络请求失败',
icon: 'error',
duration: 2000
});
}
};
const patientListByGBK = async () => {
const res = await api.patientListByGBK();
if(res.code == 1){
patientList.value = res.data;
buildGroupsFromPatients()
}
};
const page=ref(1)
const followUpList = async () => {
let userInfo=uni.getStorageSync('userInfo')
const res = await api.followUpList({
page:page.value,
pageSize:10
});
if(res.code === '1'){
followUpList.value = res.data;
}
};
//
const onRefresh = async () => {
isRefreshing.value = true;
try {
await getApplyList();
uni.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
});
} catch (error) {
console.error('刷新失败:', error);
uni.showToast({
title: '刷新失败',
icon: 'error',
duration: 1500
});
} finally {
isRefreshing.value = false;
}
};
//
const searchPatients = () => {
@ -272,11 +373,15 @@
};
//
const openMessage = () => {
const openMessage = (item) => {
uni.showToast({
title: '打开消息',
title: `打开患者 ${item?.patientName || '未知'} 的消息`,
icon: 'none'
});
//
// uni.navigateTo({
// url: `/pages_app/messageDetail/messageDetail?id=${item?.id}`
// });
};
//
@ -285,7 +390,8 @@
switch(tab) {
case 'message':
//
// -
getApplyList();
break;
case 'list':
//
@ -302,18 +408,16 @@
//
const addNewPatient = () => {
uni.showToast({
title: '添加新患者',
icon: 'none'
});
navTo({
url: '/pages_app/myPatient/myPatient'
})
};
//
const managePatientGroups = () => {
uni.showToast({
title: '管理患者分组',
icon: 'none'
});
navTo({
url: '/pages_app/patientGroup/patientGroup'
})
};
//
@ -354,14 +458,15 @@
//
onShow(() => {
loadMessageList();
loadMockGroups();
computeListHeight();
getApplyList();
patientListByGBK();
followUpList();
});
//
const loadMessageList = () => {
// API
console.log('加载患者消息列表');
};
</script>
@ -446,13 +551,32 @@
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
padding: 100rpx 30rpx;
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999999;
}
.empty-subtext {
font-size: 26rpx;
color: #999999;
margin-top: 16rpx;
}
.debug-actions {
margin-top: 30rpx;
.debug-btn {
background-color: #8B2316;
color: #ffffff;
border: none;
padding: 16rpx 32rpx;
border-radius: 8rpx;
font-size: 26rpx;
}
}
}
}
@ -510,6 +634,7 @@
//
.patient-list {
height:calc(100vh - 265rpx);
display: flex;
@ -576,6 +701,11 @@
flex: 1;
font-size: 32rpx;
color: #333333;
.new-patient-count {
color: red;
font-size: 32rpx;
margin-left: 10rpx;
}
}
}
}
@ -670,7 +800,8 @@
.patient-status {
display: flex;
align-items: center;
align-items: flex-end;
flex-direction: column;
.follow-date {
font-size: 24rpx;

View File

@ -0,0 +1,112 @@
<template>
<view class="remark-page">
<uni-nav-bar
left-icon="left"
title="设置备注和分组"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eee"
/>
<view class="form-block">
<view class="label">备注</view>
<input class="input" v-model.trim="remark" placeholder="给患者添加备注名" placeholder-class="ph" maxlength="20"/>
</view>
<view class="form-block">
<view class="label">分组</view>
<view class="iptbox">
<input class="input" v-model.trim="remark" placeholder="通过分组给患者分类" placeholder-class="ph" maxlength="20" readonly/>
<uni-icons type="right" size="20" color="#666"></uni-icons>
</view>
</view>
<view class="form-block">
<view class="label">描述</view>
<textarea class="textarea" v-model.trim="note" placeholder="补充患者关键信息,方便随访患者" placeholder-class="ph" auto-height maxlength="140"/>
</view>
<view class="bottom-bar">
<button class="save-btn" @click="saveRemark">保存</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
const remark = ref('')
const note = ref('')
const goBack = () => {
uni.navigateBack()
}
const saveRemark = () => {
if (!remark.value) {
uni.showToast({ title: '请输入备注名', icon: 'none' })
return
}
// TODO:
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => goBack(), 700)
}
</script>
<style lang="scss" scoped>
.remark-page{
min-height: 100vh;
background: #f7f7f7;
padding-bottom: 140rpx;
}
.form-block{
.iptbox{
background: #f8f8f8;
display: flex;
align-items: center;
}
background: #fff;
padding: 24rpx 30rpx 30rpx;
.label{
font-size: 28rpx;
color: #666;
margin-bottom: 16rpx;
}
.input{
flex:1;
background: #f8f8f8;
border-radius: 12rpx;
padding: 24rpx;
font-size: 30rpx;
color: #333;
}
.textarea{
background: #f8f8f8;
border-radius: 12rpx;
padding: 24rpx;
font-size: 28rpx;
color: #333;
min-height: 180rpx;
line-height: 1.6;
}
.ph{
color: #bfbfbf;
}
}
.bottom-bar{
position: fixed;
left: 0; right: 0; bottom: 0;
background: #ffffff;
border-top: 1rpx solid #f0f0f0;
padding: 20rpx 24rpx env(safe-area-inset-bottom);
.save-btn{
width: 100%;
height: 92rpx;
background: #8B2316;
color: #fff;
border: none;
border-radius: 12rpx;
font-size: 32rpx;
}
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<view class="setting-page">
<uni-nav-bar
left-icon="left"
title="常用设置"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eee"
>
</uni-nav-bar>
<view class="list-block">
<view class="cell" @click="goRemark">
<text class="cell-left">设置备注</text>
<view class="cell-right">
<text class="cell-desc">给患者添加备注名</text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="cell" @click="goRemark">
<text class="cell-left">设置分组</text>
<view class="cell-right">
<text class="cell-desc">通过分组给患者分类</text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="cell" @click="goRemark">
<text class="cell-left">患者描述</text>
<view class="cell-right">
<text class="cell-desc">补充患者关键信息方便随访患者</text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="cell" @click="setNextFollow">
<text class="cell-left">下次随访时间</text>
<view class="cell-right">
<text class="cell-desc">添加随访提醒</text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="cell" @click="goFeedback">
<text class="cell-left">投诉反馈</text>
<view class="cell-right">
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
</view>
<view class="danger-block">
<text class="danger-text" @click="unbindPatient">解除随访</text>
</view>
</view>
</template>
<script setup>
import navTo from '@/utils/navTo.js'
import { onLoad } from '@dcloudio/uni-app'
const goBack = () => {
uni.navigateBack();
}
const goRemark = () => {
navTo({ url: '/pages_app/patientRemark/patientRemark' })
}
const setNextFollow = () => {
navTo({ url: '/pages_app/visit/visit' })
}
const goFeedback = () => {
navTo({ url: '/pages_app/feedback/feedback' })
}
const unbindPatient = () => {
uni.showModal({
title: '确认解除',
content: '确定要解除随访关系吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '已提交解除', icon: 'success' })
// TODO: 访
}
}
})
}
</script>
<style lang="scss" scoped>
.setting-page{
min-height: 100vh;
background:#f7f7f7;
}
.list-block{
margin-top: 20rpx;
background: #fff;
.cell{
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child{ border-bottom: none; }
.cell-left{
font-size: 32rpx;
color: #333;
}
.cell-right{
display: flex;
align-items: center;
gap: 16rpx;
.cell-desc{
font-size: 26rpx;
color: #999;
}
}
}
}
.danger-block{
margin-top: 30rpx;
background: #fff;
padding: 40rpx 0;
display: flex;
justify-content: center;
.align-center{ align-items: center; }
.danger-text{
color: #8B2316;
font-size: 32rpx;
}
}
</style>

View File

@ -88,7 +88,7 @@
:class="{ active: tag.selected }"
@click="toggleTag(index)"
>
{{ tag.name }}
{{ tag.DES}}
</view>
</view>
@ -170,7 +170,24 @@
icon: 'none'
});
};
const loadGuideTags = async () => {
try {
const res = await api.guideTag({
type:6
});
console.log('指南标签API响应:', res);
if(res && res.code === 200 && res.data) {
// API
filterTags.value = res.data.map(tag => ({
...tag,
selected: false
}));
console.log('指南标签加载成功:', filterTags.value);
}
} catch (e) {
console.error('加载指南标签失败:', e);
}
};
//
const onRefresh = async () => {
@ -363,6 +380,7 @@
//
console.log('页面显示,开始加载课件数据');
loadData(true);
loadGuideTags()
});
//

View File

@ -9,7 +9,7 @@
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#ffffff"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right" @click="submitSchedule">

View File

@ -0,0 +1,130 @@
<template>
<view class="select-page">
<uni-nav-bar
left-icon="left"
title="选择患者"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="confirm-btn" :class="{ active: selectedIds.length > 0 }" @click="confirmSelect">
<text class="confirm-text">确定({{ selectedIds.length }})</text>
</view>
</template>
</uni-nav-bar>
<!-- 搜索框 -->
<view class="search-bar">
<view class="input-wrap">
<input class="search-input" v-model.trim="keyword" placeholder="搜索患者的备注名、昵称或手机号" placeholder-class="ph" @confirm="onSearch" />
</view>
<view class="search-btn" @click="onSearch">
<uni-icons type="search" size="50rpx" color="#999" />
</view>
</view>
<!-- 列表 -->
<scroll-view class="list" scroll-y>
<view class="item" @click="toggle(p.uuid)" v-for="p in patientList" :key="p.uuid">
<image class="avatar" :src="docUrl + (p.photo || '')" mode="aspectFill" />
<view class="name">{{ p.realName || '-' }}</view>
<view class="check" >
<view class="circle" :class="{ active: selectedIds.includes(p.uuid) }"></view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import docUrl from '@/utils/docUrl.js'
import { onShow,onLoad} from "@dcloudio/uni-app";
import api from '@/api/api.js'
const keyword = ref('')
const selectedIds = ref([])
const patientList = ref([])
const selectedDetail = ref([])
const patientListByGBK = async () => {
const res = await api.patientListByGBK();
if(res.code == 1){
patientList.value = res.data;
}
};
onLoad(() => {
})
onShow(() => {
patientListByGBK();
});
const toggle = (id) => {
const i = selectedIds.value.indexOf(id)
if (i > -1) {
selectedIds.value.splice(i, 1)
const di = selectedDetail.value.findIndex(it => it.uuid === id)
if (di > -1) selectedDetail.value.splice(di, 1)
} else {
selectedIds.value.push(id)
const p = patientList.value.find(x => x.uuid === id)
selectedDetail.value.push({ uuid: id, realName: p?.realName || '', photo: p?.photo || '' })
}
}
const onSearch = () => {}
const goBack = () => uni.navigateBack()
const confirmSelect = () => {
const payload = { ids: selectedIds.value, list: selectedDetail.value }
//
try {
const pages = getCurrentPages()
const curr = pages[pages.length - 1]
const ec = curr?.getOpenerEventChannel?.()
ec?.emit && ec.emit('onPatientsSelected', payload)
} catch (e) {}
// 使
try { uni.setStorageSync('patientsSelectedPayload', payload) } catch (e) {}
uni.navigateBack()
}
</script>
<style lang="scss" scoped>
.select-page{
min-height: 100vh; background:#fefefe;
}
.confirm-text{ color:#fff; font-size: 28rpx;white-space: nowrap; }
.confirm-btn{ background:#7f7f7f; padding: 10rpx 18rpx; border-radius: 26rpx; }
.confirm-btn.active{ background:#8B2316; }
.search-bar{
border: 2rpx solid #eee;
margin: 20rpx 30rpx; display:flex; align-items:center; gap: 16rpx;
.input-wrap{ flex:1; background:#fff; border-radius: 12rpx; padding: 16rpx 20rpx; }
.search-input{ font-size: 28rpx; color:#333; }
.ph{ color:#bfbfbf; }
.search-btn{
display: flex;
align-items: center;
justify-content: center;
width: 88rpx; height: 72rpx; background:#fff;
}
}
.list{ border-radius: 12rpx; }
.item{background:#fff; display:flex; align-items:center;padding: 24rpx 30rpx; border-bottom: 2rpx solid #eee; }
.avatar{ width: 96rpx; height:96rpx; border-radius: 16rpx; background:#ffe; }
.name{ flex:1; margin-left: 20rpx; font-size: 32rpx; color:#333; }
.check{ padding-left: 12rpx; }
.circle{ width: 40rpx; height: 40rpx; border-radius: 50%; border: 2rpx solid #cfcfcf; }
.circle.active{ background:#8B2316; border-color:#8B2316; position: relative; }
.circle.active::after{ content:''; position:absolute; left: 14rpx; top: 6rpx; width: 10rpx; height: 18rpx; border: 4rpx solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg); }
</style>

View File

@ -9,7 +9,7 @@
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#ffffff"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right" @click="submitPlan">

BIN
static/iv_delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB