# Conflicts:
#	pages/my/my.vue
This commit is contained in:
haomingming 2025-09-26 17:16:18 +08:00
commit 71136a03a6
15 changed files with 1870 additions and 48 deletions

View File

@ -556,6 +556,21 @@ const api = {
toAddNickname(data){
return request('/expertAPI/toAddNickname', data, 'post', false);
},
caseDetail(data){
return request('/expertAPI/caseDetail', data, 'post', false);
},
conditionRecordList(data){
return request('/expertAPI/conditionRecordList', data, 'post', false);
},
addConditionRecord(data){
return request('/expertAPI/addConditionRecord', data, 'post', false);
},
updateConditionRecord(data){
return request('/expertAPI/upConditionRecord', data, 'post', false);
},
deleteConditionRecord(data){
return request('/expertAPI/delConditionRecord', data, 'post', false);
},
}
export default api

View File

@ -8,14 +8,15 @@
"license": "ISC",
"description": "",
"dependencies": {
"@xkit-yx/im-store-v2": "^0.8.3",
"@xkit-yx/utils": "^0.7.2",
"crypto-js": "^4.2.0",
"@xkit-yx/im-store-v2": "^0.8.3",
"@xkit-yx/utils": "^0.7.2",
"mobx": "^6.6.1",
"nim-web-sdk-ng": "^10.9.30",
"dayjs": "^1.11.18",
"image-tools": "^1.4.0",
"js-base64": "^3.7.8",
"js-md5": "^0.8.3",
"mobx": "^6.6.1",
"nim-web-sdk-ng": "^10.9.30",
"pinyin": "^4.0.0",
"uview-plus": "^3.4.73"
}

View File

