no message

This commit is contained in:
wangdongbo 2026-01-29 18:03:40 +08:00
parent ceb5fe8841
commit 4fc7a8f6f2
12 changed files with 1251 additions and 11 deletions

View File

@ -115,6 +115,12 @@
"navigationBarTitleText": "病历详情" "navigationBarTitleText": "病历详情"
} }
}, },
{
"path": "pages/case/medical-case-form",
"style": {
"navigationBarTitleText": "添加病历"
}
},
{ {
"path": "pages/case/service-record-detail", "path": "pages/case/service-record-detail",
"style": { "style": {

View File

@ -0,0 +1,486 @@
<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 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>
<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.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 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, onMounted } from 'vue';
const caseType = ref('');
const formData = ref({});
const isEditing = ref(true);
const customerId = ref('');
const groupId = ref('');
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 || '';
// options
if (options.formData) {
try {
formData.value = JSON.parse(decodeURIComponent(options.formData));
} catch (e) {
console.error('解析表单数据失败:', e);
}
}
//
const titles = {
outpatient: '添加门诊病历',
inpatient: '添加住院病历',
physicalExam: '添加体检记录',
preConsultation: '添加预问诊记录'
};
uni.setNavigationBarTitle({
title: titles[caseType.value] || '添加病历'
});
});
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 api = (await import('@/utils/api.js')).default;
const result = await api('addMedicalRecord', {
customerId: customerId.value,
caseType: caseType.value,
...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 = () => {
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] || [];
};
</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: 160rpx;
padding: 20rpx 24rpx;
background-color: #f8f9fa;
border-radius: 8rpx;
font-size: 28rpx;
color: #333333;
&[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;
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,23 +348,30 @@ $primary-color: #0877F1;
.text-input, .text-input,
.voice-input-btn { .voice-input-btn {
flex: 1; flex: 1;
padding: 0 46rpx; padding: 16rpx 46rpx;
background-color: #f3f5fa; background-color: #f3f5fa;
border-radius: 20rpx; border-radius: 20rpx;
margin: 0 16rpx; margin: 0 16rpx;
font-size: 28rpx; font-size: 28rpx;
height: 80rpx; min-height: 80rpx;
max-height: 200rpx;
border: none; border: none;
outline: none; outline: none;
box-sizing: border-box; box-sizing: border-box;
display: flex; line-height: 1.5;
align-items: center;
line-height: 96rpx;
color: #333; color: #333;
} }
.voice-input-btn {
height: 80rpx;
display: flex;
align-items: center;
padding: 0 46rpx;
}
.voice-input-btn { .voice-input-btn {
text-align: center; text-align: center;
line-height: 80rpx;
} }
.more-panel { .more-panel {

View File

@ -0,0 +1,356 @@
<template>
<view class="ai-assistant-buttons">
<view
v-for="button in buttons"
:key="button.id"
class="ai-button"
:class="{ loading: button.loading }"
@click="handleButtonClick(button)"
>
<image class="button-icon" :src="button.icon" mode="aspectFit" />
<text class="button-text">{{
button.loading && button.loadingText ? button.loadingText : button.text
}}</text>
</view>
<!-- 病历类型选择弹窗 -->
<medical-case-type-selector
ref="typeSelectorRef"
@select="handleCaseTypeSelect"
/>
<!-- 进度显示弹窗 -->
<medical-case-progress ref="progressRef" />
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import request from "@/utils/http.js";
import api from "@/utils/api.js";
import MedicalCaseTypeSelector from "./medical-case-type-selector.vue";
import MedicalCaseProgress from "./medical-case-progress.vue";
const props = defineProps({
groupId: {
type: String,
required: true,
},
patientAccountId: {
type: String,
default: "",
},
corpId: {
type: String,
default: "",
},
customerId: {
type: String,
default: "",
},
});
const emit = defineEmits(["streamText", "clearInput"]);
const typeSelectorRef = ref(null);
const progressRef = ref(null);
const buttons = ref([
{
id: "followUp",
text: "追问病情",
loadingText: "AI分析中正在为您生成追问建议…",
icon: "/static/icon/zhuiwen.png",
loading: false,
},
{
id: "aiAssistant",
text: "开启AI助手",
icon: "/static/icon/kaiqiAI.png",
loading: false,
},
{
id: "supplementRecord",
text: "补充病历",
icon: "/static/icon/buchong.png",
loading: false,
},
]);
//
const handleButtonClick = async (button) => {
if (button.loading) return;
switch (button.id) {
case "followUp":
await handleFollowUpInquiry(button);
break;
case "aiAssistant":
handleAIAssistant(button);
break;
case "supplementRecord":
handleSupplementRecord(button);
break;
}
};
//
const handleFollowUpInquiry = async (button) => {
try {
button.loading = true;
// ID
let finalPatientAccountId = props.patientAccountId;
if (!finalPatientAccountId) {
// ID
const chatRecordsResult = await api("getChatRecordsByGroupId", {
groupID: props.groupId,
count: 5,
});
if (
chatRecordsResult.success &&
chatRecordsResult.data &&
chatRecordsResult.data.records
) {
const records = chatRecordsResult.data.records;
//
const currentUserId = uni.getStorageSync("openid") || "";
const patientMessage = records.find(
(record) =>
record.From_Account && record.From_Account !== currentUserId
);
if (patientMessage) {
finalPatientAccountId = patientMessage.From_Account;
}
}
}
if (!finalPatientAccountId) {
uni.showToast({
title: "无法获取患者信息",
icon: "none",
});
button.loading = false;
return;
}
// loading
const result = await request(
{
url: "/getYoucanData/im",
data: {
type: "followUpInquiry",
groupId: props.groupId,
patientAccountId: finalPatientAccountId,
corpId: props.corpId,
},
},
false
); // falseloading
if (result.success && result.data && result.data.suggestion) {
//
streamTextToInput(result.data.suggestion);
} else {
uni.showToast({
title: result.message || "获取追问建议失败",
icon: "none",
});
}
} catch (error) {
console.error("追问病情失败:", error);
uni.showToast({
title: "操作失败,请重试",
icon: "none",
});
} finally {
button.loading = false;
}
};
//
const streamTextToInput = (text) => {
if (!text) return;
//
emit("clearInput");
let currentIndex = 0;
const speed = 50; //
//
setTimeout(() => {
const streamInterval = setInterval(() => {
if (currentIndex < text.length) {
const char = text[currentIndex];
emit("streamText", char);
currentIndex++;
} else {
clearInterval(streamInterval);
}
}, speed);
}, 100);
};
// AI
const handleAIAssistant = (button) => {
uni.showToast({
title: "AI助手功能开发中",
icon: "none",
});
};
//
const handleSupplementRecord = (button) => {
typeSelectorRef.value?.open();
};
//
const handleCaseTypeSelect = async (type) => {
try {
//
progressRef.value?.open(type.id);
//
progressRef.value?.updateProgress(20);
//
const result = await request({
url: "/getYoucanData/im",
data: {
type: "supplementMedicalCase",
groupId: props.groupId,
patientAccountId: props.patientAccountId || props.customerId,
corpId: props.corpId,
caseType: type.id,
},
});
progressRef.value?.updateProgress(60);
if (result.success && result.data) {
const { detectedInfo, formData } = result.data;
//
if (detectedInfo && detectedInfo.length > 0) {
detectedInfo.forEach((info) => {
progressRef.value?.addDetectedInfo(info);
});
}
progressRef.value?.updateProgress(80);
progressRef.value?.setGenerating(true);
//
setTimeout(() => {
progressRef.value?.updateProgress(100);
//
setTimeout(() => {
progressRef.value?.close();
//
uni.navigateTo({
url: `/pages/case/medical-case-form?caseType=${
type.id
}&customerId=${
props.customerId || props.patientAccountId
}&groupId=${props.groupId}&formData=${encodeURIComponent(
JSON.stringify(formData || {})
)}`,
});
}, 500);
}, 1000);
} else {
progressRef.value?.close();
uni.showToast({
title: result.message || "生成病历失败",
icon: "none",
});
}
} catch (error) {
console.error("补充病历失败:", error);
progressRef.value?.close();
uni.showToast({
title: "操作失败,请重试",
icon: "none",
});
}
};
//
onMounted(() => {
uni.$on("regenerateMedicalCase", handleRegenerateMedicalCase);
});
onUnmounted(() => {
uni.$off("regenerateMedicalCase", handleRegenerateMedicalCase);
});
const handleRegenerateMedicalCase = (data) => {
const type = { id: data.caseType };
handleCaseTypeSelect(type);
};
</script>
<style scoped lang="scss">
.ai-assistant-buttons {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 24rpx;
background-color: #f8f9fa;
border-bottom: 1rpx solid #e5e5e5;
.ai-button {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 20rpx;
background-color: #ffffff;
border: 1rpx solid #e0e0e0;
border-radius: 40rpx;
transition: all 0.3s ease;
&.loading {
opacity: 0.8;
pointer-events: none;
.loading-icon {
width: 32rpx;
height: 32rpx;
border: 3rpx solid #e0e0e0;
border-top-color: #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
}
&:active {
background-color: #f0f0f0;
transform: scale(0.98);
}
.button-icon {
width: 32rpx;
height: 32rpx;
}
.button-text {
font-size: 28rpx;
color: #333333;
white-space: nowrap;
}
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -6,8 +6,9 @@
<uni-icons v-else type="mic" size="28" color="#666" /> <uni-icons v-else type="mic" size="28" color="#666" />
</view> </view>
<view class="input-area"> <view class="input-area">
<input v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..." <textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
@confirm="sendTextMessage" @focus="handleInputFocus" /> @confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
:auto-height="true" :show-confirm-bar="false" :adjust-position="true" />
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord" <input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled> @touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled>
</input> </input>
@ -25,7 +26,6 @@
<text>{{ btn.text }}</text> <text>{{ btn.text }}</text>
</view> </view>
</view> </view>
<!-- 录音遮罩层 --> <!-- 录音遮罩层 -->
<view v-if="isRecording" class="recording-overlay"> <view v-if="isRecording" class="recording-overlay">
<view class="recording-modal" :class="{ 'cancel-mode': isCancelMode }"> <view class="recording-modal" :class="{ 'cancel-mode': isCancelMode }">
@ -95,6 +95,11 @@ const cloudCustomData = computed(() => {
return arr.filter(Boolean).join("|"); return arr.filter(Boolean).join("|");
}); });
//
const appendStreamText = (char) => {
inputText.value += char;
};
// + // +
const recordingDuration = ref(0); const recordingDuration = ref(0);
let recordingTimer = null; let recordingTimer = null;
@ -165,9 +170,22 @@ const sendTextMessageFromPhrase = async (content) => {
}); });
}; };
//
const setInputText = (text) => {
inputText.value = text;
};
//
const clearInputText = () => {
inputText.value = '';
};
// //
defineExpose({ defineExpose({
sendTextMessageFromPhrase sendTextMessageFromPhrase,
appendStreamText,
setInputText,
clearInputText
}); });
// //
@ -432,6 +450,13 @@ function handleInputFocus() {
}); });
} }
function handleInput(e) {
// textarea
nextTick().then(() => {
emit("scrollToBottom");
});
}
onMounted(() => { onMounted(() => {
// //
initRecorderManager(); initRecorderManager();

View File

@ -0,0 +1,197 @@
<template>
<uni-popup ref="popup" type="center" :mask-click="false">
<view class="progress-modal">
<view class="close-btn" @click="close">
<text class="close-icon"></text>
</view>
<view class="progress-content">
<view class="progress-title">{{ progressTitle }}</view>
<view class="progress-bar-wrapper">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progress + '%' }"></view>
</view>
<text class="progress-text">{{ progress }}%</text>
</view>
<view v-if="detectedInfo.length > 0" class="detected-info">
<text class="detected-title">检测到以下{{ caseTypeName }}信息</text>
<view class="info-list">
<view v-for="(item, index) in detectedInfo" :key="index" class="info-item">
<text class="check-icon"></text>
<text class="info-text">{{ item }}</text>
</view>
</view>
</view>
<view v-if="isGenerating" class="generating-text">
正在生成结构化{{ caseTypeName }}...
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, computed } from 'vue';
const popup = ref(null);
const progress = ref(0);
const detectedInfo = ref([]);
const isGenerating = ref(false);
const caseType = ref('');
const CASE_TYPE_NAMES = {
outpatient: '门诊病历',
inpatient: '住院病历',
physicalExam: '体检记录',
preConsultation: '预问诊记录'
};
const caseTypeName = computed(() => CASE_TYPE_NAMES[caseType.value] || '病历');
const progressTitle = computed(() => {
if (progress.value < 100) {
return `正在智能整理${caseTypeName.value}...`;
}
return `${caseTypeName.value}生成完成`;
});
const open = (type) => {
caseType.value = type;
progress.value = 0;
detectedInfo.value = [];
isGenerating.value = false;
popup.value?.open();
};
const close = () => {
popup.value?.close();
};
const updateProgress = (value) => {
progress.value = value;
};
const addDetectedInfo = (info) => {
detectedInfo.value.push(info);
};
const setGenerating = (value) => {
isGenerating.value = value;
};
defineExpose({
open,
close,
updateProgress,
addDetectedInfo,
setGenerating
});
</script>
<style scoped lang="scss">
.progress-modal {
width: 600rpx;
background-color: #ffffff;
border-radius: 24rpx;
padding: 48rpx 40rpx;
position: relative;
.close-btn {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
.close-icon {
font-size: 40rpx;
color: #999999;
}
}
.progress-content {
.progress-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
margin-bottom: 32rpx;
text-align: center;
}
.progress-bar-wrapper {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
.progress-bar {
flex: 1;
height: 16rpx;
background-color: #e5e5e5;
border-radius: 8rpx;
overflow: hidden;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%);
transition: width 0.3s ease;
}
}
.progress-text {
font-size: 28rpx;
color: #1890ff;
font-weight: 600;
min-width: 80rpx;
text-align: right;
}
}
.detected-info {
margin-bottom: 24rpx;
.detected-title {
font-size: 28rpx;
color: #666666;
display: block;
margin-bottom: 16rpx;
}
.info-list {
.info-item {
display: flex;
align-items: flex-start;
gap: 12rpx;
margin-bottom: 12rpx;
.check-icon {
font-size: 28rpx;
color: #52c41a;
font-weight: bold;
}
.info-text {
flex: 1;
font-size: 26rpx;
color: #333333;
line-height: 1.5;
}
}
}
}
.generating-text {
font-size: 28rpx;
color: #1890ff;
text-align: center;
padding: 16rpx 0;
}
}
}
</style>

