diff --git a/subPackages/sub_workbench/prescription_v2/components/MedicalRecordPanel.vue b/subPackages/sub_workbench/prescription_v2/components/MedicalRecordPanel.vue index 880b0b9..d428394 100644 --- a/subPackages/sub_workbench/prescription_v2/components/MedicalRecordPanel.vue +++ b/subPackages/sub_workbench/prescription_v2/components/MedicalRecordPanel.vue @@ -225,7 +225,12 @@ 引用历史 - + + AI写病历 @@ -322,54 +327,64 @@ border-radius="24" :safe-area-inset-bottom="true" :mask-close-able="true" - height="70%" + height="85%" z-index="10090" @close="closeEntryDrawer" > + - - - 选择词条 - {{ entryKeyword ? ('搜到 ' + entryList.length + ' 条') : ('共 ' + entryList.length + ' 条') }} + + + + 选择词条 + {{ entryKeyword ? ('搜到 ' + entryList.length + ' 条') : ('共 ' + entryList.length + ' 条') }} + + - - - - - - + + + + + + + {{ p.text }} + {{ p.text }} + + + {{ row.match_score || 0 }}% + + + + {{ p.text }} {{ p.text }} - {{ row.match_score || 0 }}% + + {{ row.remark }} + + + {{ p.text }} + {{ p.text }} + + + - - 首拼(全拼): - - {{ p.text }} - {{ p.text }} - - - - - {{ p.text }} - {{ p.text }} - - - {{ row.remark }} + 暂无词条 - 暂无词条 @@ -487,12 +502,16 @@ export default { diagnosisReadonly: { type: Boolean, default: false }, /** 是否处于病历 Tab(控制底部栏;probe 时必须为 false,否则 fixed 会穿透到处方) */ active: { type: Boolean, default: false }, + /** 门店是否开通病历 VIP(medical_record);无则不请求病历接口,避免弹错 */ + canUseMedicalRecord: { type: Boolean, default: false }, /** 就诊人姓名(AI 确认步展示) */ patientName: { type: String, default: '' }, /** 就诊人性别 1男2女 */ patientSex: { type: [Number, String], default: 0 }, /** 就诊人年龄 */ patientAge: { type: [Number, String], default: 0 }, + /** 门店是否开通 AI写病历 VIP(ai_medical_record) */ + canUseAiMedicalRecord: { type: Boolean, default: false }, }, data() { const tcmCaseSubFields = [ @@ -509,7 +528,12 @@ export default { entryList: [], entryKeyword: '', 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, citeLoading: false, @@ -599,10 +623,13 @@ export default { watch: { registerId: { immediate: true, - handler(v) { - if (v) this.load() + handler() { + this.tryLoad() }, }, + canUseMedicalRecord() { + this.tryLoad() + }, diagnosis(v) { this.localDiagnosis = v || '' this.schedulePersistDraft() @@ -629,6 +656,15 @@ export default { }, }, methods: { + /** + * 仅在有病历 VIP 时拉病历;普通诊所静默跳过,不弹「未开通 VIP」 + */ + tryLoad() { + if (!this.registerId || !this.canUseMedicalRecord) { + return + } + this.load() + }, /** * 通知父页:病历侧是否有弹层打开(与处方弹层共用返回拦截) */ @@ -655,6 +691,10 @@ export default { * 打开 AI 写病历:先进历史抽屉(无历史则自动弹出生成框) */ onOpenAiModal() { + if (!this.canUseAiMedicalRecord) { + uni.showToast({ title: '当前门店未开通AI写病历', icon: 'none' }) + return + } if (!this.registerId) { uni.showToast({ title: '挂号无效', icon: 'none' }) return @@ -723,11 +763,12 @@ export default { async load() { this.hydrating = true try { - const res = await getMedicalRecordApi({ - register_id: this.registerId, - store_id: this.storeId || undefined, - }) - const data = (res && res.data) || res || {} + const mrParams = { register_id: this.registerId } + const mrStoreId = Number(this.storeId || 0) + if (mrStoreId > 0) mrParams.store_id = mrStoreId + const res = await getMedicalRecordApi(mrParams) + // Laravel jok 在 result;兼容 data / 已解包 + const data = (res && (res.result != null ? res.result : res.data)) || res || {} this.form = Object.assign(emptyForm(), data) const draft = PrescriptionStorage.loadMedicalRecordDraft(this.registerId) if (draft) { @@ -744,17 +785,11 @@ export default { } if (this.diagnosis) this.localDiagnosis = this.diagnosis if (this.medicalAdvice) this.localAdvice = this.medicalAdvice - this.$emit('vip-ok') } catch (e) { + // 有 VIP 才请求;若仍失败(网络等)仅回填本地草稿,不再靠 VIP 文案探测 const draft = PrescriptionStorage.loadMedicalRecordDraft(this.registerId) if (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 { this.hydrating = false @@ -827,10 +862,10 @@ export default { uni.showToast({ title: '已导入,请核对后保存', icon: 'none' }) }, async onClear() { - await clearMedicalRecordApi({ - register_id: this.registerId, - store_id: this.storeId || undefined, - }) + const clearParams = { register_id: this.registerId } + const clearStoreId = Number(this.storeId || 0) + if (clearStoreId > 0) clearParams.store_id = clearStoreId + await clearMedicalRecordApi(clearParams) const keepPatient = this.effectivePatientId this.form = emptyForm() this.form.user_patient_id = keepPatient @@ -855,11 +890,13 @@ export default { this.citeList = [] this.citeActiveId = 0 try { - const res = await listMedicalRecordByPatientApi({ + const citeParams = { user_patient_id: patientId, - store_id: this.storeId || undefined, 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 exclude = Number(this.registerId || 0) this.citeList = list.map((row) => { @@ -991,11 +1028,12 @@ export default { if (this.entryTcmType) { list = await this.fetchTcmDictList(this.entryTcmType, kw) } else { - const res = await searchMedicalRecordEntryApi({ - field_code: this.entryField, - keyword: kw || undefined, - store_id: this.storeId || undefined, - }) + // uni 会把 undefined 序列化成字符串 "undefined",无值时不要塞进 params + const params = { field_code: this.entryField } + if (kw) params.keyword = kw + const sid = Number(this.storeId || 0) + if (sid > 0) params.store_id = sid + const res = await searchMedicalRecordEntryApi(params) list = this.unwrapEntryList(res) if (list[0] && list[0].rank_mode) { mode = String(list[0].rank_mode) @@ -1521,51 +1559,96 @@ export default { padding: 20rpx 0 28rpx; } -/* ─── 词条选择抽屉 ─── */ +/* ─── 词条选择抽屉(紧凑列表 + 可滑) ─── */ .entry-drawer { height: 100%; + min-height: 0; background: #fff; - padding: 28rpx 28rpx 0; display: flex; flex-direction: column; 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 { flex: 1; 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 { - padding: 22rpx 0; + padding: 16rpx 0; border-bottom: 1rpx solid #f0f2f4; } .entry-row-top { display: flex; - align-items: flex-start; + flex-direction: row; + align-items: center; justify-content: space-between; - gap: 16rpx; + gap: 12rpx; } .entry-row-name { flex: 1; min-width: 0; + font-size: 28rpx; + font-weight: 700; + color: #1a2530; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .entry-score { flex-shrink: 0; - font-size: 22rpx; + font-size: 20rpx; font-weight: 700; color: #ff7043; background: rgba(255, 112, 67, 0.1); - padding: 4rpx 14rpx; + padding: 2rpx 12rpx; border-radius: 10rpx; } -.entry-remark-tag { - display: inline-block; +.entry-row-content { + 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; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + gap: 8rpx; +} +.entry-remark-tag, +.entry-py-tag { font-size: 20rpx; color: #8a9399; background: #f0f2f4; - padding: 4rpx 14rpx; + padding: 2rpx 12rpx; border-radius: 8rpx; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .hit-text { color: #e64e1f; diff --git a/subPackages/sub_workbench/prescription_v2/index.vue b/subPackages/sub_workbench/prescription_v2/index.vue index e1fbeaa..41d4cd8 100644 --- a/subPackages/sub_workbench/prescription_v2/index.vue +++ b/subPackages/sub_workbench/prescription_v2/index.vue @@ -127,9 +127,9 @@ 医保 - - 查看传方 - + + + @@ -552,9 +552,8 @@ >另存常用方 - @@ -578,8 +577,9 @@ @@ -841,6 +841,8 @@ export default { mainTab: 'rx', /** 是否开通病历 VIP(拉取病历成功后为 true) */ canUseMedicalRecord: false, + /** 当前诊所 VIP permissions(来自 get-current-store-type.vip) */ + storeVipPermissions: [], pendingReusePrescriptionId: '', patientInfo: null, prescriptionCategories: [ @@ -1159,12 +1161,20 @@ export default { if (this.isSalespersonTransferMode || this.isCommonPrescription) { return '686rpx'; } - // 右侧 AI 胶囊 + 发送,主按钮与病历「保存病历」同量级 - if (!this.isSpecialPrescriptionCartLocked) { + // 右侧 AI 胶囊 + 发送 + if (!this.isSpecialPrescriptionCartLocked && this.canUseAiPrescription) { return '240rpx'; } return '480rpx'; }, + /** 门店 VIP:AI辅助出方 */ + canUseAiPrescription() { + return this.hasStoreVipPermission('ai_prescription'); + }, + /** 门店 VIP:AI写病历 */ + canUseAiMedicalRecord() { + return this.hasStoreVipPermission('ai_medical_record'); + }, navbarTitle() { if (this.isSalespersonTransferMode) { return '传方'; @@ -1710,6 +1720,30 @@ export default { this.applyPreSendPriceDiscount(payload.priceDiscount); 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() { try { const storeId = uni.getStorageSync('store_id'); @@ -1725,8 +1759,10 @@ export default { } const res = await getCurrentStoreTypeApi(params); if (res) { - this.seeRate = Number(res.see_rate ?? 0); - this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0); + this.applyStoreVipFromRes(res); + 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); } } catch (error) { @@ -1745,21 +1781,23 @@ export default { ...(storeId ? { store_id: storeId } : {}) }); if (res) { - this.registerStoreInfo = res; - this.seeRate = Number(res.see_rate ?? 0); - if (Number(res.allow_insurance_category ?? 0) !== 1) { + this.applyStoreVipFromRes(res); + const data = (res.result != null ? res.result : res.data != null ? res.data : res) || res; + this.registerStoreInfo = data; + this.seeRate = Number(data.see_rate ?? 0); + if (Number(data.allow_insurance_category ?? 0) !== 1) { this.feeCategory = 1; } this.checkSalespersonTransfer(); - if (res.is_from_transfer === 1 && res.delegate_store_id) { - this.selectedStoreId = res.delegate_store_id; + if (data.is_from_transfer === 1 && data.delegate_store_id) { + this.selectedStoreId = data.delegate_store_id; this.sendMode = 1; - } else if (res.store_id) { - this.selectedStoreId = res.store_id; - this.sendMode = res.can_change ? 1 : 0; + } else if (data.store_id) { + this.selectedStoreId = data.store_id; + this.sendMode = data.can_change ? 1 : 0; } - const priceStoreId = this.selectedStoreId || res.store_id || storeId; - this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0); + const priceStoreId = this.selectedStoreId || data.store_id || storeId; + this.enableOrderPricePercentAdjust = Number(data.enable_order_price_percent_adjust ?? 0); await this.loadPriceAdjustConfigForStore(priceStoreId); } } catch (error) { @@ -1924,14 +1962,6 @@ export default { onMrAdviceUpdate(v) { this.medicalAdvice = v || ''; }, - onMedicalRecordVipDenied() { - this.canUseMedicalRecord = false; - if (this.mainTab === 'mr') this.mainTab = 'rx'; - }, - onMedicalRecordVipOk() { - this.canUseMedicalRecord = true; - this.restoreMainTab(); - }, /** 发送处方成功后同步病历(有 VIP 时) */ async syncMedicalRecordAfterSend() { if (!this.canUseMedicalRecord) return; @@ -2021,6 +2051,10 @@ export default { * 打开 AI 给处方抽屉:带入完整病历(含未保存),先确认再生成 */ handleOpenAiPrescriptionDrawer() { + if (!this.canUseAiPrescription) { + this.$toast('当前门店未开通AI辅助出方VIP功能'); + return; + } if (guardSpecialPrescriptionCartEdit(this)) return; if (!this.registerId) { this.$toast('挂号无效');