This commit is contained in:
Your Name
2026-04-21 09:49:44 +08:00
parent c9e0419895
commit c15c348b7a
454 changed files with 5391 additions and 25651 deletions
@@ -1544,7 +1544,9 @@ async function loadCreateOrderPaidOrders() {
}
try {
createOrderPaidOrdersLoading.value = true
const res: any = await prescriptionOrderPaidPayOrders({ diagnosis_id: did })
const res: any = await prescriptionOrderPaidPayOrders({
diagnosis_id: did
})
const lists = res?.lists ?? res?.data?.lists ?? []
createOrderPaidOrders.value = Array.isArray(lists) ? lists : []
const dep = Number(res?.deposit_min_amount ?? res?.data?.deposit_min_amount) || 0
@@ -444,10 +444,10 @@
<el-descriptions-item label="处方日期">{{ detailPrescription.prescription_date || '—' }}</el-descriptions-item>
<el-descriptions-item label="医师">{{ detailPrescription.doctor_name || '—' }}</el-descriptions-item>
<el-descriptions-item label="类型">{{ detailPrescription.prescription_type || '—' }}</el-descriptions-item>
<el-descriptions-item label="临床诊断" :span="2">
<el-descriptions-item label="临床诊断" :span="2" >
{{ detailPrescription.clinical_diagnosis || '—' }}
</el-descriptions-item>
<el-descriptions-item label="剂数 / 用法" :span="2">
<el-descriptions-item label="剂数 / 用法" :span="2" v-if="showPrescriptionAuditFilter">
{{ detailData.dose_count ?? detailPrescription.dose_count ?? '' }} {{ detailData.dose_unit || '' }} ·
{{ detailPrescription.usage_instruction || detailPrescription.usage_method || '' }}
</el-descriptions-item>
@@ -1357,35 +1357,60 @@
<!-- 处方详情查看处方单样式 -->
<el-drawer
v-model="prescriptionViewVisible"
title="查看处方"
size="920px"
:close-on-click-modal="false"
>
<template #header>
<div class="flex items-center justify-between w-full pr-2 gap-3">
<span class="text-base font-medium text-gray-800">查看处方</span>
<div v-if="prescriptionViewData && !prescriptionViewLoading" class="flex items-center gap-2 shrink-0">
<el-button
size="small"
:loading="prescriptionSlipExporting"
@click="downloadPrescriptionSlipImage"
>
下载图片
</el-button>
<el-button
size="small"
:loading="prescriptionSlipExporting"
@click="downloadPrescriptionSlipPdf"
>
下载PDF
</el-button>
</div>
</div>
</template>
<div v-loading="prescriptionViewLoading" class="cf-slip-wrap">
<div v-if="prescriptionViewData" class="cf-slip-print">
<div v-if="prescriptionViewData" ref="prescriptionSlipPrintRef" class="cf-slip-print">
<div class="cf-slip-header">
<h2 class="cf-slip-title">{{ SLIP_HOSPITAL_NAME }}处方笺</h2>
<div class="cf-slip-type">
<el-tag v-if="Number(prescriptionViewData.void_status) === 1" type="danger" size="small">已作废</el-tag>
<el-tag
v-if="Number(prescriptionViewData.void_status) === 1"
type="danger"
size="small"
class="cf-slip-no-export"
>已作废</el-tag>
<el-tag
v-else-if="Number(prescriptionViewData.business_prescription_audit_rejected) === 1"
type="danger"
size="small"
class="mr-1"
class="mr-1 cf-slip-no-export"
>已驳回</el-tag>
<el-tag
v-else-if="Number(prescriptionViewData.audit_status) === 1"
type="success"
size="small"
class="mr-1"
class="mr-1 cf-slip-no-export"
>已通过</el-tag>
<el-tag
v-else-if="Number(prescriptionViewData.audit_status) === 0"
type="warning"
size="small"
class="mr-1"
class="mr-1 cf-slip-no-export"
>待审核</el-tag>
<el-tag v-else type="danger" size="small" class="mr-1">已驳回</el-tag>
<el-tag v-else type="danger" size="small" class="mr-1 cf-slip-no-export">已驳回</el-tag>
<template v-if="Number(prescriptionViewData.void_status) !== 1">
<div>普通处方</div>
<div class="text-sm">当日有效</div>
@@ -1397,7 +1422,7 @@
</div>
<div
v-else-if="Number(prescriptionViewData.business_prescription_audit_rejected) === 1"
class="rounded border border-red-200 bg-red-50 text-red-800 text-sm px-3 py-2 mb-3"
class="cf-slip-no-export rounded border border-red-200 bg-red-50 text-red-800 text-sm px-3 py-2 mb-3"
>
<div class="font-medium">审核状态已驳回业务订单·处方审核</div>
<div v-if="prescriptionViewData.business_prescription_audit_remark" class="mt-1">
@@ -1407,7 +1432,7 @@
</div>
<div
v-else-if="Number(prescriptionViewData.audit_status) === 2"
class="rounded border border-red-200 bg-red-50 text-red-800 text-sm px-3 py-2 mb-3"
class="cf-slip-no-export rounded border border-red-200 bg-red-50 text-red-800 text-sm px-3 py-2 mb-3"
>
<div class="font-medium">消费者处方审核已驳回</div>
<div v-if="prescriptionViewData.audit_remark" class="mt-1">驳回意见{{ prescriptionViewData.audit_remark }}</div>
@@ -1451,6 +1476,9 @@
<span class="cf-slip-inline-gap" />
服用方式: {{ prescriptionViewData.usage_way ? ' ' + prescriptionViewData.usage_way : '' }}
</div>
<div v-if="slipChuwanVisible">
出丸{{ slipPillGrams === null ? '' : slipPillGrams }}
</div>
<div v-if="slipDietaryText" class="cf-slip-dietary">忌口{{ slipDietaryText }}</div>
<div v-if="prescriptionViewData.usage_notes" class="cf-slip-notes">{{ prescriptionViewData.usage_notes }}</div>
</div>
@@ -1642,6 +1670,8 @@ import {
prescriptionOrderPreviewGancaoRecipel,
prescriptionDetail
} from '@/api/tcm'
import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'
import { getDictData } from '@/api/app'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
@@ -1926,10 +1956,19 @@ function formatTime(v: unknown) {
return String(v)
}
function canEditRow(row: { fulfillment_status?: number }) {
function canEditRow(row: {
fulfillment_status?: number
gancao_reciperl_order_no?: string | null
gancao_submit_time?: number | null
}) {
// 已发货(5)、已签收(6)、已完成(3)、已取消(4) 不可编辑
const fs = Number(row.fulfillment_status)
return fs === 1 || fs === 2
if (fs !== 1 && fs !== 2) return false
// 已成功提交甘草(已生成甘草处方单号 或 提交时间 > 0)不允许再编辑
const gcNo = String(row.gancao_reciperl_order_no || '').trim()
const gcTime = Number(row.gancao_submit_time || 0)
if (gcNo !== '' || gcTime > 0) return false
return true
}
function canRxAudit(row: { prescription_audit_status?: number; fulfillment_status?: number }) {
@@ -2097,7 +2136,7 @@ async function loadDetailUnlinkedPayOrders(diagnosisId: number, prescriptionOrde
prescription_order_id: prescriptionOrderId
})
const lists = res?.lists ?? res?.data?.lists ?? []
// 过滤掉已关联的支付单
// 列表已由服务端限制为 2026-04-20 及之后创建的支付单(本单已关联除外);此处再过滤已关联项
const filtered = Array.isArray(lists)
? lists.filter((item: any) => !linkedIds.includes(item.id))
: []
@@ -2386,6 +2425,7 @@ async function loadEditPaidOrders(diagnosisId: number, prescriptionOrderId?: num
...(prescriptionOrderId ? { prescription_order_id: prescriptionOrderId } : {})
})
const lists = res?.lists ?? res?.data?.lists ?? []
// 可选支付单:服务端仅 2026-04-20 及之后;编辑时含本单已关联的旧单
editPaidOrders.value = Array.isArray(lists) ? lists : []
const dep = Number(res?.deposit_min_amount ?? res?.data?.deposit_min_amount)
if (!Number.isNaN(dep) && dep >= 0) {
@@ -2993,7 +3033,7 @@ async function loadAddPayOrderAvailable(diagnosisId: number, prescriptionOrderId
prescription_order_id: prescriptionOrderId
})
const lists = res?.lists ?? res?.data?.lists ?? []
// 过滤掉当前订单已关联的支付单
// 服务端仅 2026-04-20 及之后;再排除当前订单已关联
const filtered = Array.isArray(lists)
? lists.filter((item: any) => !currentLinkedIds.includes(item.id))
: []
@@ -3155,6 +3195,8 @@ const SLIP_ADDRESS_LINE = '地址:四川省成都市双流区黄甲街道黄
const prescriptionViewVisible = ref(false)
const prescriptionViewLoading = ref(false)
const prescriptionViewData = ref<any>(null)
const prescriptionSlipPrintRef = ref<HTMLElement | null>(null)
const prescriptionSlipExporting = ref(false)
// 处方单辅助函数
const slipHerbsList = computed(() => {
@@ -3169,6 +3211,23 @@ const slipDietaryText = computed(() => {
return ''
})
const slipChuwanVisible = computed(() => {
const d = prescriptionViewData.value
if (!d) return false
return (d.prescription_type || '浓缩水丸') === '浓缩水丸'
})
const slipPillGrams = computed(() => {
const d = prescriptionViewData.value
if (!d || (d.prescription_type || '浓缩水丸') !== '浓缩水丸') return null
const days = Number(d.usage_days ?? d.dose_count)
const times = Number(d.times_per_day)
const doseG = Number(d.dosage_amount)
if (!Number.isFinite(days) || !Number.isFinite(times) || !Number.isFinite(doseG)) return null
if (days <= 0 || times <= 0 || doseG <= 0) return null
return days * times * doseG
})
function formatAuditTime(ts: number | null | undefined) {
if (!ts) return ''
const d = new Date(ts * 1000)
@@ -3215,6 +3274,93 @@ async function openPrescriptionView(row: any) {
prescriptionViewLoading.value = false
}
}
async function preloadSlipImagesToDataUrl(el: HTMLElement): Promise<void> {
const imgs = el.querySelectorAll<HTMLImageElement>('img[src^="http"]')
const tasks = Array.from(imgs).map(async (img) => {
try {
const res = await fetch(img.src, { mode: 'cors' })
if (!res.ok) return
const blob = await res.blob()
const dataUrl = await new Promise<string>((resolve, reject) => {
const r = new FileReader()
r.onload = () => resolve(r.result as string)
r.onerror = reject
r.readAsDataURL(blob)
})
img.src = dataUrl
} catch {
/* 跨域失败时保留原 src */
}
})
await Promise.allSettled(tasks)
}
async function capturePrescriptionSlipCanvas(): Promise<HTMLCanvasElement> {
await nextTick()
const el = prescriptionSlipPrintRef.value
if (!el) throw new Error('未找到处方笺内容')
el.scrollIntoView({ behavior: 'instant', block: 'nearest' })
await preloadSlipImagesToDataUrl(el)
await new Promise((r) => setTimeout(r, 200))
return html2canvas(el, {
scale: 2,
useCORS: true,
logging: false,
backgroundColor: '#ffffff',
ignoreElements: (node) =>
node instanceof HTMLElement && node.classList.contains('cf-slip-no-export')
})
}
function slipDownloadBaseName() {
const name = prescriptionViewData.value?.patient_name
const safe = typeof name === 'string' && name.trim() ? name.trim().replace(/[\\/:*?"<>|]/g, '_') : '未命名'
return `处方_${safe}_${Date.now()}`
}
async function downloadPrescriptionSlipImage() {
if (!prescriptionViewData.value) return
prescriptionSlipExporting.value = true
try {
const canvas = await capturePrescriptionSlipCanvas()
const link = document.createElement('a')
link.download = `${slipDownloadBaseName()}.png`
link.href = canvas.toDataURL('image/png')
link.click()
feedback.msgSuccess('图片已下载')
} catch {
feedback.msgError('下载图片失败')
} finally {
prescriptionSlipExporting.value = false
}
}
async function downloadPrescriptionSlipPdf() {
if (!prescriptionViewData.value) return
prescriptionSlipExporting.value = true
try {
const canvas = await capturePrescriptionSlipCanvas()
const imgData = canvas.toDataURL('image/png')
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a5'
})
const pageW = pdf.internal.pageSize.getWidth()
const pageH = pdf.internal.pageSize.getHeight()
const scale = Math.min(pageW / canvas.width, pageH / canvas.height)
const imgW = canvas.width * scale
const imgH = canvas.height * scale
pdf.addImage(imgData, 'PNG', 0, 0, imgW, imgH)
pdf.save(`${slipDownloadBaseName()}.pdf`)
feedback.msgSuccess('PDF已下载')
} catch {
feedback.msgError('下载PDF失败')
} finally {
prescriptionSlipExporting.value = false
}
}
</script>
<style scoped lang="scss">