Compare commits

..

2 Commits

Author SHA1 Message Date
f1f148d8a1 no message 2026-02-09 12:01:03 +08:00
00478235e7 no message 2026-02-09 11:52:40 +08:00
7 changed files with 738 additions and 160 deletions

10
.env.production Normal file
View File

@ -0,0 +1,10 @@
MP_API_BASE_URL=https://ytk.youcan365.com
MP_IMAGE_URL=https://ytk.youcan365.com
MP_CACHE_PREFIX=production
MP_WX_APP_ID=wx1d8337a40c11d66c
MP_CORP_ID=wpLgjyawAA8N0gWmXgyJq8wpjGcOT7fg
MP_TIM_SDK_APP_ID=1600123876
MP_INVITE_TEAMMATE_QRCODE=https://ykt.youcan365.com/invite-teammate
MP_INVITE_PATIENT_QRCODE=https://ykt.youcan365.com/invite-patient
MP_PATIENT_PAGE_BASE_URL= 'https://www.youcan365.com/h5/#/'
MP_SURVEY_URL= 'https://www.youcan365.com/surveyDev/#/pages/survey/survey'

View File

@ -0,0 +1,473 @@
<template>
<view class="medical-case-form">
<view class="form-container">
<!-- 动态渲染表单字段 -->
<view
v-for="field in currentFields"
:key="field.key"
class="form-item"
:class="{ required: field.required }"
>
<view class="item-label">{{ field.label }}</view>
<!-- 日期选择器 -->
<picker
v-if="field.type === 'date'"
mode="date"
:value="formData[field.key]"
@change="onDateChange(field.key, $event)"
:disabled="!isEditing"
>
<view class="picker-value">
{{ formData[field.key] || "暂无" }}
</view>
</picker>
<!-- 多行文本 -->
<textarea
v-else-if="field.type === 'textarea'"
class="item-textarea"
v-model="formData[field.key]"
placeholder="请输入"
:disabled="!isEditing"
/>
<!-- 单行文本 -->
<input
v-else
class="item-input"
v-model="formData[field.key]"
placeholder="暂无"
:disabled="!isEditing"
/>
</view>
</view>
<view class="footer-buttons">
<view class="btn-regenerate" @click="handleRegenerate">
<text class="btn-text">重新生成</text>
</view>
<view class="btn-save" @click="handleSave">
<text class="btn-text">保存至档案</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account";
import api from "@/utils/api.js";
const caseType = ref("");
const formData = ref({});
const isEditing = ref(true);
const customerId = ref("");
const groupId = ref("");
const accountStore = useAccountStore();
const { doctorInfo } = storeToRefs(accountStore);
//
const CASE_TYPE_NAMES = {
outpatient: "门诊病历",
inhospital: "住院病历",
physicalExaminationTemplate: "体检记录",
preConsultation: "预问诊记录",
};
//
const FIELD_LABELS = {
//
visitTime: "就诊日期",
chiefComplaint: "主诉",
medicalHistorySummary: "病史概要",
examination: "检查",
diagnosisName: "门诊诊断",
//
inhosDate: "入院日期",
operation: "手术记录",
operationDate: "手术日期",
treatmentPlan: "治疗方案",
//
inspectTime: "体检日期",
inspectSummary: "体检小结",
positiveFind: "阳性发现及处理意见",
//
presentIllnessHistory: "现病史",
pastMedicalHistory: "既往史",
};
//
const FIELD_CONFIG = {
outpatient: [
{
key: "visitTime",
label: FIELD_LABELS.visitTime,
type: "date",
required: false,
},
{
key: "diagnosisName",
label: FIELD_LABELS.diagnosisName,
type: "textarea",
required: false,
},
{
key: "chiefComplaint",
label: FIELD_LABELS.chiefComplaint,
type: "textarea",
required: false,
},
{
key: "medicalHistorySummary",
label: FIELD_LABELS.medicalHistorySummary,
type: "textarea",
required: false,
},
{
key: "examination",
label: FIELD_LABELS.examination,
type: "textarea",
required: false,
},
{
key: "treatmentPlan",
label: "治疗方案",
type: "textarea",
required: false,
},
],
inhospital: [
{
key: "inhosDate",
label: FIELD_LABELS.inhosDate,
type: "date",
required: false,
},
{
key: "diagnosisName",
label: "住院主诊断",
type: "textarea",
required: false,
},
{
key: "operation",
label: FIELD_LABELS.operation,
type: "textarea",
required: false,
},
{
key: "operationDate",
label: FIELD_LABELS.operationDate,
type: "date",
required: false,
},
{
key: "treatmentPlan",
label: FIELD_LABELS.treatmentPlan,
type: "textarea",
required: false,
},
{
key: "chiefComplaint",
label: FIELD_LABELS.chiefComplaint,
type: "textarea",
required: false,
},
{
key: "medicalHistorySummary",
label: FIELD_LABELS.medicalHistorySummary,
type: "textarea",
required: false,
},
{
key: "examination",
label: FIELD_LABELS.examination,
type: "textarea",
required: false,
},
{
key: "treatmentPlan",
label: "治疗方案",
type: "textarea",
required: false,
},
],
physicalExaminationTemplate: [
{
key: "inspectTime",
label: FIELD_LABELS.inspectTime,
type: "date",
required: false,
},
{
key: "inspectSummary",
label: FIELD_LABELS.inspectSummary,
type: "textarea",
required: false,
},
{
key: "positiveFind",
label: FIELD_LABELS.positiveFind,
type: "textarea",
required: false,
},
],
preConsultation: [
{
key: "chiefComplaint",
label: FIELD_LABELS.chiefComplaint,
type: "textarea",
required: false,
},
{
key: "presentIllnessHistory",
label: FIELD_LABELS.presentIllnessHistory,
type: "textarea",
required: false,
},
{
key: "pastMedicalHistory",
label: FIELD_LABELS.pastMedicalHistory,
type: "textarea",
required: false,
},
],
};
//
const currentFields = computed(() => {
return FIELD_CONFIG[caseType.value] || [];
});
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options;
caseType.value = options.caseType || "";
customerId.value = options.patientId || "";
groupId.value = options.groupId || "";
// options
if (options.formData) {
try {
formData.value = JSON.parse(decodeURIComponent(options.formData));
} catch (e) {
console.error("解析表单数据失败:", e);
}
}
//
const title = CASE_TYPE_NAMES[caseType.value]
? `添加${CASE_TYPE_NAMES[caseType.value]}`
: "添加病历";
uni.setNavigationBarTitle({ title });
});
const onDateChange = (field, event) => {
formData.value[field] = event.detail.value;
};
const handleRegenerate = () => {
uni.showModal({
title: "提示",
content: "确定要重新生成吗?当前编辑的内容将被覆盖",
success: (res) => {
if (res.confirm) {
//
uni.navigateBack({
success: () => {
uni.$emit("regenerateMedicalCase", {
caseType: caseType.value,
customerId: customerId.value,
groupId: groupId.value,
});
},
});
}
},
});
};
const handleSave = async () => {
//
const requiredFields = getRequiredFields();
const missingFields = requiredFields.filter(
(field) => !formData.value[field.key]
);
if (missingFields.length > 0) {
uni.showToast({
title: `请填写${missingFields[0].label}`,
icon: "none",
});
return;
}
try {
uni.showLoading({ title: "保存中..." });
const result = await api("addMedicalRecord", {
medicalType: caseType.value,
memberId: customerId.value,
creator: doctorInfo.value.userid,
...formData.value,
});
uni.hideLoading();
if (result.success) {
uni.showToast({
title: "保存成功",
icon: "success",
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: result.message || "保存失败",
icon: "none",
});
}
} catch (error) {
uni.hideLoading();
console.error("保存病历失败:", error);
uni.showToast({
title: "保存失败,请重试",
icon: "none",
});
}
};
const getRequiredFields = () => {
return currentFields.value.filter((field) => field.required);
};
</script>
<style scoped lang="scss">
.medical-case-form {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
.form-container {
background-color: #ffffff;
padding: 32rpx;
.form-item {
margin-bottom: 32rpx;
&.required .item-label::before {
content: "*";
color: #ff4d4f;
margin-right: 8rpx;
}
.item-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 16rpx;
}
.item-input,
.picker-value {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
font-size: 28rpx;
color: #333333;
&[disabled] {
color: #999999;
}
}
.picker-value {
display: flex;
align-items: center;
color: #999999;
}
.item-textarea {
width: 100%;
min-height: 100rpx;
padding: 20rpx 24rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
font-size: 28rpx;
color: #333333;
height: 100px;
&[disabled] {
color: #999999;
}
}
}
.tips-box {
margin-top: 32rpx;
padding: 24rpx;
background-color: #fffbe6;
border-radius: 8rpx;
.tips-text {
display: block;
font-size: 24rpx;
color: #666666;
line-height: 1.6;
margin-bottom: 8rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
gap: 24rpx;
padding: 24rpx 32rpx;
background-color: #ffffff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
.btn-regenerate,
.btn-save {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 44rpx;
.btn-text {
font-size: 32rpx;
font-weight: 500;
}
}
.btn-regenerate {
background-color: #ffffff;
border: 2rpx solid #1890ff;
.btn-text {
color: #1890ff;
}
}
.btn-save {
background-color: #1890ff;
.btn-text {
color: #ffffff;
}
}
}
}
</style>

View File

@ -348,7 +348,7 @@ const handleNextFromProgress = (data) => {
// //
uni.navigateTo({ uni.navigateTo({
url: `/pages/case/medical-case-form?caseType=${data.caseType}&patientId=${ url: `/pages/case/ai-medical-case-form?caseType=${data.caseType}&patientId=${
props.patientId props.patientId
}&groupId=${props.groupId}&formData=${encodeURIComponent( }&groupId=${props.groupId}&formData=${encodeURIComponent(
JSON.stringify(extractedData) JSON.stringify(extractedData)

View File

@ -769,6 +769,13 @@ onShow(() => {
checkLoginAndInitTIM(); checkLoginAndInitTIM();
} else if (timChatManager.tim && !timChatManager.isLoggedIn) { } else if (timChatManager.tim && !timChatManager.isLoggedIn) {
timChatManager.ensureIMConnection(); timChatManager.ensureIMConnection();
} else if (timChatManager.tim && timChatManager.isLoggedIn && chatInfo.value.conversationID) {
//
console.log("页面从后台返回,重新加载消息列表");
messageList.value = [];
isCompleted.value = false;
lastFirstMessageId.value = "";
loadMessageList();
} }
startIMMonitoring(30000); startIMMonitoring(30000);

View File

@ -67,10 +67,7 @@
<text class="empty-text">医生信息未获取请稍后重试</text> <text class="empty-text">医生信息未获取请稍后重试</text>
</view> </view>
<view <view
v-else-if=" v-else-if="!loading && filteredConversationList.length === 0"
!loading &&
filteredConversationList.length === 0
"
class="empty-container" class="empty-container"
> >
<image class="empty-image" src="/static/empty.svg" mode="aspectFit" /> <image class="empty-image" src="/static/empty.svg" mode="aspectFit" />
@ -165,12 +162,78 @@ const handleAddPatient = withInfo(() => {
}); });
}); });
//
const updateUnreadBadgeImmediately = async () => {
try {
if (!globalTimChatManager || !globalTimChatManager.tim) {
console.warn("TIM实例不存在无法更新徽章");
return;
}
const response = await globalTimChatManager.tim.getConversationList();
if (!response || !response.data || !response.data.conversationList) {
console.warn("获取会话列表返回数据异常");
return;
}
//
const totalUnreadCount = response.data.conversationList
.filter(
(conv) => conv.conversationID && conv.conversationID.startsWith("GROUP")
)
.reduce((sum, conv) => sum + (conv.unreadCount || 0), 0);
// tabBar - TabBar
try {
if (totalUnreadCount > 0) {
uni.setTabBarBadge({
index: 1,
text: totalUnreadCount > 99 ? "99+" : String(totalUnreadCount),
});
console.log("已更新 tabBar 徽章:", totalUnreadCount);
} else {
uni.removeTabBarBadge({
index: 1,
});
console.log("已移除 tabBar 徽章");
}
} catch (badgeError) {
// TabBar
if (badgeError.errMsg && badgeError.errMsg.includes("not TabBar page")) {
console.log("当前不是TabBar页面跳过徽章更新");
} else {
console.error("更新TabBar徽章失败:", badgeError);
}
}
} catch (error) {
console.error("更新未读徽章失败:", error);
}
};
// IM // IM
const initIM = async () => { const initIM = async () => {
if (!isIMInitialized.value) { // isIMInitialized
const needsInit =
!isIMInitialized.value ||
!globalTimChatManager ||
!globalTimChatManager.isLoggedIn;
if (needsInit) {
uni.showLoading({ uni.showLoading({
title: "连接中...", title: "连接中...",
}); });
//
if (
isIMInitialized.value &&
globalTimChatManager &&
!globalTimChatManager.isLoggedIn
) {
console.log("IM已初始化但连接已断开清理旧实例后重新初始化");
await globalTimChatManager.cleanupOldInstance();
}
const success = await initIMAfterLogin(); const success = await initIMAfterLogin();
uni.hideLoading(); uni.hideLoading();
@ -191,30 +254,6 @@ const initIM = async () => {
}); });
return false; return false;
} }
} else if (globalTimChatManager && !globalTimChatManager.isLoggedIn) {
uni.showLoading({
title: "重连中...",
});
const reconnected = await globalTimChatManager.ensureIMConnection();
uni.hideLoading();
if (!reconnected) {
//
uni.showModal({
title: "IM连接失败",
content:
"连接失败请检查网络后重试。如果IM连接失败请重新登陆IM再连接",
confirmText: "重新登陆",
cancelText: "取消",
success: (res) => {
if (res.confirm) {
//
handleReloginIM();
}
},
});
return false;
}
} }
return true; return true;
}; };
@ -281,7 +320,7 @@ const loadConversationList = async () => {
} }
} }
// // -
if (!globalTimChatManager.isLoggedIn) { if (!globalTimChatManager.isLoggedIn) {
console.warn("IM未登录尝试重新连接"); console.warn("IM未登录尝试重新连接");
const reconnected = await globalTimChatManager.ensureIMConnection(); const reconnected = await globalTimChatManager.ensureIMConnection();
@ -294,7 +333,19 @@ const loadConversationList = async () => {
throw new Error("IM管理器方法不可用"); throw new Error("IM管理器方法不可用");
} }
const result = await globalTimChatManager.getGroupList(); //
const timeoutPromise = new Promise((_, reject) => {
setTimeout(
() => reject(new Error("加载会话列表超时,请检查网络连接")),
35000
);
});
const result = await Promise.race([
globalTimChatManager.getGroupList(),
timeoutPromise,
]);
if (result && result.success && result.groupList) { if (result && result.success && result.groupList) {
// //
conversationList.value = await mergeConversationWithGroupDetails( conversationList.value = await mergeConversationWithGroupDetails(
@ -317,17 +368,26 @@ const loadConversationList = async () => {
); );
} else { } else {
console.error("加载群聊列表失败:", result); console.error("加载群聊列表失败:", result);
uni.showToast({ throw new Error(result?.message || "加载失败,请重试");
title: "加载失败,请重试",
icon: "none",
});
} }
} catch (error) { } catch (error) {
console.error("加载会话列表失败:", error); console.error("加载会话列表失败:", error);
//
if (
error.message &&
(error.message.includes("超时") || error.message.includes("连接"))
) {
uni.showToast({
title: "网络连接不稳定,请重试",
icon: "none",
});
} else {
uni.showToast({ uni.showToast({
title: error.message || "加载失败,请重试", title: error.message || "加载失败,请重试",
icon: "none", icon: "none",
}); });
}
} finally { } finally {
loading.value = false; loading.value = false;
} }
@ -605,7 +665,7 @@ onShow(async () => {
// //
await getTeams(); await getTeams();
// IM // IM - IM
const imReady = await initIM(); const imReady = await initIM();
if (!imReady) { if (!imReady) {
console.error("IM初始化失败"); console.error("IM初始化失败");

View File

@ -17,7 +17,8 @@
加入我的团队协同开展患者管理服务 加入我的团队协同开展患者管理服务
</view> </view>
<view class="mt-10 flex px-15 leading-normal text-center"> <view class="mt-10 flex px-15 leading-normal text-center">
<button class="mr-10 border-auto rounded py-5 text-base text-primary flex-grow" @click="saveImage('save')"> <button class="mr-10 border-auto rounded py-5 text-base text-primary flex-grow"
@click="saveImage('save')">
保存图片 保存图片
</button> </button>
<button class="bg-primary rounded py-5 text-base text-white flex-grow" open-type="share">分享微信</button> <button class="bg-primary rounded py-5 text-base text-white flex-grow" open-type="share">分享微信</button>
@ -29,39 +30,61 @@
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, ref } from "vue"; import {
import { storeToRefs } from "pinia"; computed,
import { onLoad, onShareAppMessage } from "@dcloudio/uni-app"; ref
import useGuard from "@/hooks/useGuard.js"; } from "vue";
import useAccountStore from "@/store/account.js"; import {
import api from '@/utils/api'; storeToRefs
import { toast } from "@/utils/widget"; } from "pinia";
import { getInviteMatePoster } from './base-poster-data'; import {
onLoad,
onShareAppMessage
} from "@dcloudio/uni-app";
import useGuard from "@/hooks/useGuard.js";
import useAccountStore from "@/store/account.js";
import api from '@/utils/api';
import {
toast
} from "@/utils/widget";
import {
getInviteMatePoster
} from './base-poster-data';
const env = __VITE_ENV__; const env = __VITE_ENV__;
const inviteQrcode = env.MP_INVITE_TEAMMATE_QRCODE; const inviteQrcode = env.MP_INVITE_TEAMMATE_QRCODE;
const options = { margin: 10 }; const options = {
const team = ref(null); margin: 10
const teamId = ref(''); };
const painterRef = ref() const team = ref(null);
const poster = ref({}) const teamId = ref('');
const painterRef = ref()
const poster = ref({})
const { useLoad, useShow } = useGuard(); const {
const { account } = storeToRefs(useAccountStore()); useLoad,
useShow
} = useGuard();
const {
account
} = storeToRefs(useAccountStore());
const qrcode = computed(() => `${inviteQrcode}?type=inviteTeam&teamId=${teamId.value}`) const qrcode = computed(() => `${inviteQrcode}?type=inviteTeam&teamId=${teamId.value}`)
async function getTeam() { async function getTeam() {
const res = await api('getTeamData', { teamId: teamId.value, corpId: account.value.corpId }); const res = await api('getTeamData', {
teamId: teamId.value,
corpId: account.value.corpId
});
if (res && res.data) { if (res && res.data) {
team.value = res.data; team.value = res.data;
} else { } else {
toast(res?.message || '获取团队信息失败') toast(res?.message || '获取团队信息失败')
} }
} }
async function saveImage(action = 'save') { async function saveImage(action = 'save') {
const data = getInviteMatePoster(team.value.name, qrcode.value) const data = getInviteMatePoster(team.value.name, qrcode.value)
try { try {
await painterRef.value.render(data); await painterRef.value.render(data);
@ -75,7 +98,7 @@ async function saveImage(action = 'save') {
if (action === 'save') { if (action === 'save') {
uni.saveImageToPhotosAlbum({ uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath, filePath: res.tempFilePath,
success: function () { success: function() {
console.log('save success'); console.log('save success');
} }
}); });
@ -90,29 +113,29 @@ async function saveImage(action = 'save') {
} catch (e) { } catch (e) {
toast(e?.message) toast(e?.message)
} }
} }
useLoad(options => { useLoad(options => {
teamId.value = options.teamId; teamId.value = options.teamId;
}) })
useShow(() => { useShow(() => {
getTeam() getTeam()
}); });
onShareAppMessage(() => { onShareAppMessage(() => {
return { return {
title: '邀请团队成员', title: '邀请团队成员',
path: `pages/login/redirect-page?type=inviteTeam&teamId=${teamId.value}` path: `pages/login/redirect-page?type=inviteTeam&teamId=${teamId.value}`
} }
}) })
</script> </script>
<style> <style>
.canvas-box { .canvas-box {
top: 10000rpx; top: 10000rpx;
position: absolute; position: absolute;
z-index: -1; z-index: -1;
width: 0; width: 0;
height: 0; height: 0;
overflow: hidden; overflow: hidden;
} }
</style> </style>

View File

@ -1032,6 +1032,8 @@ class TimChatManager {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 检查userId是否存在不存在则不需要初始化 // 检查userId是否存在不存在则不需要初始化
if (!this.currentUserID) { if (!this.currentUserID) {
console.error('currentUserID不存在无法获取群聊列表')
reject(new Error('用户ID不存在'))
return return
} }
@ -1076,12 +1078,13 @@ class TimChatManager {
if (timeoutHandle) clearTimeout(timeoutHandle) if (timeoutHandle) clearTimeout(timeoutHandle)
this.getGroupListInternal().then(resolve).catch(reject) this.getGroupListInternal().then(resolve).catch(reject)
} else if (waitTime >= maxWaitTime) { } else if (waitTime >= maxWaitTime) {
console.error('等待SDK就绪超时') console.error('等待SDK就绪超时当前isLoggedIn:', this.isLoggedIn)
if (timeoutHandle) clearTimeout(timeoutHandle) if (timeoutHandle) clearTimeout(timeoutHandle)
// 超时时返回错误而不是继续等待
reject(new Error('SDK初始化超时请检查网络连接')) reject(new Error('SDK初始化超时请检查网络连接'))
} else { } else {
waitTime += checkInterval waitTime += checkInterval
console.log(`等待SDK就绪... (${Math.floor(waitTime / 1000)}/${Math.floor(maxWaitTime / 1000)})`) console.log(`等待SDK就绪... (${Math.floor(waitTime / 1000)}/${Math.floor(maxWaitTime / 1000)}, isLoggedIn: ${this.isLoggedIn})`)
timeoutHandle = setTimeout(checkSDKReady, checkInterval) timeoutHandle = setTimeout(checkSDKReady, checkInterval)
} }
} }
@ -2758,6 +2761,7 @@ class TimChatManager {
// 标记会话为已读 // 标记会话为已读
markConversationAsRead(conversationID) { markConversationAsRead(conversationID) {
if (!this.tim || !this.isLoggedIn) { if (!this.tim || !this.isLoggedIn) {
console.log('⚠️ TIM未初始化或未登录无法标记会话已读'); console.log('⚠️ TIM未初始化或未登录无法标记会话已读');
return; return;
@ -2777,6 +2781,7 @@ class TimChatManager {
} }
} }
// 更新会话列表 // 更新会话列表
updateConversationListOnNewMessage(message) { updateConversationListOnNewMessage(message) {
try { try {