feat: 病历模块优化、AI辅助问诊
This commit is contained in:
@@ -225,7 +225,12 @@
|
|||||||
<view class="mr-btn-text" @click="onOpenCiteDrawer">引用历史</view>
|
<view class="mr-btn-text" @click="onOpenCiteDrawer">引用历史</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-row flex-ali-center">
|
<view class="flex-row flex-ali-center">
|
||||||
<view class="mr-ai-btn" @click="onOpenAiModal">
|
<!-- 无 VIP ai_medical_record 时隐藏入口 -->
|
||||||
|
<view
|
||||||
|
v-if="canUseAiMedicalRecord"
|
||||||
|
class="mr-ai-btn"
|
||||||
|
@click="onOpenAiModal"
|
||||||
|
>
|
||||||
<text class="mr-ai-btn__icon">✦</text>
|
<text class="mr-ai-btn__icon">✦</text>
|
||||||
<text class="mr-ai-btn__text">AI写病历</text>
|
<text class="mr-ai-btn__text">AI写病历</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -322,11 +327,13 @@
|
|||||||
border-radius="24"
|
border-radius="24"
|
||||||
:safe-area-inset-bottom="true"
|
:safe-area-inset-bottom="true"
|
||||||
:mask-close-able="true"
|
:mask-close-able="true"
|
||||||
height="70%"
|
height="85%"
|
||||||
z-index="10090"
|
z-index="10090"
|
||||||
@close="closeEntryDrawer"
|
@close="closeEntryDrawer"
|
||||||
>
|
>
|
||||||
|
<!-- 词条抽屉:头固定 + 列表区可滑(与诊断弹窗同结构,避免 scroll-view 高度塌缩) -->
|
||||||
<view class="entry-drawer">
|
<view class="entry-drawer">
|
||||||
|
<view class="entry-drawer__hd">
|
||||||
<view class="drawer-handle" />
|
<view class="drawer-handle" />
|
||||||
<view class="drawer-header">
|
<view class="drawer-header">
|
||||||
<text class="drawer-header__title">选择词条</text>
|
<text class="drawer-header__title">选择词条</text>
|
||||||
@@ -334,11 +341,13 @@
|
|||||||
</view>
|
</view>
|
||||||
<u-input
|
<u-input
|
||||||
v-model="entryKeyword"
|
v-model="entryKeyword"
|
||||||
placeholder="搜索词条名称/内容/首拼"
|
placeholder="搜索名称/内容/首拼"
|
||||||
:custom-style="entrySearchStyle"
|
:custom-style="entrySearchStyle"
|
||||||
@input="onEntryKeywordInput"
|
@input="onEntryKeywordInput"
|
||||||
/>
|
/>
|
||||||
<scroll-view scroll-y class="entry-scroll">
|
</view>
|
||||||
|
<scroll-view scroll-y class="entry-scroll" enable-flex>
|
||||||
|
<view class="entry-scroll-inner">
|
||||||
<view
|
<view
|
||||||
v-for="row in entryList"
|
v-for="row in entryList"
|
||||||
:key="row.id"
|
:key="row.id"
|
||||||
@@ -346,30 +355,36 @@
|
|||||||
@click="pickEntry(row)"
|
@click="pickEntry(row)"
|
||||||
>
|
>
|
||||||
<view class="entry-row-top">
|
<view class="entry-row-top">
|
||||||
<view class="fs-28 font-bold entry-row-name">
|
<view class="entry-row-name">
|
||||||
<block v-for="(p, pi) in (row._titleParts || [])" :key="pi">
|
<block v-for="(p, pi) in (row._titleParts || [])" :key="'t' + pi">
|
||||||
<text v-if="p.hit" class="hit-text">{{ p.text }}</text>
|
<text v-if="p.hit" class="hit-text">{{ p.text }}</text>
|
||||||
<text v-else>{{ p.text }}</text>
|
<text v-else>{{ p.text }}</text>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="entryKeyword" class="entry-score">{{ row.match_score || 0 }}%</text>
|
<text v-if="entryKeyword" class="entry-score">{{ row.match_score || 0 }}%</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="row.pinyin_initials" class="fs-22 color-sub m-t-4">
|
<!-- 正文多行展示,行高收紧以一屏多看几条 -->
|
||||||
<text>首拼(全拼):</text>
|
<view v-if="row.content" class="entry-row-content">
|
||||||
<block v-for="(p, pi) in (row._pyParts || [])" :key="pi">
|
<block v-for="(p, pi) in (row._contentParts || [])" :key="'c' + pi">
|
||||||
<text v-if="p.hit" class="hit-text">{{ p.text }}</text>
|
|
||||||
<text v-else>{{ p.text }}</text>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
<view class="fs-24 color-sub m-t-8">
|
|
||||||
<block v-for="(p, pi) in (row._contentParts || [])" :key="pi">
|
|
||||||
<text v-if="p.hit" class="hit-text">{{ p.text }}</text>
|
<text v-if="p.hit" class="hit-text">{{ p.text }}</text>
|
||||||
<text v-else>{{ p.text }}</text>
|
<text v-else>{{ p.text }}</text>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="row.remark || (entryKeyword && row.pinyin_initials)"
|
||||||
|
class="entry-row-meta"
|
||||||
|
>
|
||||||
<text v-if="row.remark" class="entry-remark-tag">{{ row.remark }}</text>
|
<text v-if="row.remark" class="entry-remark-tag">{{ row.remark }}</text>
|
||||||
|
<text v-if="entryKeyword && row.pinyin_initials" class="entry-py-tag">
|
||||||
|
<block v-for="(p, pi) in (row._pyParts || [])" :key="'p' + pi">
|
||||||
|
<text v-if="p.hit" class="hit-text">{{ p.text }}</text>
|
||||||
|
<text v-else>{{ p.text }}</text>
|
||||||
|
</block>
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!entryList.length" class="drawer-empty">暂无词条</view>
|
<view v-if="!entryList.length" class="drawer-empty">暂无词条</view>
|
||||||
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
@@ -487,12 +502,16 @@ export default {
|
|||||||
diagnosisReadonly: { type: Boolean, default: false },
|
diagnosisReadonly: { type: Boolean, default: false },
|
||||||
/** 是否处于病历 Tab(控制底部栏;probe 时必须为 false,否则 fixed 会穿透到处方) */
|
/** 是否处于病历 Tab(控制底部栏;probe 时必须为 false,否则 fixed 会穿透到处方) */
|
||||||
active: { type: Boolean, default: false },
|
active: { type: Boolean, default: false },
|
||||||
|
/** 门店是否开通病历 VIP(medical_record);无则不请求病历接口,避免弹错 */
|
||||||
|
canUseMedicalRecord: { type: Boolean, default: false },
|
||||||
/** 就诊人姓名(AI 确认步展示) */
|
/** 就诊人姓名(AI 确认步展示) */
|
||||||
patientName: { type: String, default: '' },
|
patientName: { type: String, default: '' },
|
||||||
/** 就诊人性别 1男2女 */
|
/** 就诊人性别 1男2女 */
|
||||||
patientSex: { type: [Number, String], default: 0 },
|
patientSex: { type: [Number, String], default: 0 },
|
||||||
/** 就诊人年龄 */
|
/** 就诊人年龄 */
|
||||||
patientAge: { type: [Number, String], default: 0 },
|
patientAge: { type: [Number, String], default: 0 },
|
||||||
|
/** 门店是否开通 AI写病历 VIP(ai_medical_record) */
|
||||||
|
canUseAiMedicalRecord: { type: Boolean, default: false },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const tcmCaseSubFields = [
|
const tcmCaseSubFields = [
|
||||||
@@ -509,7 +528,12 @@ export default {
|
|||||||
entryList: [],
|
entryList: [],
|
||||||
entryKeyword: '',
|
entryKeyword: '',
|
||||||
entrySearchTimer: null,
|
entrySearchTimer: null,
|
||||||
entrySearchStyle: { background: '#f4f6f8', borderRadius: '14rpx', padding: '14rpx 20rpx', marginBottom: '20rpx', border: '1rpx solid #e8ecef' },
|
entrySearchStyle: {
|
||||||
|
background: '#f4f6f8',
|
||||||
|
borderRadius: '14rpx',
|
||||||
|
padding: '12rpx 20rpx',
|
||||||
|
border: '1rpx solid #e8ecef',
|
||||||
|
},
|
||||||
/** 引用历史抽屉 */
|
/** 引用历史抽屉 */
|
||||||
citeShow: false,
|
citeShow: false,
|
||||||
citeLoading: false,
|
citeLoading: false,
|
||||||
@@ -599,10 +623,13 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
registerId: {
|
registerId: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(v) {
|
handler() {
|
||||||
if (v) this.load()
|
this.tryLoad()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
canUseMedicalRecord() {
|
||||||
|
this.tryLoad()
|
||||||
|
},
|
||||||
diagnosis(v) {
|
diagnosis(v) {
|
||||||
this.localDiagnosis = v || ''
|
this.localDiagnosis = v || ''
|
||||||
this.schedulePersistDraft()
|
this.schedulePersistDraft()
|
||||||
@@ -629,6 +656,15 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* 仅在有病历 VIP 时拉病历;普通诊所静默跳过,不弹「未开通 VIP」
|
||||||
|
*/
|
||||||
|
tryLoad() {
|
||||||
|
if (!this.registerId || !this.canUseMedicalRecord) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.load()
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 通知父页:病历侧是否有弹层打开(与处方弹层共用返回拦截)
|
* 通知父页:病历侧是否有弹层打开(与处方弹层共用返回拦截)
|
||||||
*/
|
*/
|
||||||
@@ -655,6 +691,10 @@ export default {
|
|||||||
* 打开 AI 写病历:先进历史抽屉(无历史则自动弹出生成框)
|
* 打开 AI 写病历:先进历史抽屉(无历史则自动弹出生成框)
|
||||||
*/
|
*/
|
||||||
onOpenAiModal() {
|
onOpenAiModal() {
|
||||||
|
if (!this.canUseAiMedicalRecord) {
|
||||||
|
uni.showToast({ title: '当前门店未开通AI写病历', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!this.registerId) {
|
if (!this.registerId) {
|
||||||
uni.showToast({ title: '挂号无效', icon: 'none' })
|
uni.showToast({ title: '挂号无效', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -723,11 +763,12 @@ export default {
|
|||||||
async load() {
|
async load() {
|
||||||
this.hydrating = true
|
this.hydrating = true
|
||||||
try {
|
try {
|
||||||
const res = await getMedicalRecordApi({
|
const mrParams = { register_id: this.registerId }
|
||||||
register_id: this.registerId,
|
const mrStoreId = Number(this.storeId || 0)
|
||||||
store_id: this.storeId || undefined,
|
if (mrStoreId > 0) mrParams.store_id = mrStoreId
|
||||||
})
|
const res = await getMedicalRecordApi(mrParams)
|
||||||
const data = (res && res.data) || res || {}
|
// Laravel jok 在 result;兼容 data / 已解包
|
||||||
|
const data = (res && (res.result != null ? res.result : res.data)) || res || {}
|
||||||
this.form = Object.assign(emptyForm(), data)
|
this.form = Object.assign(emptyForm(), data)
|
||||||
const draft = PrescriptionStorage.loadMedicalRecordDraft(this.registerId)
|
const draft = PrescriptionStorage.loadMedicalRecordDraft(this.registerId)
|
||||||
if (draft) {
|
if (draft) {
|
||||||
@@ -744,17 +785,11 @@ export default {
|
|||||||
}
|
}
|
||||||
if (this.diagnosis) this.localDiagnosis = this.diagnosis
|
if (this.diagnosis) this.localDiagnosis = this.diagnosis
|
||||||
if (this.medicalAdvice) this.localAdvice = this.medicalAdvice
|
if (this.medicalAdvice) this.localAdvice = this.medicalAdvice
|
||||||
this.$emit('vip-ok')
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// 有 VIP 才请求;若仍失败(网络等)仅回填本地草稿,不再靠 VIP 文案探测
|
||||||
const draft = PrescriptionStorage.loadMedicalRecordDraft(this.registerId)
|
const draft = PrescriptionStorage.loadMedicalRecordDraft(this.registerId)
|
||||||
if (draft) {
|
if (draft) {
|
||||||
this.applyDraft(draft)
|
this.applyDraft(draft)
|
||||||
this.$emit('vip-ok')
|
|
||||||
} else {
|
|
||||||
const msg = (e && e.message) || (e && e.msg) || ''
|
|
||||||
if (String(msg).indexOf('VIP') > -1 || String(msg).indexOf('vip') > -1 || String(msg).indexOf('病历') > -1) {
|
|
||||||
this.$emit('vip-denied')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.hydrating = false
|
this.hydrating = false
|
||||||
@@ -827,10 +862,10 @@ export default {
|
|||||||
uni.showToast({ title: '已导入,请核对后保存', icon: 'none' })
|
uni.showToast({ title: '已导入,请核对后保存', icon: 'none' })
|
||||||
},
|
},
|
||||||
async onClear() {
|
async onClear() {
|
||||||
await clearMedicalRecordApi({
|
const clearParams = { register_id: this.registerId }
|
||||||
register_id: this.registerId,
|
const clearStoreId = Number(this.storeId || 0)
|
||||||
store_id: this.storeId || undefined,
|
if (clearStoreId > 0) clearParams.store_id = clearStoreId
|
||||||
})
|
await clearMedicalRecordApi(clearParams)
|
||||||
const keepPatient = this.effectivePatientId
|
const keepPatient = this.effectivePatientId
|
||||||
this.form = emptyForm()
|
this.form = emptyForm()
|
||||||
this.form.user_patient_id = keepPatient
|
this.form.user_patient_id = keepPatient
|
||||||
@@ -855,11 +890,13 @@ export default {
|
|||||||
this.citeList = []
|
this.citeList = []
|
||||||
this.citeActiveId = 0
|
this.citeActiveId = 0
|
||||||
try {
|
try {
|
||||||
const res = await listMedicalRecordByPatientApi({
|
const citeParams = {
|
||||||
user_patient_id: patientId,
|
user_patient_id: patientId,
|
||||||
store_id: this.storeId || undefined,
|
|
||||||
limit: 50,
|
limit: 50,
|
||||||
})
|
}
|
||||||
|
const citeStoreId = Number(this.storeId || 0)
|
||||||
|
if (citeStoreId > 0) citeParams.store_id = citeStoreId
|
||||||
|
const res = await listMedicalRecordByPatientApi(citeParams)
|
||||||
const list = this.unwrapCiteList(res)
|
const list = this.unwrapCiteList(res)
|
||||||
const exclude = Number(this.registerId || 0)
|
const exclude = Number(this.registerId || 0)
|
||||||
this.citeList = list.map((row) => {
|
this.citeList = list.map((row) => {
|
||||||
@@ -991,11 +1028,12 @@ export default {
|
|||||||
if (this.entryTcmType) {
|
if (this.entryTcmType) {
|
||||||
list = await this.fetchTcmDictList(this.entryTcmType, kw)
|
list = await this.fetchTcmDictList(this.entryTcmType, kw)
|
||||||
} else {
|
} else {
|
||||||
const res = await searchMedicalRecordEntryApi({
|
// uni 会把 undefined 序列化成字符串 "undefined",无值时不要塞进 params
|
||||||
field_code: this.entryField,
|
const params = { field_code: this.entryField }
|
||||||
keyword: kw || undefined,
|
if (kw) params.keyword = kw
|
||||||
store_id: this.storeId || undefined,
|
const sid = Number(this.storeId || 0)
|
||||||
})
|
if (sid > 0) params.store_id = sid
|
||||||
|
const res = await searchMedicalRecordEntryApi(params)
|
||||||
list = this.unwrapEntryList(res)
|
list = this.unwrapEntryList(res)
|
||||||
if (list[0] && list[0].rank_mode) {
|
if (list[0] && list[0].rank_mode) {
|
||||||
mode = String(list[0].rank_mode)
|
mode = String(list[0].rank_mode)
|
||||||
@@ -1521,51 +1559,96 @@ export default {
|
|||||||
padding: 20rpx 0 28rpx;
|
padding: 20rpx 0 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 词条选择抽屉 ─── */
|
/* ─── 词条选择抽屉(紧凑列表 + 可滑) ─── */
|
||||||
.entry-drawer {
|
.entry-drawer {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 28rpx 28rpx 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.entry-drawer__hd {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 20rpx 28rpx 12rpx;
|
||||||
|
}
|
||||||
|
.entry-drawer__hd .drawer-handle {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
.entry-drawer__hd .drawer-header {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
}
|
}
|
||||||
.entry-scroll {
|
.entry-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 0;
|
height: 0;
|
||||||
min-height: 200rpx;
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.entry-scroll-inner {
|
||||||
|
padding: 0 28rpx;
|
||||||
|
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.entry-row {
|
.entry-row {
|
||||||
padding: 22rpx 0;
|
padding: 16rpx 0;
|
||||||
border-bottom: 1rpx solid #f0f2f4;
|
border-bottom: 1rpx solid #f0f2f4;
|
||||||
}
|
}
|
||||||
.entry-row-top {
|
.entry-row-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16rpx;
|
gap: 12rpx;
|
||||||
}
|
}
|
||||||
.entry-row-name {
|
.entry-row-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a2530;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.entry-score {
|
.entry-score {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 22rpx;
|
font-size: 20rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #ff7043;
|
color: #ff7043;
|
||||||
background: rgba(255, 112, 67, 0.1);
|
background: rgba(255, 112, 67, 0.1);
|
||||||
padding: 4rpx 14rpx;
|
padding: 2rpx 12rpx;
|
||||||
border-radius: 10rpx;
|
border-radius: 10rpx;
|
||||||
}
|
}
|
||||||
.entry-remark-tag {
|
.entry-row-content {
|
||||||
display: inline-block;
|
margin-top: 6rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #4a5560;
|
||||||
|
line-height: 1.45;
|
||||||
|
/* 约 3 行高度;用 max-height 兼容高亮多 text 节点 */
|
||||||
|
max-height: 114rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.entry-row-meta {
|
||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
.entry-remark-tag,
|
||||||
|
.entry-py-tag {
|
||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
color: #8a9399;
|
color: #8a9399;
|
||||||
background: #f0f2f4;
|
background: #f0f2f4;
|
||||||
padding: 4rpx 14rpx;
|
padding: 2rpx 12rpx;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.hit-text {
|
.hit-text {
|
||||||
color: #e64e1f;
|
color: #e64e1f;
|
||||||
|
|||||||
@@ -127,9 +127,9 @@
|
|||||||
<u-button size="mini" class="m-l-16" :type="feeCategory === 2 ? 'success' : 'default'" @click="feeCategory = 2">医保</u-button>
|
<u-button size="mini" class="m-l-16" :type="feeCategory === 2 ? 'success' : 'default'" @click="feeCategory = 2">医保</u-button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="isRxMainVisible && !isSalespersonTransferMode && activeCategory === 1 && hasSalespersonTransfer" class="mx-32 m-t-12 rx-flex-fixed">
|
<!-- <view v-if="isRxMainVisible && !isSalespersonTransferMode && activeCategory === 1 && hasSalespersonTransfer" class="mx-32 m-t-12 rx-flex-fixed">-->
|
||||||
<u-button size="mini" type="primary" plain @click="openSalespersonTransfer">查看传方</u-button>
|
<!-- <u-button size="mini" type="primary" plain @click="openSalespersonTransfer">查看传方</u-button>-->
|
||||||
</view>
|
<!-- </view>-->
|
||||||
|
|
||||||
<!-- 诊断:病历 Tab 必须卸载(微信 v-show 藏不住 fixed/flex 固定块) -->
|
<!-- 诊断:病历 Tab 必须卸载(微信 v-show 藏不住 fixed/flex 固定块) -->
|
||||||
<view v-if="isRxMainVisible && !isSalespersonTransferMode" class="top white p-32 flex-col m-t-16 radius-12 mx-32 rx-flex-fixed">
|
<view v-if="isRxMainVisible && !isSalespersonTransferMode" class="top white p-32 flex-col m-t-16 radius-12 mx-32 rx-flex-fixed">
|
||||||
@@ -552,9 +552,8 @@
|
|||||||
>另存常用方</view>
|
>另存常用方</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-row flex-ali-center">
|
<view class="flex-row flex-ali-center">
|
||||||
<!-- AI 胶囊与病历「AI写病历」同款;功能暂不可用时仍保留入口样式 -->
|
|
||||||
<view
|
<view
|
||||||
v-if="!isSpecialPrescriptionCartLocked && !isSalespersonTransferMode && !isCommonPrescription"
|
v-if="!isSpecialPrescriptionCartLocked && !isSalespersonTransferMode && !isCommonPrescription && canUseAiPrescription"
|
||||||
class="rx-ai-btn"
|
class="rx-ai-btn"
|
||||||
@click="handleOpenAiPrescriptionDrawer"
|
@click="handleOpenAiPrescriptionDrawer"
|
||||||
>
|
>
|
||||||
@@ -578,8 +577,9 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
病历:常挂载保草稿 / 探测 VIP。
|
病历:常挂载保草稿。
|
||||||
处方 Tab:fixed 离屏探测,绝不参与 page-flex 占高。
|
是否有 VIP 由门店 permissions 判断;无 VIP 不请求病历接口、不展示 Tab。
|
||||||
|
处方 Tab:fixed 离屏,绝不参与 page-flex 占高。
|
||||||
病历 Tab:flex:1 吃满剩余高度。
|
病历 Tab:flex:1 吃满剩余高度。
|
||||||
-->
|
-->
|
||||||
<view
|
<view
|
||||||
@@ -596,15 +596,15 @@
|
|||||||
:medical-advice="medicalAdvice"
|
:medical-advice="medicalAdvice"
|
||||||
:diagnosis-readonly="isOnlineRevisit"
|
:diagnosis-readonly="isOnlineRevisit"
|
||||||
:active="isMrMainVisible"
|
:active="isMrMainVisible"
|
||||||
|
:can-use-medical-record="canUseMedicalRecord"
|
||||||
:patient-name="(patientInfo && patientInfo.name) || ''"
|
:patient-name="(patientInfo && patientInfo.name) || ''"
|
||||||
:patient-sex="(patientInfo && patientInfo.sex) || 0"
|
:patient-sex="(patientInfo && patientInfo.sex) || 0"
|
||||||
:patient-age="(patientInfo && patientInfo.age) || 0"
|
:patient-age="(patientInfo && patientInfo.age) || 0"
|
||||||
|
:can-use-ai-medical-record="canUseAiMedicalRecord"
|
||||||
@update:diagnosis="onMrDiagnosisUpdate"
|
@update:diagnosis="onMrDiagnosisUpdate"
|
||||||
@update:medicalAdvice="onMrAdviceUpdate"
|
@update:medicalAdvice="onMrAdviceUpdate"
|
||||||
@open-diagnosis="handleOpenDiagnosisModal"
|
@open-diagnosis="handleOpenDiagnosisModal"
|
||||||
@open-doctor-order="handleOpenDoctorOrderModal"
|
@open-doctor-order="handleOpenDoctorOrderModal"
|
||||||
@vip-ok="onMedicalRecordVipOk"
|
|
||||||
@vip-denied="onMedicalRecordVipDenied"
|
|
||||||
@overlay-change="onMedicalRecordOverlayChange"
|
@overlay-change="onMedicalRecordOverlayChange"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
@@ -841,6 +841,8 @@ export default {
|
|||||||
mainTab: 'rx',
|
mainTab: 'rx',
|
||||||
/** 是否开通病历 VIP(拉取病历成功后为 true) */
|
/** 是否开通病历 VIP(拉取病历成功后为 true) */
|
||||||
canUseMedicalRecord: false,
|
canUseMedicalRecord: false,
|
||||||
|
/** 当前诊所 VIP permissions(来自 get-current-store-type.vip) */
|
||||||
|
storeVipPermissions: [],
|
||||||
pendingReusePrescriptionId: '',
|
pendingReusePrescriptionId: '',
|
||||||
patientInfo: null,
|
patientInfo: null,
|
||||||
prescriptionCategories: [
|
prescriptionCategories: [
|
||||||
@@ -1159,12 +1161,20 @@ export default {
|
|||||||
if (this.isSalespersonTransferMode || this.isCommonPrescription) {
|
if (this.isSalespersonTransferMode || this.isCommonPrescription) {
|
||||||
return '686rpx';
|
return '686rpx';
|
||||||
}
|
}
|
||||||
// 右侧 AI 胶囊 + 发送,主按钮与病历「保存病历」同量级
|
// 右侧 AI 胶囊 + 发送
|
||||||
if (!this.isSpecialPrescriptionCartLocked) {
|
if (!this.isSpecialPrescriptionCartLocked && this.canUseAiPrescription) {
|
||||||
return '240rpx';
|
return '240rpx';
|
||||||
}
|
}
|
||||||
return '480rpx';
|
return '480rpx';
|
||||||
},
|
},
|
||||||
|
/** 门店 VIP:AI辅助出方 */
|
||||||
|
canUseAiPrescription() {
|
||||||
|
return this.hasStoreVipPermission('ai_prescription');
|
||||||
|
},
|
||||||
|
/** 门店 VIP:AI写病历 */
|
||||||
|
canUseAiMedicalRecord() {
|
||||||
|
return this.hasStoreVipPermission('ai_medical_record');
|
||||||
|
},
|
||||||
navbarTitle() {
|
navbarTitle() {
|
||||||
if (this.isSalespersonTransferMode) {
|
if (this.isSalespersonTransferMode) {
|
||||||
return '传方';
|
return '传方';
|
||||||
@@ -1710,6 +1720,30 @@ export default {
|
|||||||
this.applyPreSendPriceDiscount(payload.priceDiscount);
|
this.applyPreSendPriceDiscount(payload.priceDiscount);
|
||||||
this.closePreSendPriceAdjust();
|
this.closePreSendPriceAdjust();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 判断当前诊所 VIP 是否含指定权益码
|
||||||
|
*/
|
||||||
|
hasStoreVipPermission(code) {
|
||||||
|
const list = this.storeVipPermissions;
|
||||||
|
return Array.isArray(list) && list.indexOf(code) >= 0;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 从门店类型接口结果写入 VIP permissions
|
||||||
|
* 有 medical_record 才显示病历 Tab;无则静默留在处方(不打 get-medical-record 避免弹 VIP 错)
|
||||||
|
*/
|
||||||
|
applyStoreVipFromRes(res) {
|
||||||
|
const data = (res && (res.result != null ? res.result : res.data != null ? res.data : res)) || {};
|
||||||
|
const vip = data.vip || {};
|
||||||
|
const perms = vip.permissions;
|
||||||
|
this.storeVipPermissions = Array.isArray(perms) ? perms.slice() : [];
|
||||||
|
const allowMr = this.hasStoreVipPermission('medical_record');
|
||||||
|
this.canUseMedicalRecord = allowMr;
|
||||||
|
if (allowMr) {
|
||||||
|
this.restoreMainTab();
|
||||||
|
} else if (this.mainTab === 'mr') {
|
||||||
|
this.mainTab = 'rx';
|
||||||
|
}
|
||||||
|
},
|
||||||
async fetchStoreSeeRate() {
|
async fetchStoreSeeRate() {
|
||||||
try {
|
try {
|
||||||
const storeId = uni.getStorageSync('store_id');
|
const storeId = uni.getStorageSync('store_id');
|
||||||
@@ -1725,8 +1759,10 @@ export default {
|
|||||||
}
|
}
|
||||||
const res = await getCurrentStoreTypeApi(params);
|
const res = await getCurrentStoreTypeApi(params);
|
||||||
if (res) {
|
if (res) {
|
||||||
this.seeRate = Number(res.see_rate ?? 0);
|
this.applyStoreVipFromRes(res);
|
||||||
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0);
|
const data = (res.result != null ? res.result : res.data != null ? res.data : res) || res;
|
||||||
|
this.seeRate = Number(data.see_rate ?? 0);
|
||||||
|
this.enableOrderPricePercentAdjust = Number(data.enable_order_price_percent_adjust ?? 0);
|
||||||
await this.loadPriceAdjustConfigForStore(params.store_id || storeId);
|
await this.loadPriceAdjustConfigForStore(params.store_id || storeId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1745,21 +1781,23 @@ export default {
|
|||||||
...(storeId ? { store_id: storeId } : {})
|
...(storeId ? { store_id: storeId } : {})
|
||||||
});
|
});
|
||||||
if (res) {
|
if (res) {
|
||||||
this.registerStoreInfo = res;
|
this.applyStoreVipFromRes(res);
|
||||||
this.seeRate = Number(res.see_rate ?? 0);
|
const data = (res.result != null ? res.result : res.data != null ? res.data : res) || res;
|
||||||
if (Number(res.allow_insurance_category ?? 0) !== 1) {
|
this.registerStoreInfo = data;
|
||||||
|
this.seeRate = Number(data.see_rate ?? 0);
|
||||||
|
if (Number(data.allow_insurance_category ?? 0) !== 1) {
|
||||||
this.feeCategory = 1;
|
this.feeCategory = 1;
|
||||||
}
|
}
|
||||||
this.checkSalespersonTransfer();
|
this.checkSalespersonTransfer();
|
||||||
if (res.is_from_transfer === 1 && res.delegate_store_id) {
|
if (data.is_from_transfer === 1 && data.delegate_store_id) {
|
||||||
this.selectedStoreId = res.delegate_store_id;
|
this.selectedStoreId = data.delegate_store_id;
|
||||||
this.sendMode = 1;
|
this.sendMode = 1;
|
||||||
} else if (res.store_id) {
|
} else if (data.store_id) {
|
||||||
this.selectedStoreId = res.store_id;
|
this.selectedStoreId = data.store_id;
|
||||||
this.sendMode = res.can_change ? 1 : 0;
|
this.sendMode = data.can_change ? 1 : 0;
|
||||||
}
|
}
|
||||||
const priceStoreId = this.selectedStoreId || res.store_id || storeId;
|
const priceStoreId = this.selectedStoreId || data.store_id || storeId;
|
||||||
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0);
|
this.enableOrderPricePercentAdjust = Number(data.enable_order_price_percent_adjust ?? 0);
|
||||||
await this.loadPriceAdjustConfigForStore(priceStoreId);
|
await this.loadPriceAdjustConfigForStore(priceStoreId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1924,14 +1962,6 @@ export default {
|
|||||||
onMrAdviceUpdate(v) {
|
onMrAdviceUpdate(v) {
|
||||||
this.medicalAdvice = v || '';
|
this.medicalAdvice = v || '';
|
||||||
},
|
},
|
||||||
onMedicalRecordVipDenied() {
|
|
||||||
this.canUseMedicalRecord = false;
|
|
||||||
if (this.mainTab === 'mr') this.mainTab = 'rx';
|
|
||||||
},
|
|
||||||
onMedicalRecordVipOk() {
|
|
||||||
this.canUseMedicalRecord = true;
|
|
||||||
this.restoreMainTab();
|
|
||||||
},
|
|
||||||
/** 发送处方成功后同步病历(有 VIP 时) */
|
/** 发送处方成功后同步病历(有 VIP 时) */
|
||||||
async syncMedicalRecordAfterSend() {
|
async syncMedicalRecordAfterSend() {
|
||||||
if (!this.canUseMedicalRecord) return;
|
if (!this.canUseMedicalRecord) return;
|
||||||
@@ -2021,6 +2051,10 @@ export default {
|
|||||||
* 打开 AI 给处方抽屉:带入完整病历(含未保存),先确认再生成
|
* 打开 AI 给处方抽屉:带入完整病历(含未保存),先确认再生成
|
||||||
*/
|
*/
|
||||||
handleOpenAiPrescriptionDrawer() {
|
handleOpenAiPrescriptionDrawer() {
|
||||||
|
if (!this.canUseAiPrescription) {
|
||||||
|
this.$toast('当前门店未开通AI辅助出方VIP功能');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (guardSpecialPrescriptionCartEdit(this)) return;
|
if (guardSpecialPrescriptionCartEdit(this)) return;
|
||||||
if (!this.registerId) {
|
if (!this.registerId) {
|
||||||
this.$toast('挂号无效');
|
this.$toast('挂号无效');
|
||||||
|
|||||||
Reference in New Issue
Block a user