feat: 病历模块优化、AI辅助问诊

This commit is contained in:
李琦
2026-08-06 09:41:06 +08:00
parent dec768424c
commit b2ad3088be
4 changed files with 239 additions and 213 deletions

View File

@@ -1,7 +1,7 @@
<template> <template>
<!-- <!--
AI写病历主弹层=历史+预览二级=确认主诉生成 AI写病历单根 u-popupVue2 不能双根
无历史自动打开生成框点生成先关二级主层等待 生成确认用抽屉内面板切换避免第二个 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;

View File

@@ -1,6 +1,7 @@
<template> <template>
<!-- <!--
AI辅助出方主弹层=历史+对照二级弹层=可编辑病历预览+生成 AI辅助出方单根 u-popupVue2 组件不能双根
生成确认用抽屉内全屏覆盖层避免第二个 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;

View File

@@ -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: '现病史' },

View File

@@ -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>