Compare commits
No commits in common. "85c56ebf8991d65b3d5df57c55cd7a710f8b2666" and "6a6b8abf9fe169cca698c0ddd1f55bb89ca2608d" have entirely different histories.
85c56ebf89
...
6a6b8abf9f
@ -1,65 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<full-page :customScroll="empty">
|
<view class="medical-case-form">
|
||||||
<view class="medical-case-form">
|
<view class="form-container">
|
||||||
<view class="form-container">
|
<!-- 动态渲染表单字段 -->
|
||||||
<!-- 动态渲染表单字段 -->
|
<view
|
||||||
<view
|
v-for="field in currentFields"
|
||||||
v-for="field in currentFields"
|
:key="field.key"
|
||||||
:key="field.key"
|
class="form-item"
|
||||||
class="form-item"
|
:class="{ required: field.required }"
|
||||||
:class="{ required: field.required }"
|
>
|
||||||
|
<view class="item-label">{{ field.label }}</view>
|
||||||
|
|
||||||
|
<!-- 日期选择器 -->
|
||||||
|
<picker
|
||||||
|
v-if="field.type === 'date'"
|
||||||
|
mode="date"
|
||||||
|
:value="formData[field.key]"
|
||||||
|
@change="onDateChange(field.key, $event)"
|
||||||
|
:disabled="!isEditing"
|
||||||
>
|
>
|
||||||
<view class="item-label">{{ field.label }}</view>
|
<view class="picker-value">
|
||||||
<!-- 日期选择器 -->
|
{{ formData[field.key] || "暂无" }}
|
||||||
<picker
|
</view>
|
||||||
v-if="field.type === 'date'"
|
</picker>
|
||||||
mode="date"
|
|
||||||
:value="formData[field.key]"
|
|
||||||
@change="onDateChange(field.key, $event)"
|
|
||||||
:disabled="!isEditing"
|
|
||||||
>
|
|
||||||
<view class="picker-value">
|
|
||||||
{{ formData[field.key] || "暂无" }}
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
|
|
||||||
<!-- 多行文本 -->
|
<!-- 多行文本 -->
|
||||||
<textarea
|
<textarea
|
||||||
v-else-if="field.type === 'textarea'"
|
v-else-if="field.type === 'textarea'"
|
||||||
class="item-textarea"
|
class="item-textarea"
|
||||||
v-model="formData[field.key]"
|
v-model="formData[field.key]"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:disabled="!isEditing"
|
:disabled="!isEditing"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 单行文本 -->
|
<!-- 单行文本 -->
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
class="item-input"
|
class="item-input"
|
||||||
v-model="formData[field.key]"
|
v-model="formData[field.key]"
|
||||||
placeholder="暂无"
|
placeholder="暂无"
|
||||||
:disabled="!isEditing"
|
:disabled="!isEditing"
|
||||||
/>
|
/>
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="footer-buttons">
|
|
||||||
<view class="btn-regenerate" @click="handleRegenerate">
|
|
||||||
<text class="btn-text">重新生成</text>
|
|
||||||
</view>
|
|
||||||
<view class="btn-save" @click="handleSave">
|
|
||||||
<text class="btn-text">保存至档案</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</full-page>
|
|
||||||
|
<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>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import useAccountStore from "@/store/account";
|
import useAccountStore from "@/store/account";
|
||||||
import FullPage from "@/components/full-page.vue";
|
|
||||||
import api from "@/utils/api.js";
|
import api from "@/utils/api.js";
|
||||||
const caseType = ref("");
|
const caseType = ref("");
|
||||||
const formData = ref({});
|
const formData = ref({});
|
||||||
@ -73,7 +71,7 @@ const CASE_TYPE_NAMES = {
|
|||||||
outpatient: "门诊病历",
|
outpatient: "门诊病历",
|
||||||
inhospital: "住院病历",
|
inhospital: "住院病历",
|
||||||
physicalExaminationTemplate: "体检记录",
|
physicalExaminationTemplate: "体检记录",
|
||||||
preConsultationRecord: "预问诊记录",
|
preConsultation: "预问诊记录",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 字段标签
|
// 字段标签
|
||||||
@ -187,7 +185,7 @@ const FIELD_CONFIG = {
|
|||||||
label: FIELD_LABELS.examination,
|
label: FIELD_LABELS.examination,
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
required: false,
|
required: false,
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
physicalExaminationTemplate: [
|
physicalExaminationTemplate: [
|
||||||
{
|
{
|
||||||
@ -200,7 +198,7 @@ const FIELD_CONFIG = {
|
|||||||
key: "inspectSummary",
|
key: "inspectSummary",
|
||||||
label: FIELD_LABELS.inspectSummary,
|
label: FIELD_LABELS.inspectSummary,
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
required: true,
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "positiveFind",
|
key: "positiveFind",
|
||||||
@ -209,12 +207,12 @@ const FIELD_CONFIG = {
|
|||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
preConsultationRecord: [
|
preConsultation: [
|
||||||
{
|
{
|
||||||
key: "consultationDate",
|
key: "consultationDate",
|
||||||
label: FIELD_LABELS.consultationDate,
|
label: FIELD_LABELS.consultationDate,
|
||||||
type: "date",
|
type: "date",
|
||||||
required: true,
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "chiefComplaint",
|
key: "chiefComplaint",
|
||||||
@ -232,7 +230,7 @@ const FIELD_CONFIG = {
|
|||||||
key: "pastMedicalHistory",
|
key: "pastMedicalHistory",
|
||||||
label: FIELD_LABELS.pastMedicalHistory,
|
label: FIELD_LABELS.pastMedicalHistory,
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
required: false,
|
required: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -367,7 +367,7 @@ function normalizeMedicalType(raw) {
|
|||||||
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
||||||
if (s === 'outPatient') return 'outpatient';
|
if (s === 'outPatient') return 'outpatient';
|
||||||
if (s === 'inHospital') return 'inhospital';
|
if (s === 'inHospital') return 'inhospital';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultation') return 'preConsultationRecord';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
||||||
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export const VISIT_RECORD_TEMPLATES = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
templateType: 'preConsultationRecord',
|
templateType: 'preConsultation',
|
||||||
templateName: '预问诊记录',
|
templateName: '预问诊记录',
|
||||||
service: { timeTitle: 'consultDate', timeName: '问诊日期' },
|
service: { timeTitle: 'consultDate', timeName: '问诊日期' },
|
||||||
templateList: [
|
templateList: [
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const ALIAS_MAP = {
|
|||||||
inspectTime: 'inspectDate',
|
inspectTime: 'inspectDate',
|
||||||
inspectSummary: 'summary',
|
inspectSummary: 'summary',
|
||||||
},
|
},
|
||||||
preConsultationRecord: {
|
preConsultation: {
|
||||||
presentIllnessHistory: 'presentIllness',
|
presentIllnessHistory: 'presentIllness',
|
||||||
pastMedicalHistory: 'pastHistory',
|
pastMedicalHistory: 'pastHistory',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -124,7 +124,7 @@ function normalizeMedicalType(raw) {
|
|||||||
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
||||||
if (s === 'outPatient') return 'outpatient';
|
if (s === 'outPatient') return 'outpatient';
|
||||||
if (s === 'inHospital') return 'inhospital';
|
if (s === 'inHospital') return 'inhospital';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultation') return 'preConsultationRecord';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
||||||
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@ -106,7 +106,7 @@ function normalizeMedicalType(raw) {
|
|||||||
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
||||||
if (s === 'outPatient') return 'outpatient';
|
if (s === 'outPatient') return 'outpatient';
|
||||||
if (s === 'inHospital') return 'inhospital';
|
if (s === 'inHospital') return 'inhospital';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultation') return 'preConsultationRecord';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
||||||
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@ -858,7 +858,7 @@ function normalizeMedicalType(raw) {
|
|||||||
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
|
||||||
if (s === 'outPatient') return 'outpatient';
|
if (s === 'outPatient') return 'outpatient';
|
||||||
if (s === 'inHospital') return 'inhospital';
|
if (s === 'inHospital') return 'inhospital';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultation') return 'preConsultationRecord';
|
||||||
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
if (s === 'preConsultationRecord') return 'preConsultationRecord';
|
||||||
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@ -6,21 +6,28 @@ $text-color-sub: #999;
|
|||||||
$primary-color: #0877F1;
|
$primary-color: #0877F1;
|
||||||
|
|
||||||
.chat-page {
|
.chat-page {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 患者信息栏样式 */
|
/* 患者信息栏样式 - 固定在顶部 */
|
||||||
.patient-info-bar {
|
.patient-info-bar {
|
||||||
position: relative;
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
padding: 20rpx 32rpx;
|
padding: 20rpx 32rpx;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
flex-shrink: 0; /* 防止被压缩 */
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.patient-info-content {
|
.patient-info-content {
|
||||||
@ -84,10 +91,14 @@ $primary-color: #0877F1;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-content {
|
.chat-content {
|
||||||
flex: 1;
|
position: fixed;
|
||||||
|
top: 100rpx; /* 患者信息栏高度,根据实际调整 */
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 200rpx; /* 输入框高度,根据实际调整 */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
min-height: 0;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-content-compressed {
|
.chat-content-compressed {
|
||||||
@ -352,19 +363,31 @@ $primary-color: #0877F1;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-section {
|
.input-section {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-top: 1rpx solid #e0e0e0;
|
|
||||||
position: relative;
|
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
padding-bottom: 40rpx;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
transform: translateZ(0); /* 开启硬件加速,提升性能 */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-assistant-slot {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-toolbar {
|
.input-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16rpx 20rpx;
|
padding: 12rpx 20rpx;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
gap: 12rpx;
|
gap: 12rpx;
|
||||||
|
border-top: 1rpx solid #e0e0e0;
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voice-toggle-btn {
|
.voice-toggle-btn {
|
||||||
@ -487,7 +510,8 @@ $primary-color: #0877F1;
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-top: 1rpx solid #eee;
|
border-top: 1rpx solid #eee;
|
||||||
padding: 20rpx 0 40rpx 60rpx;
|
padding: 20rpx 0 20rpx 60rpx;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
gap: 40rpx 50rpx;
|
gap: 40rpx 50rpx;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
@ -517,7 +541,7 @@ $primary-color: #0877F1;
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
border-top: 1rpx solid #e0e0e0;
|
border-top: 1rpx solid #e0e0e0;
|
||||||
padding: 16rpx;
|
padding: 16rpx;
|
||||||
margin-bottom: 40rpx;
|
margin-bottom: env(safe-area-inset-bottom);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -662,7 +686,7 @@ $primary-color: #0877F1;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
padding-bottom: calc(20rpx + 40rpx);
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -794,7 +818,7 @@ $primary-color: #0877F1;
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: 32rpx 20rpx 48rpx 20rpx;
|
padding: 32rpx 20rpx 48rpx 20rpx;
|
||||||
padding-bottom: calc(48rpx + 40rpx);
|
padding-bottom: calc(48rpx + env(safe-area-inset-bottom));
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -12,6 +12,19 @@
|
|||||||
button.loading && button.loadingText ? button.loadingText : button.text
|
button.loading && button.loadingText ? button.loadingText : button.text
|
||||||
}}</text>
|
}}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 病历类型选择弹窗 -->
|
||||||
|
<medical-case-type-selector
|
||||||
|
ref="typeSelectorRef"
|
||||||
|
@select="handleCaseTypeSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 进度显示弹窗 -->
|
||||||
|
<medical-case-progress
|
||||||
|
ref="progressRef"
|
||||||
|
@regenerate="handleRegenerateFromProgress"
|
||||||
|
@next="handleNextFromProgress"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -19,6 +32,8 @@
|
|||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import request from "@/utils/http.js";
|
import request from "@/utils/http.js";
|
||||||
import api from "@/utils/api.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({
|
const props = defineProps({
|
||||||
groupId: {
|
groupId: {
|
||||||
@ -41,24 +56,11 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
typeSelectorRef: {
|
|
||||||
type: Object,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
progressRef: {
|
|
||||||
type: Object,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const emit = defineEmits([
|
const emit = defineEmits(["streamText", "clearInput", "generatingStateChange"]);
|
||||||
"streamText",
|
|
||||||
"clearInput",
|
|
||||||
"generatingStateChange",
|
|
||||||
"caseTypeSelect",
|
|
||||||
"regenerateFromProgress",
|
|
||||||
"nextFromProgress",
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
const typeSelectorRef = ref(null);
|
||||||
|
const progressRef = ref(null);
|
||||||
const isGenerating = ref(false);
|
const isGenerating = ref(false);
|
||||||
|
|
||||||
const buttons = ref([
|
const buttons = ref([
|
||||||
@ -215,22 +217,143 @@ const handleAIAssistant = (button) => {
|
|||||||
|
|
||||||
// 处理补充病历
|
// 处理补充病历
|
||||||
const handleSupplementRecord = (button) => {
|
const handleSupplementRecord = (button) => {
|
||||||
props.typeSelectorRef?.open();
|
typeSelectorRef.value?.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理病历类型选择
|
// 处理病历类型选择
|
||||||
const handleCaseTypeSelect = async (type) => {
|
const handleCaseTypeSelect = async (type) => {
|
||||||
emit("caseTypeSelect", type);
|
try {
|
||||||
|
// 打开进度弹窗
|
||||||
|
progressRef.value?.open(type.id);
|
||||||
|
progressRef.value?.updateProgress(10);
|
||||||
|
|
||||||
|
// 调用补充病历接口(流式处理)
|
||||||
|
await requestWithStream({
|
||||||
|
url: "/getYoucanData/im",
|
||||||
|
data: {
|
||||||
|
type: "supplementMedicalCase",
|
||||||
|
groupId: props.groupId,
|
||||||
|
patientAccountId: props.patientAccountId || props.customerId,
|
||||||
|
corpId: props.corpId,
|
||||||
|
caseType: type.id,
|
||||||
|
},
|
||||||
|
onProgress: (data) => {
|
||||||
|
// 处理流式数据
|
||||||
|
handleStreamData(data, type.id);
|
||||||
|
},
|
||||||
|
onComplete: (finalData) => {
|
||||||
|
// 完成后跳转
|
||||||
|
handleComplete(finalData, type.id);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
progressRef.value?.close();
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || "生成病历失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("补充病历失败:", error);
|
||||||
|
progressRef.value?.close();
|
||||||
|
uni.showToast({
|
||||||
|
title: "操作失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 流式请求处理
|
||||||
|
const requestWithStream = async ({
|
||||||
|
url,
|
||||||
|
data,
|
||||||
|
onProgress,
|
||||||
|
onComplete,
|
||||||
|
onError,
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
// 调用接口时不显示全局 loading(第二个参数为 false)
|
||||||
|
const result = await request(
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success && result.data) {
|
||||||
|
// 模拟流式处理(如果后端返回的是完整数据)
|
||||||
|
const extractedData = result.data.extractedData || {};
|
||||||
|
|
||||||
|
// 逐个字段动态显示(包括空值字段)
|
||||||
|
let progressValue = 20;
|
||||||
|
const fields = Object.entries(extractedData);
|
||||||
|
const delay = 300; // 每个字段显示间隔
|
||||||
|
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const [key, value] = fields[i];
|
||||||
|
|
||||||
|
// 显示所有字段,包括空值(会在组件中显示为"暂无")
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
onProgress({ key, value });
|
||||||
|
progressValue += Math.floor(60 / fields.length);
|
||||||
|
progressRef.value?.updateProgress(Math.min(progressValue, 80));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成
|
||||||
|
onComplete(result.data);
|
||||||
|
} else {
|
||||||
|
onError(new Error(result.message || "请求失败"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理流式数据
|
||||||
|
const handleStreamData = (data, caseType) => {
|
||||||
|
const { key, value } = data;
|
||||||
|
|
||||||
|
// 添加检测到的信息
|
||||||
|
progressRef.value?.addDetectedInfo(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理完成
|
||||||
|
const handleComplete = (finalData, caseType) => {
|
||||||
|
progressRef.value?.updateProgress(90);
|
||||||
|
progressRef.value?.setGenerating(true);
|
||||||
|
|
||||||
|
// 延迟后完成
|
||||||
|
setTimeout(() => {
|
||||||
|
progressRef.value?.updateProgress(100);
|
||||||
|
progressRef.value?.setGenerating(false);
|
||||||
|
|
||||||
|
// 延迟后显示操作按钮(不自动跳转)
|
||||||
|
setTimeout(() => {
|
||||||
|
progressRef.value?.setCompleted(true, finalData);
|
||||||
|
}, 500);
|
||||||
|
}, 800);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理从进度弹窗点击重新生成
|
// 处理从进度弹窗点击重新生成
|
||||||
const handleRegenerateFromProgress = (data) => {
|
const handleRegenerateFromProgress = (data) => {
|
||||||
emit("regenerateFromProgress", data);
|
const type = { id: data.caseType };
|
||||||
|
handleCaseTypeSelect(type);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理从进度弹窗点击下一步
|
// 处理从进度弹窗点击下一步
|
||||||
const handleNextFromProgress = (data) => {
|
const handleNextFromProgress = (data) => {
|
||||||
emit("nextFromProgress", data);
|
// 根据病历类型动态构建表单数据
|
||||||
|
const extractedData = data.data?.extractedData || {};
|
||||||
|
|
||||||
|
// 跳转到病历填写页面
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/case/ai-medical-case-form?caseType=${data.caseType}&patientId=${
|
||||||
|
props.patientId
|
||||||
|
}&groupId=${props.groupId}&formData=${encodeURIComponent(
|
||||||
|
JSON.stringify(extractedData)
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听重新生成事件
|
// 监听重新生成事件
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<view class="input-area">
|
<view class="input-area">
|
||||||
<textarea 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" @input="handleInput"
|
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
|
||||||
:auto-height="true" :show-confirm-bar="false" :hold-keyboard="true" :cursor-spacing="40"
|
:auto-height="true" :show-confirm-bar="false" :hold-keyboard="true"
|
||||||
ref="textareaRef"
|
ref="textareaRef"
|
||||||
/>
|
/>
|
||||||
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
|
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
|
||||||
|
|||||||
@ -73,7 +73,7 @@ const CASE_TYPE_NAMES = {
|
|||||||
outpatient: "门诊病历",
|
outpatient: "门诊病历",
|
||||||
inhospital: "住院病历",
|
inhospital: "住院病历",
|
||||||
physicalExaminationTemplate: "体检记录",
|
physicalExaminationTemplate: "体检记录",
|
||||||
preConsultationRecord: "预问诊记录",
|
preConsultation: "预问诊记录",
|
||||||
};
|
};
|
||||||
|
|
||||||
const FIELD_LABELS = {
|
const FIELD_LABELS = {
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const caseTypes = [
|
|||||||
name: "体检记录",
|
name: "体检记录",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "preConsultationRecord",
|
id: "preConsultation",
|
||||||
name: "预问诊记录",
|
name: "预问诊记录",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -165,30 +165,12 @@
|
|||||||
:patientAccountId="chatInfo.userID || ''"
|
:patientAccountId="chatInfo.userID || ''"
|
||||||
:patientId="patientId"
|
:patientId="patientId"
|
||||||
:corpId="corpId"
|
:corpId="corpId"
|
||||||
:typeSelectorRef="typeSelectorRef"
|
|
||||||
:progressRef="progressRef"
|
|
||||||
@streamText="handleStreamText"
|
@streamText="handleStreamText"
|
||||||
@clearInput="handleClearInput"
|
@clearInput="handleClearInput"
|
||||||
@generatingStateChange="handleGeneratingStateChange"
|
@generatingStateChange="handleGeneratingStateChange"
|
||||||
@caseTypeSelect="handleCaseTypeSelect"
|
|
||||||
@regenerateFromProgress="handleRegenerateFromProgress"
|
|
||||||
@nextFromProgress="handleNextFromProgress"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</ChatInput>
|
</ChatInput>
|
||||||
|
|
||||||
<!-- 病历类型选择弹窗 -->
|
|
||||||
<MedicalCaseTypeSelector
|
|
||||||
ref="typeSelectorRef"
|
|
||||||
@select="handleCaseTypeSelect"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 进度显示弹窗 -->
|
|
||||||
<MedicalCaseProgress
|
|
||||||
ref="progressRef"
|
|
||||||
@regenerate="handleRegenerateFromProgress"
|
|
||||||
@next="handleNextFromProgress"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -224,9 +206,6 @@ 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";
|
import AIAssistantButtons from "./components/ai-assistant-buttons.vue";
|
||||||
import MedicalCaseTypeSelector from "./components/medical-case-type-selector.vue";
|
|
||||||
import MedicalCaseProgress from "./components/medical-case-progress.vue";
|
|
||||||
import request from "@/utils/http.js";
|
|
||||||
|
|
||||||
const timChatManager = globalTimChatManager;
|
const timChatManager = globalTimChatManager;
|
||||||
|
|
||||||
@ -304,8 +283,6 @@ const { initIMAfterLogin } = useAccountStore();
|
|||||||
// 聊天输入组件引用
|
// 聊天输入组件引用
|
||||||
const chatInputRef = ref(null);
|
const chatInputRef = ref(null);
|
||||||
const aiAssistantRef = ref(null);
|
const aiAssistantRef = ref(null);
|
||||||
const typeSelectorRef = ref(null);
|
|
||||||
const progressRef = ref(null);
|
|
||||||
const isGenerating = ref(false);
|
const isGenerating = ref(false);
|
||||||
|
|
||||||
const groupId = ref("");
|
const groupId = ref("");
|
||||||
@ -1006,142 +983,6 @@ const handleGeneratingStateChange = (generating) => {
|
|||||||
isGenerating.value = generating;
|
isGenerating.value = generating;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理病历类型选择
|
|
||||||
const handleCaseTypeSelect = async (type) => {
|
|
||||||
try {
|
|
||||||
// 打开进度弹窗
|
|
||||||
progressRef.value?.open(type.id);
|
|
||||||
progressRef.value?.updateProgress(10);
|
|
||||||
|
|
||||||
// 调用补充病历接口(流式处理)
|
|
||||||
await requestWithStream({
|
|
||||||
url: "/getYoucanData/im",
|
|
||||||
data: {
|
|
||||||
type: "supplementMedicalCase",
|
|
||||||
groupId: groupId.value,
|
|
||||||
patientAccountId: chatInfo.value.userID || "",
|
|
||||||
corpId: corpId,
|
|
||||||
caseType: type.id,
|
|
||||||
},
|
|
||||||
onProgress: (data) => {
|
|
||||||
// 处理流式数据
|
|
||||||
handleStreamData(data, type.id);
|
|
||||||
},
|
|
||||||
onComplete: (finalData) => {
|
|
||||||
// 完成后跳转
|
|
||||||
handleComplete(finalData, type.id);
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
progressRef.value?.close();
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || "生成病历失败",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("补充病历失败:", error);
|
|
||||||
progressRef.value?.close();
|
|
||||||
uni.showToast({
|
|
||||||
title: "操作失败,请重试",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 流式请求处理
|
|
||||||
const requestWithStream = async ({
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
onProgress,
|
|
||||||
onComplete,
|
|
||||||
onError,
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
// 调用接口时不显示全局 loading(第二个参数为 false)
|
|
||||||
const result = await request(
|
|
||||||
{
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.success && result.data) {
|
|
||||||
// 模拟流式处理(如果后端返回的是完整数据)
|
|
||||||
const extractedData = result.data.extractedData || {};
|
|
||||||
|
|
||||||
// 逐个字段动态显示(包括空值字段)
|
|
||||||
let progressValue = 20;
|
|
||||||
const fields = Object.entries(extractedData);
|
|
||||||
const delay = 300; // 每个字段显示间隔
|
|
||||||
|
|
||||||
for (let i = 0; i < fields.length; i++) {
|
|
||||||
const [key, value] = fields[i];
|
|
||||||
|
|
||||||
// 显示所有字段,包括空值(会在组件中显示为"暂无")
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
||||||
onProgress({ key, value });
|
|
||||||
progressValue += Math.floor(60 / fields.length);
|
|
||||||
progressRef.value?.updateProgress(Math.min(progressValue, 80));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完成
|
|
||||||
onComplete(result.data);
|
|
||||||
} else {
|
|
||||||
onError(new Error(result.message || "请求失败"));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
onError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理流式数据
|
|
||||||
const handleStreamData = (data, caseType) => {
|
|
||||||
const { key, value } = data;
|
|
||||||
|
|
||||||
// 添加检测到的信息
|
|
||||||
progressRef.value?.addDetectedInfo(key, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理完成
|
|
||||||
const handleComplete = (finalData, caseType) => {
|
|
||||||
progressRef.value?.updateProgress(90);
|
|
||||||
progressRef.value?.setGenerating(true);
|
|
||||||
|
|
||||||
// 延迟后完成
|
|
||||||
setTimeout(() => {
|
|
||||||
progressRef.value?.updateProgress(100);
|
|
||||||
progressRef.value?.setGenerating(false);
|
|
||||||
|
|
||||||
// 延迟后显示操作按钮(不自动跳转)
|
|
||||||
setTimeout(() => {
|
|
||||||
progressRef.value?.setCompleted(true, finalData);
|
|
||||||
}, 500);
|
|
||||||
}, 800);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理从进度弹窗点击重新生成
|
|
||||||
const handleRegenerateFromProgress = (data) => {
|
|
||||||
const type = { id: data.caseType };
|
|
||||||
handleCaseTypeSelect(type);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理从进度弹窗点击下一步
|
|
||||||
const handleNextFromProgress = (data) => {
|
|
||||||
// 根据病历类型动态构建表单数据
|
|
||||||
const extractedData = data.data?.extractedData || {};
|
|
||||||
|
|
||||||
// 跳转到病历填写页面
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/case/ai-medical-case-form?caseType=${data.caseType}&patientId=${
|
|
||||||
patientId.value
|
|
||||||
}&groupId=${groupId.value}&formData=${encodeURIComponent(
|
|
||||||
JSON.stringify(extractedData)
|
|
||||||
)}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 暴露方法给常用语页面调用
|
// 暴露方法给常用语页面调用
|
||||||
defineExpose({
|
defineExpose({
|
||||||
sendCommonPhrase,
|
sendCommonPhrase,
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
<view class="flex flex-col justify-center h-full bg-white">
|
<view class="flex flex-col justify-center h-full bg-white">
|
||||||
<view v-if="list.length === 0" class="w-full">
|
<view v-if="list.length === 0" class="w-full">
|
||||||
<empty-data text="暂无团队" />
|
<empty-data text="暂无团队" />
|
||||||
<view class="mt-10 mx-auto w-100 text-base text-primary border-auto leading-normal py-5 text-center rounded"
|
<view class="mt-10 mx-auto w-100 text-base text-primary border-auto leading-normal py-5 text-center rounded">
|
||||||
@click="toCreateTeam()">
|
|
||||||
创建团队
|
创建团队
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -88,10 +87,6 @@ const indicator = computed(() => ({
|
|||||||
}))
|
}))
|
||||||
const team = computed(() => list.value[current.value] || null);
|
const team = computed(() => list.value[current.value] || null);
|
||||||
|
|
||||||
function toCreateTeam() {
|
|
||||||
uni.navigateTo({ url: '/pages/work/team/edit/team-edit' })
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle(val) {
|
function toggle(val) {
|
||||||
const num = current.value + val;
|
const num = current.value + val;
|
||||||
if (num > 0) {
|
if (num > 0) {
|
||||||
|
|||||||
@ -180,29 +180,28 @@ export async function sendArticleMessage(article, options = {}) {
|
|||||||
url: article.url || '',
|
url: article.url || '',
|
||||||
messageType: 'article',
|
messageType: 'article',
|
||||||
};
|
};
|
||||||
if (options.articleId && options.userId && options.customerId && options.corpId) {
|
|
||||||
const params = {
|
|
||||||
articleId: options.articleId,
|
|
||||||
userId: options.userId,
|
|
||||||
customerId: options.customerId,
|
|
||||||
corpId: options.corpId,
|
|
||||||
uniqueRecord: 'YES'
|
|
||||||
};
|
|
||||||
if (options.teamId) {
|
|
||||||
params.teamId = options.teamId;
|
|
||||||
}
|
|
||||||
const res = await api('addArticleSendRecord', params);
|
|
||||||
if (!res || !res.success) {
|
|
||||||
toast('发送文章失败');
|
|
||||||
return
|
|
||||||
}
|
|
||||||
customMessageData.sendId = res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送自定义消息
|
// 发送自定义消息
|
||||||
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
|
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
|
// 记录文章发送记录(异步,不阻塞)
|
||||||
|
if (options.articleId && options.userId && options.customerId && options.corpId) {
|
||||||
|
const params = {
|
||||||
|
articleId: options.articleId,
|
||||||
|
userId: options.userId,
|
||||||
|
customerId: options.customerId,
|
||||||
|
corpId: options.corpId,
|
||||||
|
uniqueRecord: 'YES'
|
||||||
|
};
|
||||||
|
if (options.teamId) {
|
||||||
|
params.teamId = options.teamId;
|
||||||
|
}
|
||||||
|
api('addArticleSendRecord', params).catch((err) => {
|
||||||
|
console.error('记录文章发送失败:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 写入服务记录留痕(异步,不阻塞)
|
// 写入服务记录留痕(异步,不阻塞)
|
||||||
if (canWriteServiceRecord(options)) {
|
if (canWriteServiceRecord(options)) {
|
||||||
const base = normalizeServiceRecordBase(options);
|
const base = normalizeServiceRecordBase(options);
|
||||||
@ -288,13 +287,8 @@ export async function sendSurveyMessage(survey, options = {}) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 构建自定义消息
|
||||||
const customMessageData = buildSurveyMessage(survey, surveyLink);
|
const customMessageData = buildSurveyMessage(survey, surveyLink);
|
||||||
customMessageData.name = options.customerName;
|
|
||||||
customMessageData.memberId = options.customerId;
|
|
||||||
customMessageData.corpId = options.corpId;
|
|
||||||
customMessageData.answerId = answerId;
|
|
||||||
customMessageData.surveryId = survey.surveryId;
|
|
||||||
customMessageData.isSystem = survey.createBy === 'system';
|
|
||||||
|
|
||||||
// 发送自定义消息 - 直接传递消息对象,不再进行额外序列化
|
// 发送自定义消息 - 直接传递消息对象,不再进行额外序列化
|
||||||
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
|
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user