feat: 病历模块优化、AI辅助问诊
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<!--
|
<!--
|
||||||
AI写病历:主弹层=历史+预览;二级=确认主诉生成
|
AI写病历:单根 u-popup(Vue2 不能双根)
|
||||||
无历史自动打开生成框;点生成先关二级,主层等待
|
生成确认用抽屉内面板切换,避免第二个 popup 无反应
|
||||||
模板禁止 ?? ?. ;:class 禁止方法调用
|
模板禁止 ?? ?. ;:class 禁止方法调用
|
||||||
-->
|
-->
|
||||||
<u-popup
|
<u-popup
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<text class="ai-mr-title">AI写病历</text>
|
<text class="ai-mr-title">AI写病历</text>
|
||||||
<text class="ai-mr-sub">{{ patientName || '—' }} · {{ sexLabel }} · {{ patientAge || 0 }}岁</text>
|
<text class="ai-mr-sub">{{ patientName || '—' }} · {{ sexLabel }} · {{ patientAge || 0 }}岁</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="ai-mr-body flex-row">
|
<view v-if="!genModalVisible" class="ai-mr-body flex-row">
|
||||||
<scroll-view scroll-y class="ai-mr-hist">
|
<scroll-view scroll-y class="ai-mr-hist">
|
||||||
<view class="ai-hist-hd">
|
<view class="ai-hist-hd">
|
||||||
<text class="fs-24 font-bold">历史</text>
|
<text class="fs-24 font-bold">历史</text>
|
||||||
@@ -46,74 +46,66 @@
|
|||||||
</view>
|
</view>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<view v-if="previewChief" class="ai-chief-box">
|
<view v-if="previewChief" class="ai-chief-box">
|
||||||
<text class="fs-20 color-sub">主诉</text>
|
<text class="fs-20 color-sub">主诉</text>
|
||||||
<text class="fs-24 m-t-4">{{ previewChief }}</text>
|
<text class="fs-24 m-t-4">{{ previewChief }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="previewRows.length" class="ai-preview-grid">
|
<view v-if="previewRows.length" class="ai-preview-grid">
|
||||||
<view
|
<view
|
||||||
v-for="row in previewRows"
|
v-for="row in previewRows"
|
||||||
:key="row.key"
|
:key="row.key"
|
||||||
class="ai-preview-card"
|
class="ai-preview-card"
|
||||||
:class="{
|
:class="{
|
||||||
'ai-preview-card--full':
|
'ai-preview-card--full':
|
||||||
row.key == 'present_illness' ||
|
row.key == 'present_illness' ||
|
||||||
row.key == 'tcm_case' ||
|
row.key == 'tcm_case' ||
|
||||||
row.key == 'treatment_advice' ||
|
row.key == 'treatment_advice' ||
|
||||||
row.key == 'auxiliary_exam',
|
row.key == 'auxiliary_exam',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<text class="fs-20 color-sub">{{ row.label }}</text>
|
<text class="fs-20 color-sub">{{ row.label }}</text>
|
||||||
<text class="fs-24 m-t-4">{{ row.value }}</text>
|
<text class="fs-24 m-t-4">{{ row.value }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
</block>
|
</block>
|
||||||
</block>
|
</block>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-else class="ai-gen-panel">
|
||||||
|
<view class="ai-gen-hd flex-row flex-jus-sp flex-ali-center">
|
||||||
|
<text class="fs-30 font-bold">生成病历</text>
|
||||||
|
<text class="ai-mr-sub" @click="closeGenModal">返回</text>
|
||||||
|
</view>
|
||||||
|
<text class="ai-gen-tip">确认主诉后开始生成,将返回主层等待结果</text>
|
||||||
|
<view class="ai-gen-patient">
|
||||||
|
<text class="fs-24 color-sub">患者</text>
|
||||||
|
<text class="fs-26 m-t-8">{{ patientName || '—' }} · {{ sexLabel }} · {{ patientAge || 0 }}岁</text>
|
||||||
|
</view>
|
||||||
|
<view class="ai-gen-field">
|
||||||
|
<text class="fs-24 color-sub">主诉(可编辑)</text>
|
||||||
|
<textarea
|
||||||
|
class="ai-gen-textarea"
|
||||||
|
:value="aiChief"
|
||||||
|
maxlength="500"
|
||||||
|
placeholder="请确认或补充主诉"
|
||||||
|
@input="onAiChiefInput"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view class="ai-mr-footer safe-area-inset-bottom">
|
<view class="ai-mr-footer safe-area-inset-bottom">
|
||||||
<view class="ai-actions">
|
<view v-if="!genModalVisible" class="ai-actions">
|
||||||
<view class="ai-btn ai-btn--ghost" @click="onClose">关闭</view>
|
<view class="ai-btn ai-btn--ghost" @click="onClose">关闭</view>
|
||||||
<view class="ai-btn ai-btn--ghost" @click="openGenModal">生成病历</view>
|
<view class="ai-btn ai-btn--ghost" @click="openGenModal">生成病历</view>
|
||||||
<view class="ai-btn ai-btn--primary" @click="onConfirmImport">确认导入</view>
|
<view class="ai-btn ai-btn--primary" @click="onConfirmImport">确认导入</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-else class="ai-actions">
|
||||||
|
<view class="ai-btn ai-btn--ghost" @click="closeGenModal">返回</view>
|
||||||
|
<view class="ai-btn ai-btn--primary" @click="onGenerate">
|
||||||
|
{{ generating ? '生成中…' : '开始生成' }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
|
||||||
<page-container
|
|
||||||
v-if="genModalVisible"
|
|
||||||
:show="genModalVisible"
|
|
||||||
overlay
|
|
||||||
round
|
|
||||||
position="bottom"
|
|
||||||
@afterleave="onGenModalLeave"
|
|
||||||
@clickoverlay="closeGenModal"
|
|
||||||
>
|
|
||||||
<view class="ai-gen-sheet">
|
|
||||||
<view class="ai-gen-hd flex-row flex-jus-sp flex-ali-center">
|
|
||||||
<text class="fs-30 font-bold">生成病历</text>
|
|
||||||
<text class="ai-mr-sub" @click="closeGenModal">关闭</text>
|
|
||||||
</view>
|
|
||||||
<text class="ai-gen-tip">确认主诉后开始生成,将关闭本弹窗并在主层等待</text>
|
|
||||||
<view class="ai-gen-patient">
|
|
||||||
<text class="fs-24 color-sub">患者</text>
|
|
||||||
<text class="fs-26 m-t-8">{{ patientName || '—' }} · {{ sexLabel }} · {{ patientAge || 0 }}岁</text>
|
|
||||||
</view>
|
|
||||||
<view class="ai-gen-field">
|
|
||||||
<text class="fs-24 color-sub">主诉(可编辑)</text>
|
|
||||||
<textarea
|
|
||||||
class="ai-gen-textarea"
|
|
||||||
:value="aiChief"
|
|
||||||
maxlength="500"
|
|
||||||
placeholder="请确认或补充主诉"
|
|
||||||
@input="onAiChiefInput"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<view class="ai-mr-footer">
|
|
||||||
<view class="ai-btn ai-btn--primary ai-btn--block" @click="onGenerate">开始生成</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</page-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -216,7 +208,19 @@ export default {
|
|||||||
this.visible = false
|
this.visible = false
|
||||||
this.genModalVisible = false
|
this.genModalVisible = false
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Laravel jok 业务在 result;兼容 Yii data / 已解包对象
|
||||||
|
*/
|
||||||
|
unwrapBiz(res) {
|
||||||
|
if (res == null) return null
|
||||||
|
if (res.result != null) return res.result
|
||||||
|
if (res.data != null) return res.data
|
||||||
|
return res
|
||||||
|
},
|
||||||
formatTime(ts) {
|
formatTime(ts) {
|
||||||
|
if (typeof ts === 'string' && ts && Number.isNaN(Number(ts))) {
|
||||||
|
return ts.length > 16 ? ts.slice(5, 16) : ts
|
||||||
|
}
|
||||||
const n = Number(ts || 0)
|
const n = Number(ts || 0)
|
||||||
if (!n) return '—'
|
if (!n) return '—'
|
||||||
const d = new Date(n * 1000)
|
const d = new Date(n * 1000)
|
||||||
@@ -250,25 +254,27 @@ export default {
|
|||||||
scene: 'medical_record',
|
scene: 'medical_record',
|
||||||
limit: 30,
|
limit: 30,
|
||||||
})
|
})
|
||||||
const list = (res && res.data) || res || []
|
const list = this.unwrapBiz(res)
|
||||||
this.historyList = Array.isArray(list) ? list : []
|
this.historyList = Array.isArray(list) ? list : []
|
||||||
this.rebuildHistoryView()
|
this.rebuildHistoryView()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.historyList = []
|
this.historyList = []
|
||||||
this.rebuildHistoryView()
|
this.rebuildHistoryView()
|
||||||
|
const msg = (e && e.message) || (e && e.msg) || ''
|
||||||
|
if (msg) uni.showToast({ title: String(msg), icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
this.histLoading = false
|
this.histLoading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openGenModal() {
|
openGenModal() {
|
||||||
this.genModalVisible = true
|
// 抽屉内切到生成面板,避免 Vue2 双根第二个 popup 无效
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.genModalVisible = true
|
||||||
|
})
|
||||||
},
|
},
|
||||||
closeGenModal() {
|
closeGenModal() {
|
||||||
this.genModalVisible = false
|
this.genModalVisible = false
|
||||||
},
|
},
|
||||||
onGenModalLeave() {
|
|
||||||
this.genModalVisible = false
|
|
||||||
},
|
|
||||||
onAiChiefInput(e) {
|
onAiChiefInput(e) {
|
||||||
this.aiChief = (e && e.detail && e.detail.value) || ''
|
this.aiChief = (e && e.detail && e.detail.value) || ''
|
||||||
},
|
},
|
||||||
@@ -294,7 +300,7 @@ export default {
|
|||||||
store_id: Number(this.storeId || 0) || undefined,
|
store_id: Number(this.storeId || 0) || undefined,
|
||||||
chief_complaint: chief,
|
chief_complaint: chief,
|
||||||
})
|
})
|
||||||
const data = (res && res.data) || res || {}
|
const data = this.unwrapBiz(res) || {}
|
||||||
this.previewFields = data.fields || {}
|
this.previewFields = data.fields || {}
|
||||||
this.rebuildPreviewRows()
|
this.rebuildPreviewRows()
|
||||||
await this.loadHistory()
|
await this.loadHistory()
|
||||||
@@ -320,7 +326,7 @@ export default {
|
|||||||
this.detailLoading = true
|
this.detailLoading = true
|
||||||
try {
|
try {
|
||||||
const res = await aiGenerationDetailApi({ id: id })
|
const res = await aiGenerationDetailApi({ id: id })
|
||||||
const data = (res && res.data) || res || {}
|
const data = this.unwrapBiz(res) || {}
|
||||||
this.previewFields = data.fields || {}
|
this.previewFields = data.fields || {}
|
||||||
const snap = data.input_snapshot || {}
|
const snap = data.input_snapshot || {}
|
||||||
this.previewChief = String(snap.chief_complaint || this.aiChief || '').trim()
|
this.previewChief = String(snap.chief_complaint || this.aiChief || '').trim()
|
||||||
@@ -359,6 +365,17 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.ai-gen-panel {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.ai-mr-title-row {
|
.ai-mr-title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -470,16 +487,8 @@ export default {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
.ai-gen-sheet {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 24rpx 24rpx 0 0;
|
|
||||||
max-height: 78vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
.ai-gen-hd {
|
.ai-gen-hd {
|
||||||
padding: 24rpx 32rpx 8rpx;
|
padding: 16rpx 32rpx 8rpx;
|
||||||
}
|
}
|
||||||
.ai-gen-tip {
|
.ai-gen-tip {
|
||||||
padding: 0 32rpx 12rpx;
|
padding: 0 32rpx 12rpx;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<!--
|
<!--
|
||||||
AI辅助出方:主弹层=历史+对照;二级弹层=可编辑病历预览+生成
|
AI辅助出方:单根 u-popup(Vue2 组件不能双根)
|
||||||
|
生成确认用抽屉内全屏覆盖层,避免第二个 popup / page-container 点了无反应
|
||||||
模板禁止 ?? ?. ;:class 禁止方法调用
|
模板禁止 ?? ?. ;:class 禁止方法调用
|
||||||
-->
|
-->
|
||||||
<u-popup
|
<u-popup
|
||||||
@@ -18,11 +19,12 @@
|
|||||||
<text class="ai-rx-title">AI辅助出方</text>
|
<text class="ai-rx-title">AI辅助出方</text>
|
||||||
<text class="ai-rx-sub">{{ patientName || '—' }} · {{ sexLabel }} · {{ patientAge || 0 }}岁</text>
|
<text class="ai-rx-sub">{{ patientName || '—' }} · {{ sexLabel }} · {{ patientAge || 0 }}岁</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="conflictView.length" class="ai-conflict">
|
<view v-if="conflictView.length && !genModalVisible" class="ai-conflict">
|
||||||
<text class="ai-conflict-title">配伍禁忌提示</text>
|
<text class="ai-conflict-title">配伍禁忌提示</text>
|
||||||
<view v-for="(line, ci) in conflictView" :key="ci" class="ai-conflict-line">· {{ line }}</view>
|
<view v-for="(line, ci) in conflictView" :key="ci" class="ai-conflict-line">· {{ line }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="ai-rx-body flex-row">
|
<!-- 主内容:历史 + 对照 -->
|
||||||
|
<view v-if="!genModalVisible" class="ai-rx-body flex-row">
|
||||||
<scroll-view scroll-y class="ai-rx-hist">
|
<scroll-view scroll-y class="ai-rx-hist">
|
||||||
<view class="ai-hist-hd">
|
<view class="ai-hist-hd">
|
||||||
<text class="fs-24 font-bold">历史</text>
|
<text class="fs-24 font-bold">历史</text>
|
||||||
@@ -47,7 +49,6 @@
|
|||||||
<view v-if="!matchResult" class="p-32 color-sub fs-24">请选择历史或生成处方</view>
|
<view v-if="!matchResult" class="p-32 color-sub fs-24">请选择历史或生成处方</view>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<view class="ai-sec-title">已对照({{ matchedCount }})</view>
|
<view class="ai-sec-title">已对照({{ matchedCount }})</view>
|
||||||
<!-- 中药一行多味 -->
|
|
||||||
<view v-if="isTcmRx" class="ai-tcm-grid">
|
<view v-if="isTcmRx" class="ai-tcm-grid">
|
||||||
<view v-for="m in matchedView" :key="m.mi" class="ai-tcm-cell">
|
<view v-for="m in matchedView" :key="m.mi" class="ai-tcm-cell">
|
||||||
<view class="flex-row flex-ali-center flex-jus-sp">
|
<view class="flex-row flex-ali-center flex-jus-sp">
|
||||||
@@ -113,7 +114,6 @@
|
|||||||
<text class="fs-26">{{ u.ai_name }}</text>
|
<text class="fs-26">{{ u.ai_name }}</text>
|
||||||
<text class="ai-link m-t-8" :data-ui="u.ui" @click="onSearchUnmatchedTap">去手动搜索</text>
|
<text class="ai-link m-t-8" :data-ui="u.ui" @click="onSearchUnmatchedTap">去手动搜索</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 药方摘要:仿 Descriptions 双列紧凑 -->
|
|
||||||
<view
|
<view
|
||||||
v-if="rxPrescriptionName || rxReason || rxBasis || rxDosage || rxDayDosage"
|
v-if="rxPrescriptionName || rxReason || rxBasis || rxDosage || rxDayDosage"
|
||||||
class="ai-rx-meta m-t-16"
|
class="ai-rx-meta m-t-16"
|
||||||
@@ -143,67 +143,57 @@
|
|||||||
</block>
|
</block>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- 二级:抽屉内全屏生成面板(不用第二个 popup) -->
|
||||||
|
<view v-else class="ai-gen-panel">
|
||||||
|
<view class="ai-gen-hd flex-row flex-jus-sp flex-ali-center">
|
||||||
|
<text class="fs-30 font-bold">生成处方</text>
|
||||||
|
<text class="ai-rx-sub" @click="closeGenModal">返回</text>
|
||||||
|
</view>
|
||||||
|
<text class="ai-gen-tip">点击文字可编辑,将同步到病历 · {{ typeLabel }}</text>
|
||||||
|
<scroll-view scroll-y class="ai-gen-scroll">
|
||||||
|
<view class="ai-gen-grid">
|
||||||
|
<view
|
||||||
|
v-for="row in genPreviewView"
|
||||||
|
:key="row.key"
|
||||||
|
class="ai-gen-row"
|
||||||
|
:class="{ 'ai-gen-row--full': row.key == 'chief_complaint' }"
|
||||||
|
>
|
||||||
|
<text class="ai-gen-label">{{ row.label }}</text>
|
||||||
|
<textarea
|
||||||
|
v-if="editingKey == row.key"
|
||||||
|
class="ai-gen-textarea"
|
||||||
|
:value="row.value"
|
||||||
|
:data-key="row.key"
|
||||||
|
auto-height
|
||||||
|
maxlength="2000"
|
||||||
|
@input="onGenFieldInput"
|
||||||
|
@blur="onGenFieldBlur"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
v-else
|
||||||
|
class="ai-gen-val"
|
||||||
|
:data-key="row.key"
|
||||||
|
@click="onStartEditTap"
|
||||||
|
>{{ row.value || '(空,点击填写)' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
<view class="ai-rx-footer safe-area-inset-bottom">
|
<view class="ai-rx-footer safe-area-inset-bottom">
|
||||||
<view class="ai-actions">
|
<view v-if="!genModalVisible" class="ai-actions">
|
||||||
<view class="ai-btn ai-btn--ghost" @click="onClose">关闭</view>
|
<view class="ai-btn ai-btn--ghost" @click="onClose">关闭</view>
|
||||||
<view class="ai-btn ai-btn--ghost" @click="openGenModal">生成处方</view>
|
<view class="ai-btn ai-btn--ghost" @click="openGenModal">生成处方</view>
|
||||||
<view class="ai-btn ai-btn--primary" @click="onConfirmImport">确认导入</view>
|
<view class="ai-btn ai-btn--primary" @click="onConfirmImport">确认导入</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-else class="ai-actions">
|
||||||
|
<view class="ai-btn ai-btn--ghost" @click="closeGenModal">返回</view>
|
||||||
|
<view class="ai-btn ai-btn--primary" @click="onGenerate">
|
||||||
|
{{ generating ? '生成中…' : '开始生成' }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
|
||||||
<!-- 二级:可编辑病历预览 + 生成(用 page-container 防误退) -->
|
|
||||||
<page-container
|
|
||||||
v-if="genModalVisible"
|
|
||||||
:show="genModalVisible"
|
|
||||||
overlay
|
|
||||||
round
|
|
||||||
position="bottom"
|
|
||||||
@afterleave="onGenModalLeave"
|
|
||||||
@clickoverlay="closeGenModal"
|
|
||||||
>
|
|
||||||
<view class="ai-gen-sheet">
|
|
||||||
<view class="ai-gen-hd flex-row flex-jus-sp flex-ali-center">
|
|
||||||
<text class="fs-30 font-bold">生成处方</text>
|
|
||||||
<text class="ai-rx-sub" @click="closeGenModal">关闭</text>
|
|
||||||
</view>
|
|
||||||
<text class="ai-gen-tip">点击文字可编辑,将同步到病历 · {{ typeLabel }}</text>
|
|
||||||
<scroll-view scroll-y class="ai-gen-scroll">
|
|
||||||
<view class="ai-gen-grid">
|
|
||||||
<view
|
|
||||||
v-for="row in genPreviewView"
|
|
||||||
:key="row.key"
|
|
||||||
class="ai-gen-row"
|
|
||||||
:class="{ 'ai-gen-row--full': row.key == 'chief_complaint' }"
|
|
||||||
>
|
|
||||||
<text class="ai-gen-label">{{ row.label }}</text>
|
|
||||||
<textarea
|
|
||||||
v-if="editingKey == row.key"
|
|
||||||
class="ai-gen-textarea"
|
|
||||||
:value="row.value"
|
|
||||||
:data-key="row.key"
|
|
||||||
auto-height
|
|
||||||
maxlength="2000"
|
|
||||||
@input="onGenFieldInput"
|
|
||||||
@blur="onGenFieldBlur"
|
|
||||||
/>
|
|
||||||
<text
|
|
||||||
v-else
|
|
||||||
class="ai-gen-val"
|
|
||||||
:data-key="row.key"
|
|
||||||
@click="onStartEditTap"
|
|
||||||
>{{ row.value || '(空,点击填写)' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
<view class="ai-rx-footer">
|
|
||||||
<view class="ai-btn ai-btn--primary ai-btn--block" @click="onGenerate">
|
|
||||||
{{ generating ? '生成中…' : '开始生成' }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</page-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -347,7 +337,20 @@ export default {
|
|||||||
this.visible = false
|
this.visible = false
|
||||||
this.genModalVisible = false
|
this.genModalVisible = false
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Laravel jok 业务在 result;兼容 Yii data / 已解包数组
|
||||||
|
*/
|
||||||
|
unwrapBiz(res) {
|
||||||
|
if (res == null) return null
|
||||||
|
if (res.result != null) return res.result
|
||||||
|
if (res.data != null) return res.data
|
||||||
|
return res
|
||||||
|
},
|
||||||
formatTime(ts) {
|
formatTime(ts) {
|
||||||
|
// 已是格式化字符串则直接展示(查询器可能一级格式化)
|
||||||
|
if (typeof ts === 'string' && ts && Number.isNaN(Number(ts))) {
|
||||||
|
return ts.length > 16 ? ts.slice(5, 16) : ts
|
||||||
|
}
|
||||||
const n = Number(ts || 0)
|
const n = Number(ts || 0)
|
||||||
if (!n) return '—'
|
if (!n) return '—'
|
||||||
const d = new Date(n * 1000)
|
const d = new Date(n * 1000)
|
||||||
@@ -448,12 +451,14 @@ export default {
|
|||||||
scene: 'prescription',
|
scene: 'prescription',
|
||||||
limit: 30,
|
limit: 30,
|
||||||
})
|
})
|
||||||
const list = (res && res.data) || res || []
|
const list = this.unwrapBiz(res)
|
||||||
this.historyList = Array.isArray(list) ? list : []
|
this.historyList = Array.isArray(list) ? list : []
|
||||||
this.rebuildHistoryView()
|
this.rebuildHistoryView()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.historyList = []
|
this.historyList = []
|
||||||
this.rebuildHistoryView()
|
this.rebuildHistoryView()
|
||||||
|
const msg = (e && e.message) || (e && e.msg) || ''
|
||||||
|
if (msg) uni.showToast({ title: String(msg), icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
this.histLoading = false
|
this.histLoading = false
|
||||||
}
|
}
|
||||||
@@ -463,17 +468,17 @@ export default {
|
|||||||
uni.showToast({ title: '请先选择处方类型', icon: 'none' })
|
uni.showToast({ title: '请先选择处方类型', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 抽屉内切到生成面板(单根 u-popup,避免 Vue2 双根第二个挂不上)
|
||||||
this.editingKey = ''
|
this.editingKey = ''
|
||||||
this.rebuildGenPreview()
|
this.rebuildGenPreview()
|
||||||
this.genModalVisible = true
|
this.$nextTick(() => {
|
||||||
|
this.genModalVisible = true
|
||||||
|
})
|
||||||
},
|
},
|
||||||
closeGenModal() {
|
closeGenModal() {
|
||||||
this.genModalVisible = false
|
this.genModalVisible = false
|
||||||
this.editingKey = ''
|
this.editingKey = ''
|
||||||
},
|
},
|
||||||
onGenModalLeave() {
|
|
||||||
this.genModalVisible = false
|
|
||||||
},
|
|
||||||
onStartEditTap(e) {
|
onStartEditTap(e) {
|
||||||
const key = e && e.currentTarget && e.currentTarget.dataset
|
const key = e && e.currentTarget && e.currentTarget.dataset
|
||||||
? e.currentTarget.dataset.key
|
? e.currentTarget.dataset.key
|
||||||
@@ -535,7 +540,7 @@ export default {
|
|||||||
chief_complaint: chief,
|
chief_complaint: chief,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const data = (res && res.data) || res || {}
|
const data = this.unwrapBiz(res) || {}
|
||||||
await this.loadHistory()
|
await this.loadHistory()
|
||||||
this.activeId = Number(data.generation_id || 0)
|
this.activeId = Number(data.generation_id || 0)
|
||||||
this.applyMatch(data.match || null)
|
this.applyMatch(data.match || null)
|
||||||
@@ -578,7 +583,7 @@ export default {
|
|||||||
store_id: Number(this.storeId || 0) || undefined,
|
store_id: Number(this.storeId || 0) || undefined,
|
||||||
prescription_type: pt,
|
prescription_type: pt,
|
||||||
})
|
})
|
||||||
const data = (res && res.data) || res || {}
|
const data = this.unwrapBiz(res) || {}
|
||||||
this.applyMatch(data)
|
this.applyMatch(data)
|
||||||
this.applyConflict(data.conflict || null)
|
this.applyConflict(data.conflict || null)
|
||||||
this.applyRxMeta({
|
this.applyRxMeta({
|
||||||
@@ -668,6 +673,16 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.ai-gen-panel {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
.ai-rx-title-row {
|
.ai-rx-title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -884,16 +899,8 @@ export default {
|
|||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.ai-gen-sheet {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 24rpx 24rpx 0 0;
|
|
||||||
max-height: 78vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
.ai-gen-hd {
|
.ai-gen-hd {
|
||||||
padding: 24rpx 32rpx 8rpx;
|
padding: 16rpx 32rpx 8rpx;
|
||||||
}
|
}
|
||||||
.ai-gen-tip {
|
.ai-gen-tip {
|
||||||
padding: 0 32rpx 12rpx;
|
padding: 0 32rpx 12rpx;
|
||||||
@@ -901,9 +908,12 @@ export default {
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
.ai-gen-scroll {
|
.ai-gen-scroll {
|
||||||
max-height: 52vh;
|
flex: 1;
|
||||||
padding: 0 24rpx;
|
height: 0;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0 24rpx 16rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background: #f7f8fa;
|
||||||
}
|
}
|
||||||
.ai-gen-grid {
|
.ai-gen-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ export default {
|
|||||||
// AI写病历抽屉显隐
|
// AI写病历抽屉显隐
|
||||||
aiShow: false,
|
aiShow: false,
|
||||||
_draftTimer: null,
|
_draftTimer: null,
|
||||||
areaStyle: { background: '#f4f6f8', borderRadius: '12rpx', padding: '18rpx', minHeight: '120rpx', fontSize: '28rpx', color: '#1a2530', lineHeight: '1.6' },
|
areaStyle: { background: '#f4f6f8', borderRadius: '12rpx', padding: '18rpx', minHeight: '80rpx', fontSize: '28rpx', color: '#1a2530', lineHeight: '1.6' },
|
||||||
leftFields: [
|
leftFields: [
|
||||||
{ code: 'chief_complaint', label: '主诉' },
|
{ code: 'chief_complaint', label: '主诉' },
|
||||||
{ code: 'present_illness', label: '现病史' },
|
{ code: 'present_illness', label: '现病史' },
|
||||||
|
|||||||
@@ -237,17 +237,6 @@
|
|||||||
:ripple="true">
|
:ripple="true">
|
||||||
常用方
|
常用方
|
||||||
</u-button>
|
</u-button>
|
||||||
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
|
||||||
<u-button
|
|
||||||
:throttle-time="0"
|
|
||||||
shape="circle"
|
|
||||||
size="mini"
|
|
||||||
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
|
|
||||||
plain
|
|
||||||
@click="handleOpenAiPrescriptionDrawer">
|
|
||||||
AI辅助出方
|
|
||||||
</u-button>
|
|
||||||
</view>
|
|
||||||
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
||||||
<u-button
|
<u-button
|
||||||
:throttle-time="0"
|
:throttle-time="0"
|
||||||
@@ -327,17 +316,6 @@
|
|||||||
:ripple="true">
|
:ripple="true">
|
||||||
常用方
|
常用方
|
||||||
</u-button>
|
</u-button>
|
||||||
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
|
||||||
<u-button
|
|
||||||
:throttle-time="0"
|
|
||||||
shape="circle"
|
|
||||||
size="mini"
|
|
||||||
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
|
|
||||||
plain
|
|
||||||
@click="handleOpenAiPrescriptionDrawer">
|
|
||||||
AI辅助出方
|
|
||||||
</u-button>
|
|
||||||
</view>
|
|
||||||
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
||||||
<u-button
|
<u-button
|
||||||
:throttle-time="0"
|
:throttle-time="0"
|
||||||
@@ -407,17 +385,6 @@
|
|||||||
<text class="fs-28 text-gray m-l-8">{{ activeCategoryLabel }}</text>
|
<text class="fs-28 text-gray m-l-8">{{ activeCategoryLabel }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-row m-l-163" style="height: 50rpx;">
|
<view class="flex-row m-l-163" style="height: 50rpx;">
|
||||||
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
|
||||||
<u-button
|
|
||||||
:throttle-time="0"
|
|
||||||
shape="circle"
|
|
||||||
size="mini"
|
|
||||||
:custom-style="{backgroundColor: 'transparent !important',border:'1px solid #6ACDBB',color:'#6ACDBB'}"
|
|
||||||
plain
|
|
||||||
@click="handleOpenAiPrescriptionDrawer">
|
|
||||||
AI辅助出方
|
|
||||||
</u-button>
|
|
||||||
</view>
|
|
||||||
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
|
||||||
<u-button
|
<u-button
|
||||||
:throttle-time="0"
|
:throttle-time="0"
|
||||||
@@ -575,34 +542,39 @@
|
|||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
|
|
||||||
<!-- 底部操作栏:fixed;病历 Tab 必须 v-if 卸载,微信 v-show 对 fixed 无效会串到病历 -->
|
<!-- 底部操作栏:与病历底栏同布局(左文案 | 右 AI 胶囊 + 主按钮) -->
|
||||||
<view v-if="isRxMainVisible" class="bottom flex-jus-sp flex-ali-center safe-area-inset-bottom">
|
<view v-if="isRxMainVisible" class="bottom safe-area-inset-bottom">
|
||||||
<view class="flex-row flex-ali-center bottom-left-actions">
|
<view class="bottom-left flex-row flex-ali-center">
|
||||||
<view
|
<view
|
||||||
v-if="!isSalespersonTransferMode && !isCommonPrescription && supportsCommonPrescriptionTemplate"
|
v-if="!isSalespersonTransferMode && !isCommonPrescription && supportsCommonPrescriptionTemplate"
|
||||||
@click="handleSaveAsCommonPrescription"
|
@click="handleSaveAsCommonPrescription"
|
||||||
class="btnText"
|
class="btnText"
|
||||||
>另存常用方</view>
|
>另存常用方</view>
|
||||||
<!-- AI入口放底部,避免顶部三按钮挤出屏幕 -->
|
</view>
|
||||||
|
<view class="flex-row flex-ali-center">
|
||||||
|
<!-- AI 胶囊与病历「AI写病历」同款;功能暂不可用时仍保留入口样式 -->
|
||||||
<view
|
<view
|
||||||
v-if="!isSpecialPrescriptionCartLocked && !isSalespersonTransferMode && !isCommonPrescription"
|
v-if="!isSpecialPrescriptionCartLocked && !isSalespersonTransferMode && !isCommonPrescription"
|
||||||
|
class="rx-ai-btn"
|
||||||
@click="handleOpenAiPrescriptionDrawer"
|
@click="handleOpenAiPrescriptionDrawer"
|
||||||
class="btnText"
|
>
|
||||||
>AI辅助出方</view>
|
<text class="rx-ai-btn__icon">✦</text>
|
||||||
|
<text class="rx-ai-btn__text">AI辅助出方</text>
|
||||||
|
</view>
|
||||||
|
<u-button
|
||||||
|
:throttle-time="0"
|
||||||
|
@click="handleSendPrescription"
|
||||||
|
shape="circle"
|
||||||
|
:custom-style="{
|
||||||
|
backgroundColor: isSubmitting ? '#A5DCD2' : '#6ACDBB',
|
||||||
|
color: '#fff',
|
||||||
|
height: '86rpx',
|
||||||
|
width: bottomSendBtnWidth
|
||||||
|
}"
|
||||||
|
:disabled="isSubmitting">
|
||||||
|
{{ isCommonPrescription ? '保存常用方' : (isSalespersonTransferMode ? '提交传方' : '发送处方') }}
|
||||||
|
</u-button>
|
||||||
</view>
|
</view>
|
||||||
<u-button
|
|
||||||
:throttle-time="0"
|
|
||||||
@click="handleSendPrescription"
|
|
||||||
shape="circle"
|
|
||||||
:custom-style="{
|
|
||||||
backgroundColor: isSubmitting ? '#A5DCD2' : '#6ACDBB',
|
|
||||||
color: '#fff',
|
|
||||||
height: '86rpx',
|
|
||||||
width: bottomSendBtnWidth
|
|
||||||
}"
|
|
||||||
:disabled="isSubmitting">
|
|
||||||
{{ isCommonPrescription ? '保存常用方' : (isSalespersonTransferMode ? '提交传方' : '发送处方') }}
|
|
||||||
</u-button>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -1179,14 +1151,17 @@ export default {
|
|||||||
isSpecialPrescriptionCartLocked() {
|
isSpecialPrescriptionCartLocked() {
|
||||||
return isSpecialPrescriptionCartLocked(this);
|
return isSpecialPrescriptionCartLocked(this);
|
||||||
},
|
},
|
||||||
/** 底部发送按钮宽度(左侧有「另存常用方 / AI辅助出方」时收窄) */
|
/**
|
||||||
|
* 底部发送按钮宽度
|
||||||
|
* 与病历一致:右侧有 AI 胶囊时主按钮收窄;无 AI 时拉宽
|
||||||
|
*/
|
||||||
bottomSendBtnWidth() {
|
bottomSendBtnWidth() {
|
||||||
if (this.isSalespersonTransferMode || this.isCommonPrescription) {
|
if (this.isSalespersonTransferMode || this.isCommonPrescription) {
|
||||||
return '686rpx';
|
return '686rpx';
|
||||||
}
|
}
|
||||||
// 左侧至少有 AI辅助出方;有常用方模板时再多一个入口
|
// 右侧 AI 胶囊 + 发送,主按钮与病历「保存病历」同量级
|
||||||
if (this.supportsCommonPrescriptionTemplate) {
|
if (!this.isSpecialPrescriptionCartLocked) {
|
||||||
return '320rpx';
|
return '240rpx';
|
||||||
}
|
}
|
||||||
return '480rpx';
|
return '480rpx';
|
||||||
},
|
},
|
||||||
@@ -3698,27 +3673,59 @@ export default {
|
|||||||
.special-rx-card__action {
|
.special-rx-card__action {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
/* 与病历 mr-bottom 对齐:左文案 | 右 AI 胶囊 + 主按钮 */
|
||||||
.bottom {
|
.bottom {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 32rpx;
|
padding: 20rpx 28rpx;
|
||||||
background: #fff;
|
background: rgba(255, 255, 255, 0.96);
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1rpx solid #edf0f2;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.bottom-left {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.btnText {
|
.btnText {
|
||||||
|
font-size: 26rpx;
|
||||||
color: #6ACDBB;
|
color: #6ACDBB;
|
||||||
font-size: 28rpx;
|
padding: 12rpx 20rpx;
|
||||||
padding: 0 32rpx;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
border-radius: 40rpx;
|
||||||
}
|
}
|
||||||
.btnText--danger {
|
.btnText--danger {
|
||||||
color: #FC3636;
|
color: #f55353;
|
||||||
|
}
|
||||||
|
/* 与病历 .mr-ai-btn 同款胶囊 */
|
||||||
|
.rx-ai-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 14rpx 28rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
background: rgba(106, 205, 187, 0.1);
|
||||||
|
border: 1rpx solid rgba(106, 205, 187, 0.4);
|
||||||
|
}
|
||||||
|
.rx-ai-btn__icon {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #6ACDBB;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.rx-ai-btn__text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #6ACDBB;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user