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,211 +1,45 @@
<template>
<view class="medical-case-form">
<view class="form-container">
<!-- 门诊病历 -->
<template v-if="caseType === 'outpatient'">
<view class="form-item required">
<view class="item-label">就诊机构</view>
<input
class="item-input"
v-model="formData.hospital"
placeholder="暂无"
:disabled="!isEditing"
/>
</view>
<!-- 动态渲染表单字段 -->
<view
v-for="field in currentFields"
:key="field.key"
class="form-item"
:class="{ required: field.required }"
>
<view class="item-label">{{ field.label }}</view>
<view class="form-item required">
<view class="item-label">就诊日期</view>
<picker
mode="date"
:value="formData.visitTime"
@change="onDateChange('visitTime', $event)"
:disabled="!isEditing"
>
<view class="picker-value">
{{ formData.visitTime || '暂无' }}
</view>
</picker>
</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>
<view class="form-item required">
<view class="item-label">门诊诊断</view>
<textarea
class="item-textarea"
v-model="formData.diagnosisName"
placeholder="请输入"
:disabled="!isEditing"
/>
</view>
<!-- 多行文本 -->
<textarea
v-else-if="field.type === 'textarea'"
class="item-textarea"
v-model="formData[field.key]"
placeholder="请输入"
:disabled="!isEditing"
/>
<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 === '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>
<!-- 单行文本 -->
<input
v-else
class="item-input"
v-model="formData[field.key]"
placeholder="暂无"
:disabled="!isEditing"
/>
</view>
</view>
@ -221,42 +55,212 @@
</template>
<script setup>
import { ref, onMounted } from 'vue';
const caseType = ref('');
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 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.customerId || '';
groupId.value = options.groupId || '';
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);
console.error("解析表单数据失败:", e);
}
}
//
const titles = {
outpatient: '添加门诊病历',
inpatient: '添加住院病历',
physicalExam: '添加体检记录',
preConsultation: '添加预问诊记录'
};
uni.setNavigationBarTitle({
title: titles[caseType.value] || '添加病历'
});
const title = CASE_TYPE_NAMES[caseType.value]
? `添加${CASE_TYPE_NAMES[caseType.value]}`
: "添加病历";
uni.setNavigationBarTitle({ title });
});
const onDateChange = (field, event) => {
@ -265,55 +269,55 @@ const onDateChange = (field, event) => {
const handleRegenerate = () => {
uni.showModal({
title: '提示',
content: '确定要重新生成吗?当前编辑的内容将被覆盖',
title: "提示",
content: "确定要重新生成吗?当前编辑的内容将被覆盖",
success: (res) => {
if (res.confirm) {
//
uni.navigateBack({
success: () => {
uni.$emit('regenerateMedicalCase', {
uni.$emit("regenerateMedicalCase", {
caseType: caseType.value,
customerId: customerId.value,
groupId: groupId.value
groupId: groupId.value,
});
}
},
});
}
}
},
});
};
const handleSave = async () => {
//
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) {
uni.showToast({
title: `请填写${missingFields[0].label}`,
icon: 'none'
icon: "none",
});
return;
}
try {
uni.showLoading({ title: '保存中...' });
//
const api = (await import('@/utils/api.js')).default;
const result = await api('addMedicalRecord', {
customerId: customerId.value,
caseType: caseType.value,
...formData.value
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'
title: "保存成功",
icon: "success",
});
setTimeout(() => {
@ -321,40 +325,22 @@ const handleSave = async () => {
}, 1500);
} else {
uni.showToast({
title: result.message || '保存失败',
icon: 'none'
title: result.message || "保存失败",
icon: "none",
});
}
} catch (error) {
uni.hideLoading();
console.error('保存病历失败:', error);
console.error("保存病历失败:", error);
uni.showToast({
title: '保存失败,请重试',
icon: 'none'
title: "保存失败,请重试",
icon: "none",
});
}
};
const getRequiredFields = () => {
const fieldsMap = {
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] || [];
return currentFields.value.filter((field) => field.required);
};
</script>
@ -372,7 +358,7 @@ const getRequiredFields = () => {
margin-bottom: 32rpx;
&.required .item-label::before {
content: '*';
content: "*";
color: #ff4d4f;
margin-right: 8rpx;
}
@ -406,13 +392,13 @@ const getRequiredFields = () => {
.item-textarea {
width: 100%;
min-height: 160rpx;
min-height: 100rpx;
padding: 20rpx 24rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
font-size: 28rpx;
color: #333333;
height: 100px;
&[disabled] {
color: #999999;
}
@ -444,6 +430,7 @@ const getRequiredFields = () => {
bottom: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
gap: 24rpx;
padding: 24rpx 32rpx;

View File

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

View File

@ -10,12 +10,15 @@
<view class="progress-bar-wrapper">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progress + '%' }"></view>
<view
class="progress-fill"
:style="{ width: progress + '%' }"
></view>
</view>
<text class="progress-text">{{ progress }}%</text>
</view>
<view class="detected-info">
<view class="detected-info">
<text class="detected-title">检测到以下{{ caseTypeName }}信息</text>
<view class="info-list">
<view
@ -54,48 +57,47 @@
</template>
<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 progress = ref(0);
const detectedInfo = ref([]);
const isGenerating = ref(false);
const isCompleted = ref(false);
const caseType = ref('');
const caseType = ref("");
const finalData = ref(null);
const CASE_TYPE_NAMES = {
outpatient: '门诊病历',
inpatient: '住院病历',
physicalExam: '体检记录',
preConsultation: '预问诊记录'
outpatient: "门诊病历",
inhospital: "住院病历",
physicalExaminationTemplate: "体检记录",
preConsultation: "预问诊记录",
};
const FIELD_LABELS = {
//
visitTime: '就诊日期',
chiefComplaint: '主诉',
medicalHistorySummary: '病史概要',
examination: '检查',
diagnosisName: '门诊诊断',
visitTime: "就诊日期",
chiefComplaint: "主诉",
medicalHistorySummary: "病史概要",
examination: "检查",
diagnosisName: "门诊诊断",
//
inhosDate: '入院日期',
operation: '手术记录',
operationDate: '手术日期',
treatmentPlan: '术后病程',
outhosAdvice: '出院医嘱',
inhosDate: "入院日期",
operation: "手术名称",
operationDate: "手术日期",
treatmentPlan: "治疗方案",
//
inspectTime: '体检日期',
inspectSummary: '体检小结',
positiveFind: '阳性发现及处理意见',
inspectTime: "体检日期",
inspectSummary: "体检小结",
positiveFind: "阳性发现及处理意见",
//
presentIllnessHistory: '现病史',
pastMedicalHistory: '既往史'
presentIllnessHistory: "现病史",
pastMedicalHistory: "既往史",
};
const caseTypeName = computed(() => CASE_TYPE_NAMES[caseType.value] || '病历');
const caseTypeName = computed(() => CASE_TYPE_NAMES[caseType.value] || "病历");
const progressTitle = computed(() => {
if (progress.value < 100) {
@ -125,11 +127,11 @@ const updateProgress = (value) => {
const addDetectedInfo = (fieldKey, fieldValue) => {
const label = FIELD_LABELS[fieldKey] || fieldKey;
// ""
const displayValue = (fieldValue && fieldValue.trim()) ? fieldValue : '暂无';
const displayValue = fieldValue && fieldValue.trim() ? fieldValue : "暂无";
detectedInfo.value.push({
label,
value: displayValue,
animated: true
animated: true,
});
};
@ -151,14 +153,14 @@ const reset = () => {
};
const handleRegenerate = () => {
emit('regenerate', { caseType: caseType.value });
emit("regenerate", { caseType: caseType.value });
close();
};
const handleNext = () => {
emit('next', {
emit("next", {
caseType: caseType.value,
data: finalData.value
data: finalData.value,
});
close();
};
@ -170,7 +172,7 @@ defineExpose({
addDetectedInfo,
setGenerating,
setCompleted,
reset
reset,
});
</script>
@ -295,7 +297,7 @@ defineExpose({
padding: 16rpx 0;
.dot-animation::after {
content: '...';
content: "...";
animation: dots 1.5s steps(4, end) infinite;
}
}
@ -358,17 +360,19 @@ defineExpose({
}
@keyframes dots {
0%, 20% {
content: '';
0%,
20% {
content: "";
}
40% {
content: '.';
content: ".";
}
60% {
content: '..';
content: "..";
}
80%, 100% {
content: '...';
80%,
100% {
content: "...";
}
}
</style>

View File

@ -23,29 +23,29 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref } from "vue";
const emit = defineEmits(['select']);
const emit = defineEmits(["select"]);
const popup = ref(null);
const caseTypes = [
{
id: 'outpatient',
name: '门诊病历'
id: "outpatient",
name: "门诊病历",
},
{
id: 'inpatient',
name: '住院病历'
id: "inhospital",
name: "住院病历",
},
{
id: 'physicalExam',
name: '体检记录'
id: "physicalExaminationTemplate",
name: "体检记录",
},
{
id: 'preConsultation',
name: '预问诊记录'
}
id: "preConsultation",
name: "预问诊记录",
},
];
const open = () => {
@ -57,13 +57,13 @@ const close = () => {
};
const selectType = (type) => {
emit('select', type);
emit("select", type);
close();
};
defineExpose({
open,
close
close,
});
</script>

View File

@ -1,5 +1,7 @@
import { ref, computed } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import api from '@/utils/api.js'
import useTeamStore from '@/store/team.js'
/**
* 简单的群聊hook
@ -8,6 +10,9 @@ import { onShow, onUnload } from '@dcloudio/uni-app'
export default function useGroupChat(groupID) {
const groupInfo = ref({})
const members = ref([])
const teamMemberIds = ref([]) // 存储团队成员的userId列表
const patientId = ref('') // 存储患者ID
const teamStore = useTeamStore()
// 群聊成员映射
const chatMember = computed(() => {
@ -15,30 +20,79 @@ export default function useGroupChat(groupID) {
members.value.forEach(member => {
res[member.id] = {
name: member.name,
avatar: member.avatar || '/static/default-avatar.png'
avatar: member.avatar,
isTeamMember: member.isTeamMember // 标记是否为团队成员
}
})
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() {
const gid = typeof groupID === 'string' ? groupID : groupID.value
if (!gid) return
try {
// 这里可以调用API获取群聊信息
// const res = await getGroupDetail(gid)
// if (res && res.success) {
// groupInfo.value = res.data
// members.value = res.data.members || []
// }
// 1. 获取群聊基本信息
const groupResult = await api('getGroupListByGroupId', { groupId: gid })
// 暂时使用本地数据
groupInfo.value = {
groupID: gid,
name: '群聊',
status: 'active'
if (groupResult && groupResult.success && groupResult.data) {
groupInfo.value = {
groupID: gid,
name: groupResult.data.team?.name || '群聊',
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) {
console.error('获取群聊信息失败:', error)
@ -57,6 +111,8 @@ export default function useGroupChat(groupID) {
groupInfo,
members,
chatMember,
getGroupInfo
getGroupInfo,
isTeamMember,
getUserAvatar
}
}

View File

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

View File

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

View File

@ -7,7 +7,7 @@
<view class="relative user-avatar mr-10" @click="editProfile()">
<image v-if="doctorInfo && doctorInfo.avatar" class="avatar-img rounded-full overflow-hidden"
: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" />
<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" />

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 : [];
}
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',
removeTeammate: "removeTeammate",
toggleTeamLeaderRole: "toggleTeamLeaderRole",
joinTheInvitedTeam: 'joinTheInvitedTeam'
joinTheInvitedTeam: 'joinTheInvitedTeam',
getTeamMemberAvatars: 'getTeamMemberAvatars'
},
knowledgeBase: {

View File

@ -60,7 +60,7 @@ export async function mergeConversationWithGroupDetails(conversationList, option
const formattedList = mergedList
.map((group) => ({
conversationID: group.conversationID || `GROUP${group.groupID}`,
avatar: group.avatar || "/static/default-avatar.png",
avatar: group.avatar || "/static/default-patient-avatar.png",
lastMessage: group.lastMessage || "暂无消息",
lastMessageTime: group.lastMessageTime || Date.now(),
groupID: group.groupID,
@ -150,7 +150,7 @@ function mergeConversationData(conversation, groupDetailsMap) {
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 = {
groupID: groupID,
name: group?.name || '问诊群聊',
avatar: '/static/home/avatar.svg',
// avatar: '/static/home/avatar.svg',
memberCount: group?.memberCount || 0
}
@ -1172,7 +1172,7 @@ class TimChatManager {
name: conversation.groupProfile?.name || '问诊群聊',
doctorId: '',
patientName: '',
avatar: '/static/home/avatar.svg',
// avatar: '/static/home/avatar.svg',
lastMessage: '获取失败',
lastMessageTime: Date.now(),
unreadCount: conversation.unreadCount || 0,
@ -1573,7 +1573,6 @@ class TimChatManager {
} else if (dbMsg.createdAt) {
lastTime = new Date(dbMsg.createdAt).getTime()
}
// 构建基础消息对象
const message = {
ID: dbMsg.MsgSeq || dbMsg._id || `db_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@ -2569,7 +2568,6 @@ class TimChatManager {
conversationID,
groupID,
name: patientName ? `${patientName}的问诊` : groupName || '问诊群聊',
avatar: '/static/default-avatar.png',
lastMessage,
lastMessageTime,
unreadCount: conversation.unreadCount || 0,
@ -2582,7 +2580,6 @@ class TimChatManager {
conversationID: conversation.conversationID,
groupID: conversation.conversationID?.replace('GROUP', '') || '',
name: '问诊群聊',
avatar: '/static/default-avatar.png',
lastMessage: '暂无消息',
lastMessageTime: Date.now(),
unreadCount: 0,