View File

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

View File

@ -125,6 +125,16 @@
@cancel="handleRejectReasonCancel" @cancel="handleRejectReasonCancel"
/> />
<!-- AI助手按钮组 -->
<AIAssistantButtons
v-if="!isEvaluationPopupOpen && !showConsultAccept && orderStatus === 'processing'"
:groupId="groupId"
:patientAccountId="chatInfo.userID || ''"
:corpId="corpId"
@streamText="handleStreamText"
@clearInput="handleClearInput"
/>
<!-- 聊天输入组件 --> <!-- 聊天输入组件 -->
<ChatInput <ChatInput
v-if="!isEvaluationPopupOpen && !showConsultAccept" v-if="!isEvaluationPopupOpen && !showConsultAccept"
@ -175,6 +185,7 @@ import ChatInput from "./components/chat-input.vue";
import SystemMessage from "./components/system-message.vue"; import SystemMessage from "./components/system-message.vue";
import ConsultAccept from "./components/consult-accept.vue"; import ConsultAccept from "./components/consult-accept.vue";
import RejectReasonModal from "./components/reject-reason-modal.vue"; import RejectReasonModal from "./components/reject-reason-modal.vue";
import AIAssistantButtons from "./components/ai-assistant-buttons.vue";
const timChatManager = globalTimChatManager; const timChatManager = globalTimChatManager;
@ -723,9 +734,25 @@ onHide(() => {
const sendCommonPhrase = (content) => { const sendCommonPhrase = (content) => {
if (chatInputRef.value) { if (chatInputRef.value) {
chatInputRef.value.sendTextMessageFromPhrase(content); //
chatInputRef.value.setInputText(content);
} }
}; };
//
const handleStreamText = (char) => {
if (chatInputRef.value) {
chatInputRef.value.appendStreamText(char);
}
};
//
const handleClearInput = () => {
if (chatInputRef.value) {
chatInputRef.value.clearInputText();
}
};
// //
defineExpose({ defineExpose({
sendCommonPhrase, sendCommonPhrase,

BIN
static/icon/buchong.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/icon/kaiqiAI.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
static/icon/zhuiwen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

View File

@ -90,7 +90,9 @@ const urlsConfig = {
acceptConsultation: "acceptConsultation", acceptConsultation: "acceptConsultation",
sendArticleMessage: "sendArticleMessage", sendArticleMessage: "sendArticleMessage",
getChatRecordsByGroupId: "getChatRecordsByGroupId", getChatRecordsByGroupId: "getChatRecordsByGroupId",
getGroupList: "getGroupList" getGroupList: "getGroupList",
followUpInquiry: "followUpInquiry",
supplementMedicalCase: "supplementMedicalCase"
}, },
todo: { todo: {
getCustomerTodos: 'getCustomerTodos', getCustomerTodos: 'getCustomerTodos',