This commit is contained in:
huxuejian 2026-02-02 17:49:22 +08:00
commit 9cfa5a362a
13 changed files with 621 additions and 500 deletions

View File

@ -1,214 +1,48 @@
<template> <template>
<view class="medical-case-form"> <view class="medical-case-form">
<view class="form-container"> <view class="form-container">
<!-- 门诊病历 --> <!-- 动态渲染表单字段 -->
<template v-if="caseType === 'outpatient'"> <view
<view class="form-item required"> v-for="field in currentFields"
<view class="item-label">就诊机构</view> :key="field.key"
<input class="form-item"
class="item-input" :class="{ required: field.required }"
v-model="formData.hospital" >
placeholder="暂无" <view class="item-label">{{ field.label }}</view>
:disabled="!isEditing"
/> <!-- 日期选择器 -->
</view> <picker
v-if="field.type === 'date'"
<view class="form-item required"> mode="date"
<view class="item-label">就诊日期</view> :value="formData[field.key]"
<picker @change="onDateChange(field.key, $event)"
mode="date" :disabled="!isEditing"
:value="formData.visitTime" >
@change="onDateChange('visitTime', $event)" <view class="picker-value">
:disabled="!isEditing" {{ formData[field.key] || "暂无" }}
> </view>
<view class="picker-value"> </picker>
{{ formData.visitTime || '暂无' }}
</view> <!-- 多行文本 -->
</picker> <textarea
</view> v-else-if="field.type === 'textarea'"
class="item-textarea"
<view class="form-item required"> v-model="formData[field.key]"
<view class="item-label">门诊诊断</view> placeholder="请输入"
<textarea :disabled="!isEditing"
class="item-textarea" />
v-model="formData.diagnosisName"
placeholder="请输入" <!-- 单行文本 -->
:disabled="!isEditing" <input
/> v-else
</view> class="item-input"
v-model="formData[field.key]"
<view class="form-item"> placeholder="暂无"
<view class="item-label">治疗方案</view> :disabled="!isEditing"
<textarea />
class="item-textarea"
v-model="formData.treatmentPlan"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
</template>
<!-- 住院病历 -->
<template v-if="caseType === 'inpatient'">
<view class="form-item required">
<view class="item-label">就诊机构</view>
<input
class="item-input"
v-model="formData.hospital"
placeholder="暂无"
:disabled="!isEditing"
/>
</view>
<view class="form-item required">
<view class="item-label">入院日期</view>
<picker
mode="date"
:value="formData.inhosDate"
@change="onDateChange('inhosDate', $event)"
:disabled="!isEditing"
>
<view class="picker-value">
{{ formData.inhosDate || '暂无' }}
</view>
</picker>
</view>
<view class="form-item required">
<view class="item-label">住院主诊断</view>
<textarea
class="item-textarea"
v-model="formData.diagnosisName"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
<view class="form-item">
<view class="item-label">手术名称</view>
<textarea
class="item-textarea"
v-model="formData.operation"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
<view class="form-item">
<view class="item-label">手术日期</view>
<picker
mode="date"
:value="formData.operationDate"
@change="onDateChange('operationDate', $event)"
:disabled="!isEditing"
>
<view class="picker-value">
{{ formData.operationDate || '暂无' }}
</view>
</picker>
</view>
<view class="form-item">
<view class="item-label">治疗方案</view>
<textarea
class="item-textarea"
v-model="formData.treatmentPlan"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
</template>
<!-- 体检记录 -->
<template v-if="caseType === 'physicalExam'">
<view class="form-item required">
<view class="item-label">就诊机构</view>
<input
class="item-input"
v-model="formData.hospital"
placeholder="暂无"
:disabled="!isEditing"
/>
</view>
<view class="form-item required">
<view class="item-label">体检日期</view>
<picker
mode="date"
:value="formData.inspectTime"
@change="onDateChange('inspectTime', $event)"
:disabled="!isEditing"
>
<view class="picker-value">
{{ formData.inspectTime || '暂无' }}
</view>
</picker>
</view>
<view class="form-item">
<view class="item-label">体检小结</view>
<textarea
class="item-textarea"
v-model="formData.inspectSummary"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
<view class="form-item">
<view class="item-label">阳性发现及处理意见</view>
<textarea
class="item-textarea"
v-model="formData.positiveFind"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
</template>
<!-- 预问诊记录 -->
<template v-if="caseType === 'preConsultation'">
<view class="form-item">
<view class="item-label">主诉</view>
<textarea
class="item-textarea"
v-model="formData.chiefComplaint"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
<view class="form-item">
<view class="item-label">现病史</view>
<textarea
class="item-textarea"
v-model="formData.presentIllnessHistory"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
<view class="form-item">
<view class="item-label">既往史</view>
<textarea
class="item-textarea"
v-model="formData.pastMedicalHistory"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
</template>
<view class="tips-box">
<text class="tips-text">
1门诊住院病历记录生成生成后支持医生在线编辑并保存至档案或者重新生成
</text>
<text class="tips-text">
2若未来集到有效信息则以模板字段中默认项写无内容生成医生可以直接在存字段上进行编辑
</text>
</view> </view>
</view> </view>
<view class="footer-buttons"> <view class="footer-buttons">
<view class="btn-regenerate" @click="handleRegenerate"> <view class="btn-regenerate" @click="handleRegenerate">
<text class="btn-text">重新生成</text> <text class="btn-text">重新生成</text>
@ -221,42 +55,212 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, computed, onMounted } from "vue";
import { storeToRefs } from "pinia";
const caseType = ref(''); import useAccountStore from "@/store/account";
import api from "@/utils/api.js";
const caseType = ref("");
const formData = ref({}); const formData = ref({});
const isEditing = ref(true); const isEditing = ref(true);
const customerId = ref(''); const customerId = ref("");
const groupId = 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(() => { onMounted(() => {
const pages = getCurrentPages(); const pages = getCurrentPages();
const currentPage = pages[pages.length - 1]; const currentPage = pages[pages.length - 1];
const options = currentPage.options; const options = currentPage.options;
caseType.value = options.caseType || ''; caseType.value = options.caseType || "";
customerId.value = options.customerId || ''; customerId.value = options.patientId || "";
groupId.value = options.groupId || ''; groupId.value = options.groupId || "";
// options // options
if (options.formData) { if (options.formData) {
try { try {
formData.value = JSON.parse(decodeURIComponent(options.formData)); formData.value = JSON.parse(decodeURIComponent(options.formData));
} catch (e) { } catch (e) {
console.error('解析表单数据失败:', e); console.error("解析表单数据失败:", e);
} }
} }
// //
const titles = { const title = CASE_TYPE_NAMES[caseType.value]
outpatient: '添加门诊病历', ? `添加${CASE_TYPE_NAMES[caseType.value]}`
inpatient: '添加住院病历', : "添加病历";
physicalExam: '添加体检记录', uni.setNavigationBarTitle({ title });
preConsultation: '添加预问诊记录'
};
uni.setNavigationBarTitle({
title: titles[caseType.value] || '添加病历'
});
}); });
const onDateChange = (field, event) => { const onDateChange = (field, event) => {
@ -265,96 +269,78 @@ const onDateChange = (field, event) => {
const handleRegenerate = () => { const handleRegenerate = () => {
uni.showModal({ uni.showModal({
title: '提示', title: "提示",
content: '确定要重新生成吗?当前编辑的内容将被覆盖', content: "确定要重新生成吗?当前编辑的内容将被覆盖",
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
// //
uni.navigateBack({ uni.navigateBack({
success: () => { success: () => {
uni.$emit('regenerateMedicalCase', { uni.$emit("regenerateMedicalCase", {
caseType: caseType.value, caseType: caseType.value,
customerId: customerId.value, customerId: customerId.value,
groupId: groupId.value groupId: groupId.value,
}); });
} },
}); });
} }
} },
}); });
}; };
const handleSave = async () => { const handleSave = async () => {
// //
const requiredFields = getRequiredFields(); const requiredFields = getRequiredFields();
const missingFields = requiredFields.filter(field => !formData.value[field.key]); const missingFields = requiredFields.filter(
(field) => !formData.value[field.key]
);
if (missingFields.length > 0) { if (missingFields.length > 0) {
uni.showToast({ uni.showToast({
title: `请填写${missingFields[0].label}`, title: `请填写${missingFields[0].label}`,
icon: 'none' icon: "none",
}); });
return; return;
} }
try { try {
uni.showLoading({ title: '保存中...' }); uni.showLoading({ title: "保存中..." });
const result = await api("addMedicalRecord", {
// medicalType: caseType.value,
const api = (await import('@/utils/api.js')).default; memberId: customerId.value,
const result = await api('addMedicalRecord', { creator: doctorInfo.value.userid,
customerId: customerId.value, ...formData.value,
caseType: caseType.value,
...formData.value
}); });
uni.hideLoading(); uni.hideLoading();
if (result.success) { if (result.success) {
uni.showToast({ uni.showToast({
title: '保存成功', title: "保存成功",
icon: 'success' icon: "success",
}); });
setTimeout(() => { setTimeout(() => {
uni.navigateBack(); uni.navigateBack();
}, 1500); }, 1500);
} else { } else {
uni.showToast({ uni.showToast({
title: result.message || '保存失败', title: result.message || "保存失败",
icon: 'none' icon: "none",
}); });
} }
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
console.error('保存病历失败:', error); console.error("保存病历失败:", error);
uni.showToast({ uni.showToast({
title: '保存失败,请重试', title: "保存失败,请重试",
icon: 'none' icon: "none",
}); });
} }
}; };
const getRequiredFields = () => { const getRequiredFields = () => {
const fieldsMap = { return currentFields.value.filter((field) => field.required);
outpatient: [
{ key: 'hospital', label: '就诊机构' },
{ key: 'visitTime', label: '就诊日期' },
{ key: 'diagnosisName', label: '门诊诊断' }
],
inpatient: [
{ key: 'hospital', label: '就诊机构' },
{ key: 'inhosDate', label: '入院日期' },
{ key: 'diagnosisName', label: '住院主诊断' }
],
physicalExam: [
{ key: 'hospital', label: '就诊机构' },
{ key: 'inspectTime', label: '体检日期' }
],
preConsultation: []
};
return fieldsMap[caseType.value] || [];
}; };
</script> </script>
@ -363,26 +349,26 @@ const getRequiredFields = () => {
min-height: 100vh; min-height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;
padding-bottom: 120rpx; padding-bottom: 120rpx;
.form-container { .form-container {
background-color: #ffffff; background-color: #ffffff;
padding: 32rpx; padding: 32rpx;
.form-item { .form-item {
margin-bottom: 32rpx; margin-bottom: 32rpx;
&.required .item-label::before { &.required .item-label::before {
content: '*'; content: "*";
color: #ff4d4f; color: #ff4d4f;
margin-right: 8rpx; margin-right: 8rpx;
} }
.item-label { .item-label {
font-size: 28rpx; font-size: 28rpx;
color: #333333; color: #333333;
margin-bottom: 16rpx; margin-bottom: 16rpx;
} }
.item-input, .item-input,
.picker-value { .picker-value {
width: 100%; width: 100%;
@ -392,64 +378,65 @@ const getRequiredFields = () => {
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 28rpx;
color: #333333; color: #333333;
&[disabled] { &[disabled] {
color: #999999; color: #999999;
} }
} }
.picker-value { .picker-value {
display: flex; display: flex;
align-items: center; align-items: center;
color: #999999; color: #999999;
} }
.item-textarea { .item-textarea {
width: 100%; width: 100%;
min-height: 160rpx; min-height: 100rpx;
padding: 20rpx 24rpx; padding: 20rpx 24rpx;
background-color: #f8f9fa; background-color: #f8f9fa;
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 28rpx;
color: #333333; color: #333333;
height: 100px;
&[disabled] { &[disabled] {
color: #999999; color: #999999;
} }
} }
} }
.tips-box { .tips-box {
margin-top: 32rpx; margin-top: 32rpx;
padding: 24rpx; padding: 24rpx;
background-color: #fffbe6; background-color: #fffbe6;
border-radius: 8rpx; border-radius: 8rpx;
.tips-text { .tips-text {
display: block; display: block;
font-size: 24rpx; font-size: 24rpx;
color: #666666; color: #666666;
line-height: 1.6; line-height: 1.6;
margin-bottom: 8rpx; margin-bottom: 8rpx;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
} }
} }
} }
.footer-buttons { .footer-buttons {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 100;
display: flex; display: flex;
gap: 24rpx; gap: 24rpx;
padding: 24rpx 32rpx; padding: 24rpx 32rpx;
background-color: #ffffff; background-color: #ffffff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08); box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
.btn-regenerate, .btn-regenerate,
.btn-save { .btn-save {
flex: 1; flex: 1;
@ -458,25 +445,25 @@ const getRequiredFields = () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 44rpx; border-radius: 44rpx;
.btn-text { .btn-text {
font-size: 32rpx; font-size: 32rpx;
font-weight: 500; font-weight: 500;
} }
} }
.btn-regenerate { .btn-regenerate {
background-color: #ffffff; background-color: #ffffff;
border: 2rpx solid #1890ff; border: 2rpx solid #1890ff;
.btn-text { .btn-text {
color: #1890ff; color: #1890ff;
} }
} }
.btn-save { .btn-save {
background-color: #1890ff; background-color: #1890ff;
.btn-text { .btn-text {
color: #ffffff; color: #ffffff;
} }

View File

@ -44,6 +44,10 @@ const props = defineProps({
type: String, type: String,
default: "", default: "",
}, },
patientId: {
type: String,
default: "",
},
corpId: { corpId: {
type: String, type: String,
default: "", default: "",
@ -333,12 +337,15 @@ const handleRegenerateFromProgress = (data) => {
// //
const handleNextFromProgress = (data) => { const handleNextFromProgress = (data) => {
//
const extractedData = data.data?.extractedData || {};
// //
uni.navigateTo({ uni.navigateTo({
url: `/pages/case/medical-case-form?caseType=${data.caseType}&customerId=${ url: `/pages/case/medical-case-form?caseType=${data.caseType}&patientId=${
props.customerId || props.patientAccountId props.patientId
}&groupId=${props.groupId}&formData=${encodeURIComponent( }&groupId=${props.groupId}&formData=${encodeURIComponent(
JSON.stringify(data.data?.extractedData || {}) JSON.stringify(extractedData)
)}`, )}`,
}); });
}; };

View File

@ -4,29 +4,32 @@
<view class="close-btn" @click="close"> <view class="close-btn" @click="close">
<text class="close-icon"></text> <text class="close-icon"></text>
</view> </view>
<view class="progress-content"> <view class="progress-content">
<view class="progress-title">{{ progressTitle }}</view> <view class="progress-title">{{ progressTitle }}</view>
<view class="progress-bar-wrapper"> <view class="progress-bar-wrapper">
<view class="progress-bar"> <view class="progress-bar">
<view class="progress-fill" :style="{ width: progress + '%' }"></view> <view
class="progress-fill"
:style="{ width: progress + '%' }"
></view>
</view> </view>
<text class="progress-text">{{ progress }}%</text> <text class="progress-text">{{ progress }}%</text>
</view> </view>
<view class="detected-info"> <view class="detected-info">
<text class="detected-title">检测到以下{{ caseTypeName }}信息</text> <text class="detected-title">检测到以下{{ caseTypeName }}信息</text>
<view class="info-list"> <view class="info-list">
<view <view
v-for="(item, index) in detectedInfo" v-for="(item, index) in detectedInfo"
:key="index" :key="index"
class="info-item" class="info-item"
:class="{ 'fade-in': item.animated }" :class="{ 'fade-in': item.animated }"
> >
<text class="check-icon"></text> <text class="check-icon"></text>
<text <text
class="info-text" class="info-text"
:class="{ 'empty-value': item.value === '暂无' }" :class="{ 'empty-value': item.value === '暂无' }"
> >
{{ item.label }}{{ item.value }} {{ item.label }}{{ item.value }}
@ -34,11 +37,11 @@
</view> </view>
</view> </view>
</view> </view>
<view v-if="isGenerating" class="generating-text"> <view v-if="isGenerating" class="generating-text">
<text class="dot-animation">正在生成结构化{{ caseTypeName }}</text> <text class="dot-animation">正在生成结构化{{ caseTypeName }}</text>
</view> </view>
<!-- 完成后的操作按钮 --> <!-- 完成后的操作按钮 -->
<view v-if="isCompleted" class="action-buttons"> <view v-if="isCompleted" class="action-buttons">
<view class="action-button secondary" @click="handleRegenerate"> <view class="action-button secondary" @click="handleRegenerate">
@ -54,48 +57,47 @@
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from "vue";
const emit = defineEmits(['regenerate', 'next']); const emit = defineEmits(["regenerate", "next"]);
const popup = ref(null); const popup = ref(null);
const progress = ref(0); const progress = ref(0);
const detectedInfo = ref([]); const detectedInfo = ref([]);
const isGenerating = ref(false); const isGenerating = ref(false);
const isCompleted = ref(false); const isCompleted = ref(false);
const caseType = ref(''); const caseType = ref("");
const finalData = ref(null); const finalData = ref(null);
const CASE_TYPE_NAMES = { const CASE_TYPE_NAMES = {
outpatient: '门诊病历', outpatient: "门诊病历",
inpatient: '住院病历', inhospital: "住院病历",
physicalExam: '体检记录', physicalExaminationTemplate: "体检记录",
preConsultation: '预问诊记录' preConsultation: "预问诊记录",
}; };
const FIELD_LABELS = { const FIELD_LABELS = {
// //
visitTime: '就诊日期', visitTime: "就诊日期",
chiefComplaint: '主诉', chiefComplaint: "主诉",
medicalHistorySummary: '病史概要', medicalHistorySummary: "病史概要",
examination: '检查', examination: "检查",
diagnosisName: '门诊诊断', diagnosisName: "门诊诊断",
// //
inhosDate: '入院日期', inhosDate: "入院日期",
operation: '手术记录', operation: "手术名称",
operationDate: '手术日期', operationDate: "手术日期",
treatmentPlan: '术后病程', treatmentPlan: "治疗方案",
outhosAdvice: '出院医嘱',
// //
inspectTime: '体检日期', inspectTime: "体检日期",
inspectSummary: '体检小结', inspectSummary: "体检小结",
positiveFind: '阳性发现及处理意见', positiveFind: "阳性发现及处理意见",
// //
presentIllnessHistory: '现病史', presentIllnessHistory: "现病史",
pastMedicalHistory: '既往史' pastMedicalHistory: "既往史",
}; };
const caseTypeName = computed(() => CASE_TYPE_NAMES[caseType.value] || '病历'); const caseTypeName = computed(() => CASE_TYPE_NAMES[caseType.value] || "病历");
const progressTitle = computed(() => { const progressTitle = computed(() => {
if (progress.value < 100) { if (progress.value < 100) {
@ -125,11 +127,11 @@ const updateProgress = (value) => {
const addDetectedInfo = (fieldKey, fieldValue) => { const addDetectedInfo = (fieldKey, fieldValue) => {
const label = FIELD_LABELS[fieldKey] || fieldKey; const label = FIELD_LABELS[fieldKey] || fieldKey;
// "" // ""
const displayValue = (fieldValue && fieldValue.trim()) ? fieldValue : '暂无'; const displayValue = fieldValue && fieldValue.trim() ? fieldValue : "暂无";
detectedInfo.value.push({ detectedInfo.value.push({
label, label,
value: displayValue, value: displayValue,
animated: true animated: true,
}); });
}; };
@ -151,14 +153,14 @@ const reset = () => {
}; };
const handleRegenerate = () => { const handleRegenerate = () => {
emit('regenerate', { caseType: caseType.value }); emit("regenerate", { caseType: caseType.value });
close(); close();
}; };
const handleNext = () => { const handleNext = () => {
emit('next', { emit("next", {
caseType: caseType.value, caseType: caseType.value,
data: finalData.value data: finalData.value,
}); });
close(); close();
}; };
@ -170,7 +172,7 @@ defineExpose({
addDetectedInfo, addDetectedInfo,
setGenerating, setGenerating,
setCompleted, setCompleted,
reset reset,
}); });
</script> </script>
@ -183,7 +185,7 @@ defineExpose({
position: relative; position: relative;
max-height: 80vh; max-height: 80vh;
overflow-y: auto; overflow-y: auto;
.close-btn { .close-btn {
position: absolute; position: absolute;
top: 20rpx; top: 20rpx;
@ -194,13 +196,13 @@ defineExpose({
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 10; z-index: 10;
.close-icon { .close-icon {
font-size: 40rpx; font-size: 40rpx;
color: #999999; color: #999999;
} }
} }
.progress-content { .progress-content {
.progress-title { .progress-title {
font-size: 32rpx; font-size: 32rpx;
@ -209,27 +211,27 @@ defineExpose({
margin-bottom: 32rpx; margin-bottom: 32rpx;
text-align: center; text-align: center;
} }
.progress-bar-wrapper { .progress-bar-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16rpx; gap: 16rpx;
margin-bottom: 32rpx; margin-bottom: 32rpx;
.progress-bar { .progress-bar {
flex: 1; flex: 1;
height: 16rpx; height: 16rpx;
background-color: #e5e5e5; background-color: #e5e5e5;
border-radius: 8rpx; border-radius: 8rpx;
overflow: hidden; overflow: hidden;
.progress-fill { .progress-fill {
height: 100%; height: 100%;
background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%); background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%);
transition: width 0.5s ease; transition: width 0.5s ease;
} }
} }
.progress-text { .progress-text {
font-size: 28rpx; font-size: 28rpx;
color: #1890ff; color: #1890ff;
@ -238,21 +240,21 @@ defineExpose({
text-align: right; text-align: right;
} }
} }
.detected-info { .detected-info {
margin-bottom: 24rpx; margin-bottom: 24rpx;
.detected-title { .detected-title {
font-size: 28rpx; font-size: 28rpx;
color: #666666; color: #666666;
display: block; display: block;
margin-bottom: 16rpx; margin-bottom: 16rpx;
} }
.info-list { .info-list {
max-height: 400rpx; max-height: 400rpx;
overflow-y: auto; overflow-y: auto;
.info-item { .info-item {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@ -260,25 +262,25 @@ defineExpose({
margin-bottom: 12rpx; margin-bottom: 12rpx;
opacity: 0; opacity: 0;
transform: translateX(-20rpx); transform: translateX(-20rpx);
&.fade-in { &.fade-in {
animation: fadeInSlide 0.4s ease forwards; animation: fadeInSlide 0.4s ease forwards;
} }
.check-icon { .check-icon {
font-size: 28rpx; font-size: 28rpx;
color: #52c41a; color: #52c41a;
font-weight: bold; font-weight: bold;
margin-top: 2rpx; margin-top: 2rpx;
} }
.info-text { .info-text {
flex: 1; flex: 1;
font-size: 26rpx; font-size: 26rpx;
color: #333333; color: #333333;
line-height: 1.6; line-height: 1.6;
word-break: break-all; word-break: break-all;
// //
&.empty-value { &.empty-value {
color: #999999; color: #999999;
@ -287,24 +289,24 @@ defineExpose({
} }
} }
} }
.generating-text { .generating-text {
font-size: 28rpx; font-size: 28rpx;
color: #1890ff; color: #1890ff;
text-align: center; text-align: center;
padding: 16rpx 0; padding: 16rpx 0;
.dot-animation::after { .dot-animation::after {
content: '...'; content: "...";
animation: dots 1.5s steps(4, end) infinite; animation: dots 1.5s steps(4, end) infinite;
} }
} }
.action-buttons { .action-buttons {
display: flex; display: flex;
gap: 16rpx; gap: 16rpx;
margin-top: 32rpx; margin-top: 32rpx;
.action-button { .action-button {
flex: 1; flex: 1;
height: 80rpx; height: 80rpx;
@ -313,34 +315,34 @@ defineExpose({
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.3s ease; transition: all 0.3s ease;
&.primary { &.primary {
background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%); background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%);
.button-text { .button-text {
color: #ffffff; color: #ffffff;
} }
&:active { &:active {
opacity: 0.8; opacity: 0.8;
transform: scale(0.98); transform: scale(0.98);
} }
} }
&.secondary { &.secondary {
background-color: #ffffff; background-color: #ffffff;
border: 2rpx solid #d9d9d9; border: 2rpx solid #d9d9d9;
.button-text { .button-text {
color: #666666; color: #666666;
} }
&:active { &:active {
background-color: #f5f5f5; background-color: #f5f5f5;
transform: scale(0.98); transform: scale(0.98);
} }
} }
.button-text { .button-text {
font-size: 30rpx; font-size: 30rpx;
font-weight: 500; font-weight: 500;
@ -358,17 +360,19 @@ defineExpose({
} }
@keyframes dots { @keyframes dots {
0%, 20% { 0%,
content: ''; 20% {
content: "";
} }
40% { 40% {
content: '.'; content: ".";
} }
60% { 60% {
content: '..'; content: "..";
} }
80%, 100% { 80%,
content: '...'; 100% {
content: "...";
} }
} }
</style> </style>

View File

@ -7,7 +7,7 @@
<text class="close-icon"></text> <text class="close-icon"></text>
</view> </view>
</view> </view>
<view class="case-type-grid"> <view class="case-type-grid">
<view <view
v-for="type in caseTypes" v-for="type in caseTypes"
@ -23,29 +23,29 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from "vue";
const emit = defineEmits(['select']); const emit = defineEmits(["select"]);
const popup = ref(null); const popup = ref(null);
const caseTypes = [ const caseTypes = [
{ {
id: 'outpatient', id: "outpatient",
name: '门诊病历' name: "门诊病历",
}, },
{ {
id: 'inpatient', id: "inhospital",
name: '住院病历' name: "住院病历",
}, },
{ {
id: 'physicalExam', id: "physicalExaminationTemplate",
name: '体检记录' name: "体检记录",
}, },
{ {
id: 'preConsultation', id: "preConsultation",
name: '预问诊记录' name: "预问诊记录",
} },
]; ];
const open = () => { const open = () => {
@ -57,13 +57,13 @@ const close = () => {
}; };
const selectType = (type) => { const selectType = (type) => {
emit('select', type); emit("select", type);
close(); close();
}; };
defineExpose({ defineExpose({
open, open,
close close,
}); });
</script> </script>
@ -73,38 +73,38 @@ defineExpose({
background-color: #ffffff; background-color: #ffffff;
border-radius: 24rpx; border-radius: 24rpx;
padding: 40rpx; padding: 40rpx;
.selector-header { .selector-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 40rpx; margin-bottom: 40rpx;
.header-title { .header-title {
font-size: 36rpx; font-size: 36rpx;
font-weight: 600; font-weight: 600;
color: #333333; color: #333333;
} }
.close-btn { .close-btn {
width: 48rpx; width: 48rpx;
height: 48rpx; height: 48rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.close-icon { .close-icon {
font-size: 40rpx; font-size: 40rpx;
color: #999999; color: #999999;
} }
} }
} }
.case-type-grid { .case-type-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 24rpx; gap: 24rpx;
.case-type-item { .case-type-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -115,13 +115,13 @@ defineExpose({
border: 2rpx solid #e5e5e5; border: 2rpx solid #e5e5e5;
border-radius: 16rpx; border-radius: 16rpx;
transition: all 0.3s ease; transition: all 0.3s ease;
&:active { &:active {
background-color: #e8f4ff; background-color: #e8f4ff;
border-color: #1890ff; border-color: #1890ff;
transform: scale(0.98); transform: scale(0.98);
} }
.type-name { .type-name {
font-size: 30rpx; font-size: 30rpx;
color: #333333; color: #333333;

View File

@ -1,5 +1,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app' import { onShow, onUnload } from '@dcloudio/uni-app'
import api from '@/utils/api.js'
import useTeamStore from '@/store/team.js'
/** /**
* 简单的群聊hook * 简单的群聊hook
@ -8,6 +10,9 @@ import { onShow, onUnload } from '@dcloudio/uni-app'
export default function useGroupChat(groupID) { export default function useGroupChat(groupID) {
const groupInfo = ref({}) const groupInfo = ref({})
const members = ref([]) const members = ref([])
const teamMemberIds = ref([]) // 存储团队成员的userId列表
const patientId = ref('') // 存储患者ID
const teamStore = useTeamStore()
// 群聊成员映射 // 群聊成员映射
const chatMember = computed(() => { const chatMember = computed(() => {
@ -15,30 +20,79 @@ export default function useGroupChat(groupID) {
members.value.forEach(member => { members.value.forEach(member => {
res[member.id] = { res[member.id] = {
name: member.name, name: member.name,
avatar: member.avatar || '/static/default-avatar.png' avatar: member.avatar,
isTeamMember: member.isTeamMember // 标记是否为团队成员
} }
}) })
return res return res
}) })
// 获取群聊信息 // 判断某个userId是否为团队成员
const isTeamMember = (userId) => {
return teamMemberIds.value.includes(userId)
}
// 获取用户头像(根据是否为团队成员返回不同的默认头像)
const getUserAvatar = (userId) => {
const member = chatMember.value[userId]
if (!member) {
// 如果找不到成员信息,根据是否为团队成员返回默认头像
return isTeamMember(userId) ? '/static/home/avatar.svg' : '/static/default-patient-avatar.png'
}
// 如果有头像且不为空字符串,返回头像
if (member.avatar && member.avatar.trim() !== '') {
return member.avatar
}
// 否则根据是否为团队成员返回默认头像
return member.isTeamMember ? '/static/home/avatar.svg' : '/static/default-patient-avatar.png'
}
// 获取群聊信息和成员头像
async function getGroupInfo() { async function getGroupInfo() {
const gid = typeof groupID === 'string' ? groupID : groupID.value const gid = typeof groupID === 'string' ? groupID : groupID.value
if (!gid) return if (!gid) return
try { try {
// 这里可以调用API获取群聊信息 // 1. 获取群聊基本信息
// const res = await getGroupDetail(gid) const groupResult = await api('getGroupListByGroupId', { groupId: gid })
// if (res && res.success) {
// groupInfo.value = res.data
// members.value = res.data.members || []
// }
// 暂时使用本地数据 if (groupResult && groupResult.success && groupResult.data) {
groupInfo.value = { groupInfo.value = {
groupID: gid, groupID: gid,
name: '群聊', name: groupResult.data.team?.name || '群聊',
status: 'active' status: groupResult.data.orderStatus || 'active',
teamId: groupResult.data.teamId
}
// 2. 如果有teamId获取团队成员头像
if (groupResult.data.teamId) {
const avatarMap = await teamStore.getTeamMemberAvatars(groupResult.data.teamId)
// 3. 存储团队成员ID列表
teamMemberIds.value = Object.keys(avatarMap)
// 4. 构建团队成员列表
members.value = teamMemberIds.value.map(userId => ({
id: userId,
name: userId, // 这里可以从其他地方获取真实姓名
avatar: avatarMap[userId] || '',
isTeamMember: true
}))
// 5. 添加患者信息(使用默认患者头像)
if (groupResult.data.patient) {
const pid = groupResult.data.patientId?.toString() || ''
patientId.value = pid
members.value.push({
id: pid,
name: groupResult.data.patient.name || '患者',
avatar: '', // 患者不设置头像,使用默认
isTeamMember: false
})
}
}
} }
} catch (error) { } catch (error) {
console.error('获取群聊信息失败:', error) console.error('获取群聊信息失败:', error)
@ -57,6 +111,8 @@ export default function useGroupChat(groupID) {
groupInfo, groupInfo,
members, members,
chatMember, chatMember,
getGroupInfo getGroupInfo,
isTeamMember,
getUserAvatar
} }
} }

View File

@ -65,23 +65,16 @@
<!-- 消息内容 --> <!-- 消息内容 -->
<view v-else class="message-content"> <view v-else class="message-content">
<!-- 医生头像左侧 -->
<image <image
v-if="message.flow === 'in'" v-if="message.flow === 'in'"
class="doctor-msg-avatar" class="doctor-msg-avatar"
:src=" :src="getUserAvatar(message.from)"
chatMember[message.from]?.avatar || '/static/default-avatar.png'
"
mode="aspectFill" mode="aspectFill"
/> />
<!-- 患者头像右侧 -->
<image <image
v-if="message.flow === 'out'" v-if="message.flow === 'out'"
class="user-msg-avatar" class="user-msg-avatar"
:src=" :src="getUserAvatar(message.from)"
chatMember[message.from]?.avatar || '/static/home/avatar.svg'
"
mode="aspectFill" mode="aspectFill"
/> />
@ -149,6 +142,7 @@
" "
:groupId="groupId" :groupId="groupId"
:patientAccountId="chatInfo.userID || ''" :patientAccountId="chatInfo.userID || ''"
:patientId="patientId"
:corpId="corpId" :corpId="corpId"
@streamText="handleStreamText" @streamText="handleStreamText"
@clearInput="handleClearInput" @clearInput="handleClearInput"
@ -221,7 +215,7 @@ const { initIMAfterLogin } = useAccountStore();
const chatInputRef = ref(null); const chatInputRef = ref(null);
const groupId = ref(""); const groupId = ref("");
const { chatMember, getGroupInfo } = useGroupChat(groupId); const { chatMember, getGroupInfo, getUserAvatar } = useGroupChat(groupId);
// //
const updateNavigationTitle = (title = "群聊") => { const updateNavigationTitle = (title = "群聊") => {
@ -1048,4 +1042,4 @@ uni.$on("sendSurvey", async (data) => {
<style scoped lang="scss"> <style scoped lang="scss">
@import "./chat.scss"; @import "./chat.scss";
</style> </style>

View File

@ -2,23 +2,59 @@
<full-page> <full-page>
<view class="p-15"> <view class="p-15">
<view class="bg-white px-10 mb-10 rounded"> <view class="bg-white px-10 mb-10 rounded">
<form-input :form="formData" :required="rule.anotherName.required" wordLimit="10" title="anotherName" <form-input
:name="rule.anotherName.name" @change="onChange($event)" /> :form="formData"
:required="rule.anotherName.required"
wordLimit="10"
title="anotherName"
:name="rule.anotherName.name"
@change="onChange($event)"
/>
<common-cell title="avatar" name="头像"> <common-cell title="avatar" name="头像">
<view class="flex-grow flex items-center justify-end" @click="chooseAvatar()"> <view
<image v-if="formData.avatar" class="avatar mr-5 rounded-full" :src="formData.avatar" /> class="flex-grow flex items-center justify-end"
<image v-else class="avatar mr-5 rounded-full" src="/static/default-avatar.png" /> @click="chooseAvatar()"
>
<image
v-if="formData.avatar"
class="avatar mr-5 rounded-full"
:src="formData.avatar"
/>
<image
v-else
class="avatar mr-5 rounded-full"
src="/static/home/avatar.svg"
/>
<uni-icons color="#999" type="right" size="16" /> <uni-icons color="#999" type="right" size="16" />
</view> </view>
</common-cell> </common-cell>
<form-select :form="formData" name="性别" title="gender" :range="genderOptions" @change="onChange($event)" /> <form-select
<form-input :form="formData" disableChange wordLimit="11" title="mobile" name="手机号 (不可修改)" /> :form="formData"
name="性别"
title="gender"
:range="genderOptions"
@change="onChange($event)"
/>
<form-input
:form="formData"
disableChange
wordLimit="11"
title="mobile"
name="手机号 (不可修改)"
/>
</view> </view>
<view class="bg-white px-10 mb-10 rounded"> <view class="bg-white px-10 mb-10 rounded">
<!-- 填写认证资料的时候岗位必填 --> <!-- 填写认证资料的时候岗位必填 -->
<common-cell :required="type === 'cert'" title="job" :name="rule.job.name"> <common-cell
<view class="flex-grow flex items-center justify-end" @click="selectJob()"> :required="type === 'cert'"
title="job"
:name="rule.job.name"
>
<view
class="flex-grow flex items-center justify-end"
@click="selectJob()"
>
<view v-if="jobStr" class="text-base text-base">{{ jobStr }}</view> <view v-if="jobStr" class="text-base text-base">{{ jobStr }}</view>
<!-- <view class="mr-5 rounded-full" style="width: 64rpx;height: 64rpx;background: red;"></view> --> <!-- <view class="mr-5 rounded-full" style="width: 64rpx;height: 64rpx;background: red;"></view> -->
<uni-icons color="#999" type="right" size="16" /> <uni-icons color="#999" type="right" size="16" />
@ -39,12 +75,24 @@
</view> </view>
<view class="bg-white rounded"> <view class="bg-white rounded">
<form-textarea autoHeight :border="false" :form="formData" title="intro" name="个人介绍" :wordLimit="300" <form-textarea
@change="onChange($event)" /> autoHeight
:border="false"
:form="formData"
title="intro"
name="个人介绍"
:wordLimit="300"
@change="onChange($event)"
/>
</view> </view>
</view> </view>
<template #footer> <template #footer>
<button-footer :cancelText="cancelText" :confirmText="confirmText" @confirm="save()" @cancel="back()" /> <button-footer
:cancelText="cancelText"
:confirmText="confirmText"
@confirm="save()"
@cancel="back()"
/>
</template> </template>
</full-page> </full-page>
</template> </template>
@ -58,46 +106,56 @@ import api from "@/utils/api.js";
import { upload } from "@/utils/http.js"; import { upload } from "@/utils/http.js";
import { toast } from "@/utils/widget"; import { toast } from "@/utils/widget";
import buttonFooter from '@/components/button-footer.vue'; import buttonFooter from "@/components/button-footer.vue";
import commonCell from "@/components/form-template/common-cell.vue"; import commonCell from "@/components/form-template/common-cell.vue";
import FormInput from "@/components/form-template/form-cell/form-input.vue"; import FormInput from "@/components/form-template/form-cell/form-input.vue";
import FormSelect from "@/components/form-template/form-cell/form-select.vue"; import FormSelect from "@/components/form-template/form-cell/form-select.vue";
import FormTextarea from "@/components/form-template/form-cell/form-textarea.vue"; import FormTextarea from "@/components/form-template/form-cell/form-textarea.vue";
import fullPage from '@/components/full-page.vue'; import fullPage from "@/components/full-page.vue";
const { account, doctorInfo } = storeToRefs(useAccountStore()); const { account, doctorInfo } = storeToRefs(useAccountStore());
const { useLoad, useShow } = useGuard(); const { useLoad, useShow } = useGuard();
const { getDoctorInfo } = useAccountStore(); const { getDoctorInfo } = useAccountStore();
const job = { assistant: '医生助理', doctor: '医生' }; const job = { assistant: "医生助理", doctor: "医生" };
const form = ref({}); const form = ref({});
const inviteTeamId = ref('') const inviteTeamId = ref("");
const type = ref(''); const type = ref("");
const formData = computed(() => ({ ...(doctorInfo.value || {}), ...form.value, mobile: account.value?.mobile })); const formData = computed(() => ({
const cancelText = computed(() => doctorInfo.value ? '取消' : '暂不填写'); ...(doctorInfo.value || {}),
const confirmText = computed(() => type.value === 'cert' ? '下一步' : '保存'); ...form.value,
mobile: account.value?.mobile,
}));
const cancelText = computed(() => (doctorInfo.value ? "取消" : "暂不填写"));
const confirmText = computed(() => (type.value === "cert" ? "下一步" : "保存"));
const jobStr = computed(() => { const jobStr = computed(() => {
const jobs = formData.value && Array.isArray(formData.value.job) ? formData.value.job.filter(i => i === 'assistant' || i === 'doctor') : []; const jobs =
return jobs[0] && job[jobs[0]] ? job[jobs[0]] : ''; formData.value && Array.isArray(formData.value.job)
}) ? formData.value.job.filter((i) => i === "assistant" || i === "doctor")
: [];
return jobs[0] && job[jobs[0]] ? job[jobs[0]] : "";
});
const rule = computed(() => { const rule = computed(() => {
if (doctorInfo.value && ['verified', 'verifying'].includes(doctorInfo.value.verifyStatus)) { if (
doctorInfo.value &&
["verified", "verifying"].includes(doctorInfo.value.verifyStatus)
) {
return { return {
anotherName: { name: '姓名 (不可修改)', required: false, disabled: true }, anotherName: { name: "姓名 (不可修改)", required: false, disabled: true },
job: { name: '岗位 (不可修改)', disabled: true }, job: { name: "岗位 (不可修改)", disabled: true },
title: { name: '职称 (不可修改)', disabled: true }, title: { name: "职称 (不可修改)", disabled: true },
dept: { name: '科室 (不可修改)', disabled: true }, dept: { name: "科室 (不可修改)", disabled: true },
} };
} }
return { return {
anotherName: { name: '姓名', required: true, disabled: false }, anotherName: { name: "姓名", required: true, disabled: false },
job: { name: '岗位', disabled: false }, job: { name: "岗位", disabled: false },
title: { name: '职称', disabled: false }, title: { name: "职称", disabled: false },
dept: { name: '科室', disabled: false }, dept: { name: "科室", disabled: false },
} };
}) });
// //
const genderOptions = [ const genderOptions = [
@ -139,55 +197,60 @@ function chooseAvatar() {
if (url) { if (url) {
form.value.avatar = url; form.value.avatar = url;
} else { } else {
toast('上传失败') toast("上传失败");
} }
} },
}) });
} }
function onChange({ title, value }) { function onChange({ title, value }) {
form.value[title] = value form.value[title] = value;
} }
function selectJob() { function selectJob() {
if (rule.value.job.disabled) return; if (rule.value.job.disabled) return;
uni.showActionSheet({ uni.showActionSheet({
itemList: ['医生', '医生助理', '无'], itemList: ["医生", "医生助理", "无"],
success: ({ tapIndex }) => { success: ({ tapIndex }) => {
const job = ['doctor', 'assistant',][tapIndex]; const job = ["doctor", "assistant"][tapIndex];
form.value.job = job ? [job] : []; form.value.job = job ? [job] : [];
} },
}) });
} }
function toCert() { function toCert() {
if (jobStr.value === '医生') { if (jobStr.value === "医生") {
uni.navigateTo({ uni.navigateTo({
url: '/pages/work/verify/doctor' url: "/pages/work/verify/doctor",
}) });
} else if (jobStr.value === '医生助理') { } else if (jobStr.value === "医生助理") {
uni.navigateTo({ uni.navigateTo({
url: '/pages/work/verify/assistant' url: "/pages/work/verify/assistant",
}) });
} else { } else {
toast('请选择岗位信息') toast("请选择岗位信息");
} }
} }
async function save() { async function save() {
if (typeof formData.value.anotherName !== 'string' || !formData.value.anotherName.trim()) { if (
return toast('请输入姓名') typeof formData.value.anotherName !== "string" ||
!formData.value.anotherName.trim()
) {
return toast("请输入姓名");
} }
if (type.value === 'cert' && !jobStr.value) { if (type.value === "cert" && !jobStr.value) {
return toast('请选择岗位信息') return toast("请选择岗位信息");
} }
const apiName = doctorInfo.value ? 'updateCorpMemberFromWxapp' : 'addCorpMemberFromWxapp'; const apiName = doctorInfo.value
? "updateCorpMemberFromWxapp"
: "addCorpMemberFromWxapp";
const data = { const data = {
...form.value, ...form.value,
weChatOpenId: account.value.openid, weChatOpenId: account.value.openid,
mobile: account.value.mobile, mobile: account.value.mobile,
corpId: account.value.corpId, corpId: account.value.corpId,
} };
if (doctorInfo.value) { if (doctorInfo.value) {
data.id = doctorInfo.value._id; data.id = doctorInfo.value._id;
} }
@ -196,30 +259,29 @@ async function save() {
} }
const res = await api(apiName, data); const res = await api(apiName, data);
if (res && res.success) { if (res && res.success) {
await getDoctorInfo() await getDoctorInfo();
form.value = {}; form.value = {};
if (type.value === 'cert') { if (type.value === "cert") {
toCert() toCert();
} else { } else {
await toast('保存成功'); await toast("保存成功");
back() back();
} }
} else { } else {
await toast(res?.message || '保存失败'); await toast(res?.message || "保存失败");
} }
} }
useLoad(opts => { useLoad((opts) => {
type.value = opts?.type; type.value = opts?.type;
if (type.value === 'joinTeam' && opts.teamId) { if (type.value === "joinTeam" && opts.teamId) {
inviteTeamId.value = opts.teamId inviteTeamId.value = opts.teamId;
} }
})
useShow(() => {
getDoctorInfo()
}); });
useShow(() => {
getDoctorInfo();
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -7,7 +7,7 @@
<view class="relative user-avatar mr-10" @click="editProfile()"> <view class="relative user-avatar mr-10" @click="editProfile()">
<image v-if="doctorInfo && doctorInfo.avatar" class="avatar-img rounded-full overflow-hidden" <image v-if="doctorInfo && doctorInfo.avatar" class="avatar-img rounded-full overflow-hidden"
:src="doctorInfo.avatar" mode="aspectFill" /> :src="doctorInfo.avatar" mode="aspectFill" />
<image v-else class="avatar-img rounded-full overflow-hidden" src="/static/default-avatar.png" <image v-else class="avatar-img rounded-full overflow-hidden" src="/static/home/avatar.svg"
mode="aspectFill" /> mode="aspectFill" />
<view v-if="doctorInfo" class="edit-sub flex items-center justify-center rounded-full bg-primary"> <view v-if="doctorInfo" class="edit-sub flex items-center justify-center rounded-full bg-primary">
<image class="edit-icon" src="/static/work/pen.svg" mode="aspectFill" /> <image class="edit-icon" src="/static/work/pen.svg" mode="aspectFill" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -34,5 +34,18 @@ export default defineStore("teamStore", () => {
teams.value = res && Array.isArray(res.data) ? res.data : []; teams.value = res && Array.isArray(res.data) ? res.data : [];
} }
return { teams, chargeTeams, getTeam, getTeams } // 获取团队成员头像映射
async function getTeamMemberAvatars(teamId) {
if (!teamId || !account.value?.corpId) return {};
const res = await api('getTeamMemberAvatars', {
teamId,
corpId: account.value.corpId
});
if (res && res.success && res.data) {
return res.data; // 返回 { userId: avatar } 的映射对象
}
return {};
}
return { teams, chargeTeams, getTeam, getTeams, getTeamMemberAvatars }
}) })

View File

@ -25,7 +25,8 @@ const urlsConfig = {
createOwnTeam: 'createOwnTeam', createOwnTeam: 'createOwnTeam',
removeTeammate: "removeTeammate", removeTeammate: "removeTeammate",
toggleTeamLeaderRole: "toggleTeamLeaderRole", toggleTeamLeaderRole: "toggleTeamLeaderRole",
joinTheInvitedTeam: 'joinTheInvitedTeam' joinTheInvitedTeam: 'joinTheInvitedTeam',
getTeamMemberAvatars: 'getTeamMemberAvatars'
}, },
knowledgeBase: { knowledgeBase: {

View File

@ -60,7 +60,7 @@ export async function mergeConversationWithGroupDetails(conversationList, option
const formattedList = mergedList const formattedList = mergedList
.map((group) => ({ .map((group) => ({
conversationID: group.conversationID || `GROUP${group.groupID}`, conversationID: group.conversationID || `GROUP${group.groupID}`,
avatar: group.avatar || "/static/default-avatar.png", avatar: group.avatar || "/static/default-patient-avatar.png",
lastMessage: group.lastMessage || "暂无消息", lastMessage: group.lastMessage || "暂无消息",
lastMessageTime: group.lastMessageTime || Date.now(), lastMessageTime: group.lastMessageTime || Date.now(),
groupID: group.groupID, groupID: group.groupID,
@ -150,7 +150,7 @@ function mergeConversationData(conversation, groupDetailsMap) {
name: formatConversationName(groupDetail), name: formatConversationName(groupDetail),
// 更新头像(优先使用已有头像,避免闪动) // 更新头像(优先使用已有头像,避免闪动)
avatar: conversation.avatar || groupDetail.patient?.avatar || '/static/default-avatar.png' avatar: conversation.avatar || groupDetail.patient?.avatar
} }
} }

View File

@ -1086,7 +1086,7 @@ class TimChatManager {
const groupInfo = { const groupInfo = {
groupID: groupID, groupID: groupID,
name: group?.name || '问诊群聊', name: group?.name || '问诊群聊',
avatar: '/static/home/avatar.svg', // avatar: '/static/home/avatar.svg',
memberCount: group?.memberCount || 0 memberCount: group?.memberCount || 0
} }
@ -1172,7 +1172,7 @@ class TimChatManager {
name: conversation.groupProfile?.name || '问诊群聊', name: conversation.groupProfile?.name || '问诊群聊',
doctorId: '', doctorId: '',
patientName: '', patientName: '',
avatar: '/static/home/avatar.svg', // avatar: '/static/home/avatar.svg',
lastMessage: '获取失败', lastMessage: '获取失败',
lastMessageTime: Date.now(), lastMessageTime: Date.now(),
unreadCount: conversation.unreadCount || 0, unreadCount: conversation.unreadCount || 0,
@ -1573,7 +1573,6 @@ class TimChatManager {
} else if (dbMsg.createdAt) { } else if (dbMsg.createdAt) {
lastTime = new Date(dbMsg.createdAt).getTime() lastTime = new Date(dbMsg.createdAt).getTime()
} }
// 构建基础消息对象 // 构建基础消息对象
const message = { const message = {
ID: dbMsg.MsgSeq || dbMsg._id || `db_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, ID: dbMsg.MsgSeq || dbMsg._id || `db_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@ -2520,8 +2519,8 @@ class TimChatManager {
return '[自定义消息]' return '[自定义消息]'
} }
const customData = typeof payload.data === 'string' const customData = typeof payload.data === 'string'
? JSON.parse(payload.data) ? JSON.parse(payload.data)
: payload.data : payload.data
const messageType = customData.messageType || customData.type const messageType = customData.messageType || customData.type
@ -2569,7 +2568,6 @@ class TimChatManager {
conversationID, conversationID,
groupID, groupID,
name: patientName ? `${patientName}的问诊` : groupName || '问诊群聊', name: patientName ? `${patientName}的问诊` : groupName || '问诊群聊',
avatar: '/static/default-avatar.png',
lastMessage, lastMessage,
lastMessageTime, lastMessageTime,
unreadCount: conversation.unreadCount || 0, unreadCount: conversation.unreadCount || 0,
@ -2582,7 +2580,6 @@ class TimChatManager {
conversationID: conversation.conversationID, conversationID: conversation.conversationID,
groupID: conversation.conversationID?.replace('GROUP', '') || '', groupID: conversation.conversationID?.replace('GROUP', '') || '',
name: '问诊群聊', name: '问诊群聊',
avatar: '/static/default-avatar.png',
lastMessage: '暂无消息', lastMessage: '暂无消息',
lastMessageTime: Date.now(), lastMessageTime: Date.now(),
unreadCount: 0, unreadCount: 0,