@ -606,6 +606,58 @@
}
}
},
{
"path": "caseRecord/caseRecord",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "searchPatient/searchPatient",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "visitPlan/visitPlan",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "caseList/caseList",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "checkRecord/checkRecord",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "qikan/qikan",
"style": {
@ -1011,6 +1063,7 @@
}
}
},
{
"path": "productDetail/productDetail",
"style": {

View File

@ -472,6 +472,11 @@
url='/pages_course/course/course'
}else if(name=='积分商城'){
url='/pages_goods/pointMall/pointMall'
}else if(name=='我的福利'){
url='/pages_app/myWelfare/myWelfare'
}else if(name=='专题e站'){
const encoded = encodeURIComponent('https://wx.igandan.com/Esite/index.htm#/home?fromtype=doctor')
url=`/pages_app/webview/webview?url=${encoded}`
}else{
url='/pages_app/myApplication/myApplication'
}

View File

@ -215,12 +215,35 @@
<!-- 底部导航栏 -->
<CustomTabbar></CustomTabbar>
</view>
<unidialog :visible="hasSign" content="今日已签到,每天只能签到一次。<br>请明日继续哦~" @close="hasSign=false" :showCancel="true" cancelText="关闭"></unidialog>
<up-overlay :show="showSign" >
<view class="signwrap">
<view class="signbox">
<view class="close" @click="showSign=false"></view>
<view class="signbg">
<up-image :src="signImg" width="604rpx" height="964rpx" ></up-image>
</view>
<view class="signcontent">
<view class="day">今天是我们相识的第{{signInfo.gdxzday}}</view>
<view class="signtotal">本周共签到{{signInfo.totalDay}}</view>
<view class="signcontinue">已经连续签到{{signInfo.continuous_day}}</view>
<view class="tip">连续签到获取更多积分</view>
<view class="news" @click.stop="goNews">
{{signInfo.news.summary}}
</view>
</view>
</view>
</view>
</up-overlay>
</template>
<script setup>
import unidialog from '@/components/dialog/dialog.vue'
import signImg from "@/static/sign_in_bng_big.png"
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import {
ref
ref,reactive
} from 'vue';
import {
onShow
@ -246,7 +269,7 @@
import ghsjhImg from "@/static/ghsjh.png"
import tzykImg from "@/static/xxtx.png"
import versionImg from "@/static/fxxbb.png"
import jifenImg from "@/static/point_buy.png"
import fulicard from "@/static/fulicard.png"
import fpgl from "@/static/fpgl.png"
@ -258,7 +281,13 @@
import linkUrl from "@/utils/docUrl"
//
const isLargeFont = ref(false);
const hasSign = ref(false)
const showSign = ref(false)
const signInfo=reactive({
news:{
summary:''
}
})
//
const avatar = ref('')
const username = ref('')
@ -269,7 +298,26 @@
const totalPoints = ref(0)
const sign_in = ref(0)
const honor_list = ref([])
const notice_on = ref(false)
const goNews=()=>{
let url=docUrl+signInfo.news.path;
// #ifdef H5
window.open(url, '_blank');
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(url);
// #endif
// #ifdef MP
const encoded = encodeURIComponent(url);
navTo({
url: `/pages_app/webview/webview?url=${encoded}`
});
// #endif
}
// storage
const getUserInfoFromStorage = () => {
try {
@ -300,6 +348,7 @@
title: '签到成功,获得'+res.bonuspoints+'积分',
icon: 'none'
});
Object.assign(signInfo,res);
} else {
uni.showToast({
title: res?.msg || '签到失败',
@ -647,7 +696,66 @@ const switchPushPermissions = () => {
padding-bottom: 100rpx;
}
.signwrap{
display: flex;
align-items: center;
justify-content: center;
height:100%;
.signbox{
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index:0;
.close{
position:absolute;
right:0;
height:60rpx;
width:60rpx;
opacity: 0;
background:#fff;
z-index:2;
border-radius: 50%;
}
}
}
.signwrap .signcontent{
width:100%;
top:0;
position: absolute;
z-index:1;
}
.signwrap .signcontent .day{
margin-top: 384rpx;
text-align: center;
}
.signwrap .signtotal{
margin-top: 30rpx;
text-align: center;
font-size: 30rpx;
}
.signwrap .signcontinue{
font-size: 30rpx;
text-align: center;
}
.signwrap .signcontent .tip{
margin-top: 40rpx;
color:red;
font-size: 28rpx;
text-align: center;
}
.signwrap .signcontent .news{
margin: 196rpx 60rpx 0;
height: 116rpx;
font-size: 30rpx;
}
.signwrap .signbg{
width:604rpx;
height:964rpx;
}
//
.user-card {
@ -747,7 +855,7 @@ const switchPushPermissions = () => {
position: absolute;
right: 0;
top: 54rpx;
width: 160rpx;
width: 150rpx;
height: 56rpx;
display: flex;
align-items: center;
@ -769,10 +877,11 @@ const switchPushPermissions = () => {
right: 0;
top: 54rpx;
width: 160rpx;
height: 56rpx;
height: 54rpx;
display: flex;
align-items: center;
border: 1rpx solid #fc564a;
border-right: none;
border-radius: 30rpx 0 0 30rpx;
background: #fff url("@/static/qd_bg.9.png")no-repeat 0 0;
background-size: 57rpx 56rpx;

View File

@ -0,0 +1,450 @@
<template>
<view class="container">
<!-- 头部导航 -->
<uni-nav-bar
left-icon="left"
title="病情记录"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<uni-icons type="plus" size="24" color="#8B2316" @click="goAddRecord" style="margin-left: 30rpx;"></uni-icons>
</view>
</template>
</uni-nav-bar>
<!-- 主要内容区域 -->
<scroll-view
class="main-content"
scroll-y
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<!-- 空状态 -->
<view class="empty-state" v-if="recordList.length === 0 && !loading">
<text class="empty-text">暂无病情记录</text>
<button class="add-first-btn" @click="addRecord">添加第一条记录</button>
</view>
<!-- 时间线容器 -->
<view class="timeline-container" v-else>
<!-- 时间线 -->
<view class="timeline-line"></view>
<!-- 记录条目 -->
<view
class="record-item"
v-for="(record, index) in recordList"
:key="record.uuid"
@click="goToDetail(record)"
>
<!-- 时间线节点 -->
<view class="timeline-node">
<view class="node-circle"></view>
</view>
<!-- 记录内容 -->
<view class="record-content">
<!-- 日期气泡 -->
<view class="date-bubble">
<text class="date-text">{{ record.create_date }}</text>
</view>
<!-- 记录描述 -->
<view class="record-description" v-if="record.des">
<text class="description-text">{{ record.des }}</text>
</view>
<!-- 记录图片 -->
<view class="record-images" v-if="record.photo && record.photo.length > 0">
<view class="image-grid">
<view
class="image-item"
v-for="(imagePath, imgIndex) in record.photo"
:key="imgIndex"
@click.stop="previewImages(record.photo, imgIndex)"
>
<image
:src="docUrl + imagePath"
class="content-image"
mode="aspectFill"
/>
</view>
</view>
</view>
</view>
</view>
<!-- 加载更多状态 -->
<view class="load-more" v-if="recordList.length > 0">
<view class="loading-text" v-if="loadingMore">
<uni-load-more status="loading" content-text="{ contentText: { contentdown: '上拉显示更多', contentrefresh: '正在加载...', contentnomore: '没有更多数据了' } }"></uni-load-more>
</view>
<!-- <view class="no-more-text" v-else-if="currentPage >= totalPage">
<text>没有更多数据了</text>
</view> -->
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
// Vue3 Composition API
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import navBar from '@/components/navBar/navBar.vue'
import api from '@/api/api.js'
import docUrl from '@/utils/docUrl.js'
const patientUuid = ref('')
const getRecordList = (isRefresh = false) => {
let usrInfo = uni.getStorageSync('userInfo')
//
if (isRefresh) {
currentPage.value = 1
}
//
if (isRefresh) {
refreshing.value = true
} else if (currentPage.value === 1) {
loading.value = true
} else {
loadingMore.value = true
}
api.conditionRecordList({
patient_uuid: patientUuid.value,
expert_uuid: usrInfo.uuid,
page: currentPage.value
}).then(res => {
console.log(res)
if(res.code == 200){
const newList = res.data.list || []
if (isRefresh || currentPage.value === 1) {
//
recordList.value = newList
} else {
//
recordList.value = [...recordList.value, ...newList]
}
totalPage.value = res.data.totalPage || 1
}
}).catch(err => {
console.error('获取病情记录失败:', err)
uni.showToast({
title: '获取数据失败',
icon: 'none'
})
}).finally(() => {
//
loading.value = false
refreshing.value = false
loadingMore.value = false
})
}
const goToDetail = (record) => {
uni.setStorageSync('caseRecord', record)
uni.navigateTo({
url: '/pages_app/caseRecord/caseRecord?uuid=' + record.uuid + '&patientUuid=' + patientUuid.value
})
}
const goAddRecord = () => {
uni.navigateTo({
url: '/pages_app/caseRecord/caseRecord?patientUuid=' + patientUuid.value
})
}
onLoad((options) => {
patientUuid.value = options.uuid
})
onShow(() => {
getRecordList()
})
//
const recordList = ref([])
const totalPage = ref(1)
const currentPage = ref(1)
const loading = ref(false)
const refreshing = ref(false)
const loadingMore = ref(false)
//
const goBack = () => {
uni.navigateBack({
delta: 1,
fail() {
uni.redirectTo({
url: '/pages/index/index'
})
}
})
}
const addRecord = () => {
//
uni.navigateTo({
url: '/pages_app/addRecord/addRecord?patientUuid=' + patientUuid.value
})
}
const managePatients = () => {
//
addRecord()
}
const previewImages = (imageList, currentIndex) => {
const urls = imageList.map(img => docUrl + img)
uni.previewImage({
current: currentIndex,
urls: urls
})
}
//
const onRefresh = () => {
console.log('下拉刷新')
getRecordList(true)
}
//
const onLoadMore = () => {
console.log('上拉加载更多')
//
if (loadingMore.value || loading.value || refreshing.value) {
return
}
//
if (currentPage.value >= totalPage.value) {
return
}
// 1
currentPage.value++
//
getRecordList(false)
}
//
onMounted(() => {
console.log('病情记录页面已加载')
})
</script>
<style scoped>
.container {
background-color: #ffffff;
min-height: 100vh;
}
/* 头部导航样式 */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
}
.back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 40rpx;
color: #8B2316;
font-weight: bold;
}
.header-title {
font-size: 36rpx;
color: #8B2316;
font-weight: bold;
flex: 1;
text-align: center;
}
.add-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.add-icon {
font-size: 40rpx;
color: #8B2316;
font-weight: bold;
}
/* 主要内容区域 */
.main-content {
height: calc(100vh - 140rpx);
padding: 40rpx 30rpx;
box-sizing: border-box;
}
.timeline-container {
position: relative;
}
.timeline-line {
position: absolute;
left: 30rpx;
top: 0;
bottom: 0;
width: 4rpx;
background-color: #8B2316;
}
.record-item {
position: relative;
margin-bottom: 60rpx;
display: flex;
align-items: flex-start;
}
.timeline-node {
position: relative;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.node-circle {
width: 20rpx;
height: 20rpx;
background-color: #8B2316;
border-radius: 50%;
border: 4rpx solid #ffffff;
box-shadow: 0 0 0 2rpx #8B2316;
}
.record-content {
flex: 1;
margin-left: 30rpx;
}
.date-bubble {
background-color: #ffffff;
border: 2rpx solid #8B2316;
border-radius: 20rpx;
padding: 20rpx 30rpx;
margin-bottom: 20rpx;
display: inline-block;
}
.date-text {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
.record-description {
margin-bottom: 20rpx;
}
.description-text {
font-size: 30rpx;
color: #333333;
line-height: 1.6;
}
.record-images {
margin-top: 20rpx;
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 15rpx;
}
.image-item {
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.content-image {
width: 100%;
height: 100%;
}
/* 空状态样式 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
text-align: center;
}
.empty-text {
font-size: 32rpx;
color: #999999;
margin-bottom: 40rpx;
}
.add-first-btn {
background-color: #8B2316;
color: white;
border: none;
border-radius: 25rpx;
padding: 20rpx 40rpx;
font-size: 28rpx;
}
/* 加载更多样式 */
.load-more {
padding: 40rpx 0;
text-align: center;
}
.loading-text {
display: flex;
justify-content: center;
align-items: center;
}
.no-more-text {
font-size: 28rpx;
color: #999999;
}
/* 响应式调整 */
@media (max-width: 750rpx) {
.content-image {
width: 100%;
max-width: 400rpx;
}
}
</style>

View File

@ -0,0 +1,460 @@
<template>
<view class="container">
<!-- 头部导航 -->
<uni-nav-bar
left-icon="left"
title="病情记录"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<text class="modify-btn" @click="saveRecord" v-if="recordUuid"
>修改</text
>
<text class="save-btn" @click="saveRecord" v-else>保存</text>
</view>
</template>
</uni-nav-bar>
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 日期选择区域 -->
<view class="form-item row">
<view class="form-label">日期</view>
<view class="form-value" @click="openDateTime">
<text class="date-text">{{ dateTime }}</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
<view class="divider"></view>
<view class="form-item">
<view class="form-label">描述</view>
<view class="form-value">
<textarea
class="description-input"
v-model="des"
placeholder="请输入患者病情"
></textarea>
</view>
</view>
<view class="divider"></view>
<!-- 图片上传区域 -->
<view class="form-item">
<view class="form-label">图片</view>
<view class="image-upload-area">
<!-- 已上传的图片 -->
<view
class="image-item"
v-for="(image, index) in tempImageList"
:key="index"
>
<image
:src="image"
class="uploaded-image"
mode="aspectFill"
@click="previewImage(index)"
/>
<view class="delete-btn" @click="deleteImage(index)">
<uni-icons type="closeempty" size="15" color="#fff"></uni-icons>
</view>
</view>
<!-- 添加图片按钮 -->
<view
class="add-image-btn"
v-if="imageList.length < 9"
@click="chooseImage"
>
<uni-icons type="plus" size="24" color="#999"></uni-icons>
</view>
</view>
</view>
</view>
<!-- 底部删除按钮 -->
<view class="bottom-actions" v-if="recordUuid">
<button class="delete-btn" @click="deleteRecord">删除该条记录</button>
</view>
<up-datetime-picker
:show="showDate"
v-model="selectedDate"
@cancel="showDate = false"
@confirm="confirmDate"
mode="date"
></up-datetime-picker>
</view>
</template>
<script setup>
// Vue3 Composition API
import { ref, reactive, onMounted, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import api from "@/api/api.js";
import docUrl from "@/utils/docUrl.js";
import dayjs from "dayjs";
import { pathToBase64, base64ToPath } from "image-tools";
//
const des = ref("");
const showDate = ref(false);
const recordUuid = ref("");
const patientUuid = ref("");
const dateTime = ref(dayjs().format("YYYY年MM月DD日"));
const selectedDate = ref(dayjs());
const description = ref("");
const imageList = ref([]);
const tempImageList = ref([]);
const originalData = ref({});
//
const goBack = () => {
uni.navigateBack({
delta: 1,
fail() {
uni.redirectTo({
url: "/pages/index/index",
});
},
});
};
const confirmDate = ({ value, mode }) => {
console.log(value);
dateTime.value = dayjs(value).format("YYYY年MM月DD日");
selectedDate.value = dayjs(value).format("YYYY-MM-DD");
showDate.value = false;
};
const openDateTime = () => {
console.log("openDateTime");
showDate.value = true;
};
const chooseImage = () => {
uni.chooseImage({
count: 8 - imageList.value.length,
sizeType: ["original", "compressed"],
sourceType: ["album", "camera"],
success: (res) => {
//
// imageList.value = [...imageList.value, ...res.tempFilePaths]
tempImageList.value = [...tempImageList.value, ...res.tempFilePaths];
const fileManager = uni.getFileSystemManager();
for (let i = 0; i < res.tempFilePaths.length; i++) {
fileManager.readFile({
filePath: res.tempFilePaths[i],
encoding: "base64",
success: (res) => {
imageList.value.push(res.data);
},
fail: (error) => {
console.log("chooseImage", error);
},
});
}
},
});
};
const deleteImage = (index) => {
//
uni.showModal({
title: "确认删除",
content: "确定要删除这张图片吗?",
//
success: (res) => {
//
if (res.confirm) {
// imageList
imageList.value.splice(index, 1);
tempImageList.value.splice(index, 1);
}
},
});
};
const previewImage = (index) => {
uni.previewImage({
current: index,
urls: imageList.value,
});
};
const addRecord = async () => {
if (!des.value) {
uni.showToast({
title: "请输入患者病情",
icon: "none",
});
return;
}
let usrInfo = uni.getStorageSync("userInfo");
const res = await api.addConditionRecord({
patient_uuid: patientUuid.value,
expert_uuid: usrInfo.uuid,
des: des.value,
create_date: dayjs(selectedDate.value).format("YYYY-MM-DD"),
img1: imageList.value[0] || "",
img2: imageList.value[1] || "",
img3: imageList.value[2] || "",
img4: imageList.value[3] || "",
img5: imageList.value[4] || "",
img6: imageList.value[5] || "",
img7: imageList.value[6] || "",
img8: imageList.value[7] || "",
});
if (res.code == 200) {
uni.showToast({
title: "添加成功",
icon: "none",
});
goBack();
}
};
const updateRecord = async () => {
if (!des.value) {
uni.showToast({
title: "请输入患者病情",
icon: "none",
});
return;
}
let usrInfo = uni.getStorageSync("userInfo");
const res = await api.updateConditionRecord({
uuid: recordUuid.value,
patient_uuid: patientUuid.value,
expert_uuid: usrInfo.uuid,
des: des.value,
create_date: dayjs(selectedDate.value).format("YYYY-MM-DD"),
img1: imageList.value[0] || "",
img2: imageList.value[1] || "",
img3: imageList.value[2] || "",
img4: imageList.value[3] || "",
img5: imageList.value[4] || "",
img6: imageList.value[5] || "",
img7: imageList.value[6] || "",
img8: imageList.value[7] || "",
});
if (res.code == 200) {
uni.showToast({
title: "修改成功",
icon: "none",
});
uni.setStorageSync("caseRecord",null);
goBack();
}
};
const deleteConditionRecord = async () => {
const res = await api.deleteConditionRecord({
uuid: recordUuid.value,
});
if (res.code == 200) {
uni.showToast({
title: "删除成功",
icon: "none",
});
goBack();
}
};
const deleteRecord = () => {
uni.showModal({
title: "确认删除",
content: "确定要删除这条记录吗?",
success: (res) => {
if (res.confirm) {
deleteConditionRecord();
}
},
});
};
//
onLoad((options) => {
console.log(options);
recordUuid.value = options.uuid || "";
patientUuid.value = options.patientUuid || "";
if (recordUuid.value) {
imageList.value = [];
let record = uni.getStorageSync("caseRecord");
des.value = record.des;
tempImageList.value = record.photo.map((item) => docUrl + item);
// imageList.value = record.photo.map(item=>docUrl+item);
dateTime.value = dayjs(record.create_date).format("YYYY年MM月DD日");
selectedDate.value = dayjs(record.create_date);
for (let i = 0; i < record.photo.length; i++) {
console.log("url", docUrl + record.photo[i]);
uni.request({
url: docUrl + record.photo[i],
method: 'GET',
responseType: 'arraybuffer',
success: (res) => {
const base64 = `${uni.arrayBufferToBase64(res.data)}`
console.log(base64);
imageList.value.push(base64);
},
fail: (err) => {
reject(err);
},
})
}
}
});
const saveRecord = () => {
if (recordUuid.value) {
updateRecord();
} else {
addRecord();
}
};
</script>
<style scoped lang="scss">
.row {
display: flex;
align-items: center;
flex-direction: row;
}
.container {
background-color: #ffffff;
min-height: 100vh;
}
/* 头部导航样式 */
.nav-right {
padding-right: 30rpx;
}
.modify-btn,
.save-btn {
font-size: 32rpx;
color: #8b2316;
font-weight: 500;
}
/* 主要内容区域 */
.main-content {
padding: 0rpx 30rpx;
}
.form-item {
padding: 30rpx 0;
}
.form-label {
font-size: 32rpx;
color: #333333;
margin-bottom: 20rpx;
font-weight: 500;
}
.form-value {
display: flex;
flex: 1;
justify-content: flex-end;
align-items: center;
padding: 20rpx 0;
}
.date-text {
font-size: 30rpx;
color: #333333;
}
.description-input {
width: 100%;
min-height: 200rpx;
font-size: 30rpx;
color: #333333;
line-height: 1.6;
background-color: 2rpx solid #f5f5f5;
border: none;
padding: 20rpx 0;
}
.description-input:disabled {
color: #999999;
background-color: #f5f5f5;
}
/* 图片上传区域 */
.image-upload-area {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 20rpx;
}
.image-item {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
}
.uploaded-image {
width: 100%;
height: 100%;
}
.image-item .delete-btn {
position: absolute;
top: 10rpx;
right: 10rpx;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.add-image-btn {
width: 160rpx;
height: 160rpx;
border: 2rpx dashed #cccccc;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f9f9f9;
}
.divider {
height: 1rpx;
background-color: #f0f0f0;
margin: 0 -30rpx;
}
/* 底部操作区域 */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 30rpx;
background-color: #ffffff;
border-top: 1rpx solid #f0f0f0;
}
.bottom-actions .delete-btn {
width: 100%;
height: 88rpx;
background-color: #8b2316;
color: white;
border: none;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 500;
}
</style>

View File

@ -0,0 +1,341 @@
<template>
<view class="container">
<!-- 头部导航 -->
<navBar :title="headerTitle" />
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 疾病诊断部分 -->
<view class="section">
<view class="section-header">
<view class="red-bar"></view>
<text class="section-title">疾病诊断</text>
</view>
<view class="section-content">
<text class="diagnosis-text">疾病诊断: {{ pageData.diseaseName }}</text>
</view>
</view>
<!-- 化验报告部分 -->
<view class="section">
<view class="section-header">
<text class="section-title">化验报告</text>
</view>
<view class="section-content">
<text class="no-report-text" v-if="!pageData.hasLabReport">无检查化验报告</text>
<view v-else class="lab-report-container">
<view class="image-grid">
<view
class="image-item"
v-for="(image, index) in caseImages"
:key="image.uuid"
@click="previewImage(index)"
>
<image
:src="docUrl+image.path"
class="report-image"
mode="aspectFill"
/>
<!-- <view class="image-date">{{ formatDate(image.createDate) }}</view> -->
</view>
</view>
</view>
</view>
</view>
<!-- 疾病描述部分 -->
<view class="section">
<view class="section-header">
<text class="section-title">疾病描述</text>
</view>
<view class="section-content">
<text class="description-text">{{ pageData.des || '暂无描述' }}</text>
</view>
</view>
</view>
<!-- 底部线条 -->
<view class="bottom-line"></view>
</view>
</template>
<script setup>
// Vue3 Composition API
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow,onLoad } from '@dcloudio/uni-app'
import navBar from '@/components/navBar/navBar.vue'
import api from '@/api/api.js'
import docUrl from '@/utils/docUrl.js'
const uuid = ref('')
onLoad((options) => {
uuid.value = options.uuid
})
onShow(() => {
getDetail()
})
//
const pageData = ref({
date: '',
diseaseName: '',
diagnosis: '',
hasLabReport: false,
labReport: '',
description: '',
photo: '',
age: 0,
title: '',
patientUuid: '',
diseaseUuid: '',
state: 0,
createDate: '',
modifyDate: null
})
//
const caseImages = ref([])
const getDetail = () => {
api.caseDetail({
caseUuid: uuid.value
}).then(res => {
console.log(res)
if(res.code == 200){
//
pageData.value = res.data.case
//
caseImages.value = res.data.caseImg || []
//
pageData.value.hasLabReport = caseImages.value.length > 0
}
}).catch(err => {
console.error('获取案例详情失败:', err)
uni.showToast({
title: '获取数据失败',
icon: 'none'
})
})
}
//
const headerTitle = computed(() => {
return pageData.value.title || `${pageData.value.createDate}${pageData.value.diseaseName}`
})
//
const formatDate = (dateStr) => {
if (!dateStr) return ''
const date = new Date(dateStr)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '-')
}
//
const goBack = () => {
uni.navigateBack()
}
//
const previewImage = (index) => {
const urls = caseImages.value.map(img => docUrl+img.path)
uni.previewImage({
current: index,
urls: urls
})
}
//
onMounted(() => {
//
console.log('检查记录页面已加载')
})
</script>
<style scoped>
.container {
background-color: #ffffff;
min-height: 100vh;
}
/* 状态栏样式 */
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 30rpx;
background-color: #ffffff;
font-size: 28rpx;
color: #333333;
}
.status-left {
font-weight: 500;
}
.status-center {
flex: 1;
display: flex;
justify-content: center;
}
.network-icons {
display: flex;
align-items: center;
gap: 10rpx;
}
.bluetooth-icon, .wifi-icon {
font-size: 24rpx;
}
.speed {
font-size: 24rpx;
color: #666666;
}
.signal {
font-size: 24rpx;
color: #333333;
}
.status-right {
font-weight: 500;
}
/* 头部导航样式 */
.header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
}
.back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.back-icon {
font-size: 40rpx;
color: #ff4444;
font-weight: bold;
}
.header-title {
font-size: 36rpx;
color: #ff4444;
font-weight: bold;
flex: 1;
}
/* 主要内容区域 */
.main-content {
padding: 0 30rpx;
}
.section {
margin-bottom: 40rpx;
}
.section-header {
margin-top: 20rpx;
position: relative;
margin-bottom: 20rpx;
}
.red-bar {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 8rpx;
height: 40rpx;
background-color: #ff4444;
border-radius: 4rpx;
}
.section-title {
font-size: 32rpx;
color: #333333;
font-weight: bold;
margin-left: 20rpx;
}
.section-content {
padding-left: 28rpx;
}
.diagnosis-text {
font-size: 30rpx;
color: #333333;
line-height: 1.5;
}
.no-report-text {
font-size: 28rpx;
color: #999999;
line-height: 1.5;
}
.lab-report-text {
font-size: 28rpx;
color: #333333;
line-height: 1.5;
}
.lab-report-container {
margin-top: 20rpx;
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.report-image {
width: 100%;
height: 100%;
}
.image-date {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
color: white;
font-size: 20rpx;
padding: 20rpx 10rpx 10rpx;
text-align: center;
}
.description-text {
font-size: 28rpx;
color: #999999;
line-height: 1.5;
}
/* 底部线条 */
.bottom-line {
height: 2rpx;
background-color: #f0f0f0;
margin: 40rpx 30rpx 0;
}
</style>

View File

@ -47,7 +47,7 @@
<uni-icons type="right" color="#999" size="18"></uni-icons>
</view>
</view>
<view class="cell" @click="editDesc">
<view class="cell" @click="openGroup">
<text class="cell-label">描述</text>
<view class="cell-right">
<text class="hint">补充患者关键信息方便随访患者</text>
@ -62,10 +62,22 @@
</view>
<!-- 患者病史 -->
<view class="section-title">患者病史</view>
<view class="card history-card">
<text class="history-text" :class="{ fold: !showAllHistory }">{{ patientDetail.medicalHistoryContent }}</text>
<text class="toggle" @click="toggleHistory" v-if="patientDetail.patientHistoryText">{{ showAllHistory ? '收起' : '展开全部' }}</text>
<view class="history-section" v-if="patientDetail.medicalHistoryContent">
<view class="section-title">患者病史</view>
<view class="card history-card">
<text class="history-text" :class="{ fold: !showAllHistory }">{{ patientDetail.medicalHistoryContent }}</text>
<text class="toggle" @click="toggleHistory" v-if="patientDetail.medicalHistoryContent">{{ showAllHistory ? '收起' : '展开全部' }}</text>
</view>
</view>
<!-- 检查报告 -->
<view class="history-section" v-if="patientDetail.patientCase && patientDetail.patientCase.length>0">
<view class="section-title">检查报告</view>
<view class="card report-card">
<view class="report-item" v-for="item in patientDetail.patientCase" :key="item.diseaseUuid" @click="openCase(item.uuid)">
<view class="report-item-title">{{ $u.timeFormat(item.createDate, 'yyyy-mm-dd') }}</view>
<view class="report-item-content">{{ item.diseaseName }}</view>
</view>
</view>
</view>
<!-- 底部操作 -->
@ -116,8 +128,8 @@
//
const fullAddress = computed(() => {
const { provName, cityName, countyName, detailed_address } = patientInfo.value;
const addressParts = [provName, cityName, countyName, detailed_address].filter(Boolean);
const { provName, cityName, countyName } = patientInfo.value;
const addressParts = [provName, cityName, countyName].filter(Boolean);
return addressParts.length > 0 ? addressParts.join('') : '未设置';
});
@ -127,13 +139,29 @@
patient_uuid.value = options.uuid;
});
const goBack = ()=> uni.navigateBack()
const editPatient = ()=> uni.showToast({ title:'编辑资料', icon:'none' })
const openGroup = ()=> uni.showToast({ title:'分组', icon:'none' })
const editDesc = ()=> uni.showToast({ title:'编辑描述', icon:'none' })
const sendMessage = ()=>{
const openCase = (uuid)=>{
navTo({
url: '/pages_app/chat/chat?patient_uuid='+patient_uuid,
url:'/pages_app/checkRecord/checkRecord?uuid='+uuid
})
}
const editPatient = ()=> {
navTo({
url:'/pages_app/patientSetting/patientSetting?uuid=' + patient_uuid.value
})
}
const openGroup = ()=>{
navTo({
url:'/pages_app/patientRemark/patientRemark?uuid=' + patientInfo.value.uuid
})
}
const editDesc = ()=> uni.showToast({ title:'编辑描述', icon:'none' })
const sendMessage = async()=>{
let userId=uni.getStorageSync('userInfo').uuid.toLowerCase();
let conversationId=userId+'|1|'+patientInfo.value.uuid.toLowerCase();
await uni.$UIKitStore.uiStore.selectConversation(conversationId)
navTo({
url:'/pages_chat/chat/index'
})
};
const nickname = ref('');
const group = ref({});
@ -186,17 +214,48 @@
getToAddNickname();
getPatientCard();
});
const goMakePlan = ()=> uni.navigateTo({ url:'/pages_app/visit/visit' })
const recordIllness = ()=> uni.showToast({ title:'记录病情', icon:'none' })
const goMakePlan = ()=> {
navTo({
url:'/pages_app/visitPlan/visitPlan'
})
}
const recordIllness = ()=> {
navTo({
url:'/pages_app/caseList/caseList?uuid='+patient_uuid.value
})
}
const toggleHistory = ()=> showAllHistory.value = !showAllHistory.value
</script>
<style lang="scss" scoped>
.report-card{
display: flex;
gap: 55rpx;
flex-wrap: wrap;
}
.report-item{
display: flex;
flex-direction: column;
gap: 10rpx;
background: #f5f5f5;
padding: 20rpx;
align-items: center;
border-radius: 12rpx;
.report-item-title{
font-size: 28rpx;
color:#666;
}
.report-item-content{
font-size: 28rpx;
color:#333;
}
}
.content { background:#f5f5f5; min-height:100vh; }
.nav-right { padding-right: 20rpx; }
.card { background:#ffffff; padding: 20rpx; }
.header-card { display:flex; }
.card { background:#ffffff; padding: 20rpx; padding-top: 0;}
.header-card { display:flex; padding-top: 20rpx;}
.avatar { width: 140rpx; height: 140rpx; border-radius: 12rpx; margin-right: 20rpx; }
.base-info { flex:1; }
.name-row { display:flex; align-items:center; margin-bottom: 10rpx; }
@ -215,11 +274,11 @@
.hint { font-size: 28rpx; color:#999; margin-right: 12rpx; }
.phone { font-size: 32rpx; color:#b10000; }
.section-title { padding: 16rpx 30rpx; color:#8B2316; font-size: 30rpx; margin-top: 20rpx; }
.section-title { padding: 16rpx 30rpx; color:#8B2316; font-size: 30rpx; margin-top: 20rpx; background: #fff;}
.history-card { position: relative; }
.history-text { font-size: 28rpx; color:#666; line-height: 1.7; display:block; }
.history-text.fold { display:-webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.toggle { position:absolute; right: 20rpx; bottom: 16rpx; color:#b10000; font-size: 28rpx; }
.toggle { position:absolute; right: 20rpx; bottom: 25rpx; color:#b10000; font-size: 28rpx;background: #fff; z-index: 10;}
.actions { background:#ffffff; margin-top: 20rpx;}
.action { display:flex; align-items:center;justify-content: center; padding: 28rpx 30rpx; border-top:1rpx solid #f0f0f0; }

View File

@ -3,7 +3,7 @@
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="患者消息"
:title="title"
@clickLeft="goBack"
fixed
color="#8B2316"
@ -12,10 +12,18 @@
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<view class="nav-right" v-if="activeTab === 'message'">
<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>
<view class="nav-right" v-else-if="activeTab === 'list'">
<uni-icons type="search" size="24" color="#8B2316" @click="searchPatients"></uni-icons>
<uni-icons type="plusempty" size="24" color="#8B2316" @click="goCode" style="margin-left: 30rpx;" ></uni-icons>
</view>
<view class="nav-right" v-else-if="activeTab === 'plan'" @click="showAddMenu">
<view class="save-btn">保存</view>
</view>
</template>
</uni-nav-bar>
@ -167,10 +175,10 @@
</scroll-view>
<!-- 悬浮添加按钮 -->
<view class="floating-add-btn" @click="showAddMenu">
<!-- <view class="floating-add-btn" @click="showAddMenu">
<uni-icons type="plus" size="24" color="#ffffff"></uni-icons>
<text class="btn-text">添加</text>
</view>
</view> -->
<!-- 添加菜单弹窗 -->
<view class="add-menu-popup" v-if="showAddMenuFlag" @click="hideAddMenu">
@ -191,13 +199,11 @@
</view>
</view>
</view>
</view>
</view
<!-- 底部标签栏 -->
<view class="tab-bar">
<view class="tab-item" :class="{active: activeTab === 'message'}" @click="switchTab('message')">
<text class="tab-text">患者消息</text>
</view>
<view class="tab-item" :class="{active: activeTab === 'list'}" @click="switchTab('list')">
<text class="tab-text">患者列表</text>
@ -223,7 +229,7 @@
import dayjs from 'dayjs'
import lineImg from "@/static/item_visitplan_fg.png"
import ConversationList from './conversation-list/index.vue'
const title = ref('患者消息');
const goPatientDetail = (uuid) => {
navTo({
url: `/pages_app/patientDetail/patientDetail?uuid=${uuid}`
@ -235,6 +241,11 @@
url: `/pages_app/patientSetting/patientSetting?uuid=${uuid}`
})
}
const goCode = () => {
navTo({
url: `/pages_app/myCode/myCode`
})
}
//
const formatYMD = (input) => {
if (!input) return '';
@ -489,10 +500,9 @@
//
const searchPatients = () => {
uni.showToast({
title: '搜索患者',
icon: 'none'
});
navTo({
url: `/pages_app/searchPatient/searchPatient`
})
};
//
@ -519,15 +529,19 @@
const switchTab = (tab) => {
activeTab.value = tab;
switch(tab) {
case 'message':
// -
getApplyList();
title.value = '患者消息';
break;
case 'list':
title.value = '患者列表';
//
break;
case 'plan':
title.value = '随访计划';
// 访 - 访
if (followUpList.value.length === 0) {
getFollowUpList(true);
@ -618,6 +632,11 @@
</script>
<style lang="scss" scoped>
.save-btn {
font-size: 32rpx;
color: #8b2316;
font-weight: 500;
}
.content {
background-color: #f5f5f5;
height: 100vh;
@ -893,7 +912,7 @@
.group-section {
.group-header {
padding: 20rpx 30rpx;
background-color: #f8f8f8;
background-color: #e4e4e4;
font-size: 36rpx;
color: #666666;
font-weight: bold;
@ -1044,7 +1063,6 @@
}
.plan-list{
margin-top: 20rpx;
padding-bottom: 20rpx;
}

View File

@ -0,0 +1,98 @@
<template>
<view class="select-page">
<navBar title="搜索患者" />
<!-- 搜索框 -->
<view class="search-bar">
<view class="input-wrap">
<input class="search-input" v-model.trim="keyword" placeholder="搜索患者的备注名、昵称或手机号" placeholder-class="ph" @input="$u.debounce(onSearch, 500)" />
</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="goDetail(p.uuid)" v-for="p in availablePatientList" :key="p.uuid">
<image class="avatar" :src="docUrl + (p.photo || '')" mode="aspectFill" />
<view class="name">{{ p.nickname || p.realName }}</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'
import navTo from '@/utils/navTo.js'
import navBar from '@/components/navBar/navBar.vue'
const from = ref('');
const keyword = ref('')
const patientList = ref([])
//
const availablePatientList = computed(() => {
return patientList.value
})
const patientListByGBK = async () => {
const res = await api.patientListByGBK();
if(res.code == 1){
patientList.value = res.data;
}
};
onLoad((options) => {
if(options.from == 'chatMsg'){
from.value = 'chatMsg';
}
})
onShow(() => {
patientListByGBK();
});
const goDetail = (uuid) => {
navTo({ url: `/pages_app/patientDetail/patientDetail?uuid=${uuid}` })
}
const onSearch = () => {
patientList.value = patientList.value.filter(p => p.realName.indexOf(keyword.value) !== -1 || (p.nickname && p.nickname.indexOf(keyword.value) !== -1) || p.mobile.indexOf(keyword.value) !== -1)
}
const goBack = () => 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

@ -20,7 +20,7 @@
<!-- 搜索框 -->
<view class="search-bar">
<view class="input-wrap">
<input class="search-input" v-model.trim="keyword" placeholder="搜索患者的备注名、昵称或手机号" placeholder-class="ph" @confirm="onSearch" />
<input class="search-input" v-model.trim="keyword" placeholder="搜索患者的备注名、昵称或手机号" placeholder-class="ph" @input="$u.debounce(onSearch, 500)" />
</view>
<view class="search-btn" @click="onSearch">
<uni-icons type="search" size="50rpx" color="#999" />
@ -31,7 +31,7 @@
<scroll-view class="list" scroll-y>
<view class="item" @click="toggle(p.uuid)" v-for="p in availablePatientList" :key="p.uuid">
<image class="avatar" :src="docUrl + (p.photo || '')" mode="aspectFill" />
<view class="name">{{ p.realName || '-' }}</view>
<view class="name">{{ p.nickname || p.realName }}</view>
<view class="check" >
<view class="circle" :class="{ active: selectedIds.includes(p.uuid) }"></view>
</view>
@ -111,7 +111,9 @@
}
}
const onSearch = () => {}
const onSearch = () => {
patientList.value = patientList.value.filter(p => p.realName.indexOf(keyword.value) !== -1 || (p.nickname && p.nickname.indexOf(keyword.value) !== -1) || p.mobile.indexOf(keyword.value) !== -1)
}
const goBack = () => uni.navigateBack()
const confirmSelect = () => {
const payload = { ids: selectedIds.value, list: selectedDetail.value }

View File

@ -20,7 +20,7 @@
<!-- 搜索框 -->
<view class="search-bar">
<view class="input-wrap">
<input class="search-input" v-model.trim="keyword" placeholder="搜索患者的备注名、昵称或手机号" placeholder-class="ph" @confirm="onSearch" />
<input class="search-input" v-model.trim="keyword" placeholder="搜索患者的备注名、昵称或手机号" placeholder-class="ph" @input="$u.debounce(onSearch, 500)" />
</view>
<view class="search-btn" @click="onSearch">
<uni-icons type="search" size="50rpx" color="#999" />
@ -31,7 +31,7 @@
<scroll-view class="list" scroll-y>
<view class="item" @click="toggle(p.uuid)" v-for="p in availablePatientList" :key="p.uuid">
<image class="avatar" :src="docUrl + (p.photo || '')" mode="aspectFill" />
<view class="name">{{ p.realName || '-' }}</view>
<view class="name">{{ p.nickname || p.realName }}</view>
<view class="check" >
<view class="circle" :class="{ active: selectedIds.includes(p.uuid) }"></view>
</view>
@ -114,7 +114,9 @@
}
}
const onSearch = () => {}
const onSearch = () => {
patientList.value = patientList.value.filter(p => p.realName.indexOf(keyword.value) !== -1 || (p.nickname && p.nickname.indexOf(keyword.value) !== -1) || p.mobile.indexOf(keyword.value) !== -1)
}
const goBack = () => uni.navigateBack()
const confirmSelect = (id) => {
const payload =patientList.value.find(x => x.uuid === id)

View File

@ -0,0 +1,209 @@
<template>
<view class="content">
<uni-nav-bar
left-icon="left"
title="随访计划"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<text class="save-btn" @click="showAddMenu" >添加</text>
</view>
</template>
</uni-nav-bar>
<view class="plan">
<scroll-view
class="plan-scroll"
scroll-y="true"
refresher-enabled="true"
:refresher-triggered="followUpRefreshing"
@refresherrefresh="onFollowUpRefresh"
@scrolltolower="onFollowUpLoadMore"
:lower-threshold="100"
>
<view class="plan-list" v-if="groupedFollowUpList.length > 0">
<view class="plan-group" v-for="group in groupedFollowUpList" :key="group.yearMonth">
<view class="group-header">{{ group.yearMonth }}</view>
<view class="plan-card" v-for="item in group.items" :key="item.uuid" @click="goFollowDetail(item)">
<view class="left-rail">
<text class="day">{{ formatDay(item.datetime) }}</text>
</view>
<view class="linebox">
<up-image :src="lineImg" width="14rpx" height="140rpx" ></up-image>
</view>
<view class="right-content">
<view class="leftcontent">
<view class="note">{{ item.note }}</view>
<view class="name">{{ item.patientname }}</view>
</view>
<uni-icons type="forward" size="20" color="#999"></uni-icons>
</view>
</view>
</view>
<view class="load-more" v-if="followUpLoading">
<uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
<text class="load-text">加载中...</text>
</view>
<view class="no-more" v-if="!followUpHasMore && followUpList.length > 0">
<text class="no-more-text">没有更多数据了</text>
</view>
</view>
<empty v-else></empty>
</scroll-view>
<!-- <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>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import dayjs from 'dayjs'
import api from '@/api/api.js'
import navTo from '@/utils/navTo.js'
import dayImg from '@/static/visit_data11.png'
import planImg from '@/static/visitplan.png'
import lineImg from '@/static/item_visitplan_fg.png'
const formatYearMonth = (input) => {
if (!input) return ''
const d = dayjs(input)
return d.isValid() ? d.format('YYYY年MM月') : ''
}
const formatDay = (input) => {
if (!input) return ''
const d = dayjs(input)
return d.isValid() ? d.format('DD日') : ''
}
const followUpList = ref([])
const followUpLoading = ref(false)
const followUpRefreshing = ref(false)
const followUpHasMore = ref(true)
const followUpPageSize = ref(10)
const page = ref(1)
const groupedFollowUpList = computed(() => {
if (!followUpList.value || followUpList.value.length === 0) return []
const groups = new Map()
followUpList.value.forEach(item => {
const yearMonth = formatYearMonth(item.datetime)
if (!groups.has(yearMonth)) groups.set(yearMonth, [])
groups.get(yearMonth).push(item)
})
return Array.from(groups.entries()).map(([yearMonth, items]) => ({
yearMonth,
items: items.sort((a, b) => new Date(a.datetime) - new Date(b.datetime))
})).sort((a, b) => new Date(a.items[0].datetime) - new Date(b.items[0].datetime))
})
const getFollowUpList = async (isRefresh = false) => {
if (followUpLoading.value) return
followUpLoading.value = true
try {
const currentPage = isRefresh ? 1 : page.value
const res = await api.followUpList({ page: currentPage, pageSize: followUpPageSize.value })
if (res.code === 200) {
const newData = res.data.list || []
if (isRefresh) {
followUpList.value = newData
page.value = 1
} else {
followUpList.value = [...followUpList.value, ...newData]
}
followUpHasMore.value = newData.length >= followUpPageSize.value
if (!isRefresh) page.value++
}
} catch (e) {
uni.showToast({ title: '获取数据失败', icon: 'error' })
} finally {
followUpLoading.value = false
followUpRefreshing.value = false
}
}
const onFollowUpRefresh = async () => {
followUpRefreshing.value = true
await getFollowUpList(true)
uni.showToast({ title: '刷新成功', icon: 'none' })
}
const onFollowUpLoadMore = async () => {
if (!followUpHasMore.value || followUpLoading.value) return
await getFollowUpList(false)
}
const goBack = () => uni.navigateBack()
const showAddMenuFlag = ref(false)
const showAddMenu = () => { showAddMenuFlag.value = true }
const hideAddMenu = () => { showAddMenuFlag.value = false }
const addFollowUpPlan = () => { showAddMenuFlag.value = false; uni.navigateTo({ url: '/pages_app/visit/visit?from=visitPlan' }) }
const addSchedule = () => { showAddMenuFlag.value = false; uni.navigateTo({ url: '/pages_app/schedule/schedule' }) }
const goFollowDetail = (raw) => { if (!raw) return; navTo({ url: `/pages_app/followDetail/followDetail?followUpUuid=${encodeURIComponent(raw.uuid || '')}&patient_name=${raw.patientname}` }) }
onShow(() => {
followUpList.value = []
page.value = 1
followUpHasMore.value = true
followUpLoading.value = false
followUpRefreshing.value = false
getFollowUpList(true)
})
</script>
<style lang="scss" scoped>
.save-btn {
font-size: 32rpx;
color: #8b2316;
font-weight: 500;
}
.content { background-color: #f5f5f5; height: 100vh; overflow-y: hidden; }
.linebox{ margin:0 20rpx; }
.plan{ position: fixed; top: 140rpx; left: 0; right: 0; bottom:0rpx; }
.plan-scroll { height: 100%; }
.plan-list{ padding-bottom: 20rpx; }
.plan-group{ background:#f5f5f5; }
.group-header{ text-align: center; background:#e4e4e4; color:#333; font-size: 30rpx; padding: 20rpx 30rpx; }
.plan-card{ display:flex; align-items:center; background:#fff; padding: 26rpx 30rpx; border-bottom:1rpx solid #f0f0f0; }
.left-rail{ display:flex; align-items:center; color:#8B2316; }
.left-rail .day{ font-size: 36rpx; margin-right: 12rpx; }
.right-content{ flex:1; display:flex; align-items:center; justify-content:space-between; }
.right-content .note{ font-size: 30rpx; color:#333; }
.right-content .name{ margin-top: 30rpx; font-size: 28rpx; color:#8B2316; }
.load-more { display:flex; align-items:center; justify-content:center; padding:30rpx; color:#999; }
.load-more .load-text { margin-left:10rpx; font-size:28rpx; }
.no-more { display:flex; align-items:center; justify-content:center; padding:30rpx; }
.no-more .no-more-text{ font-size:28rpx; color:#999; }
.floating-add-btn { position: fixed; bottom: 140rpx; 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); }
.floating-add-btn .btn-text{ font-size:20rpx; color:#fff; 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:#fff; 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; }
.menu-item:last-child{ border-bottom:none; }
.menu-text{ font-size:32rpx; color:#333; margin-left:20rpx; }
.menu-divider{ height:1rpx; background:#f0f0f0; margin:0 40rpx; }
</style>

BIN
static/point_buy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB