Merge branch 'master' into kpi-statis
This commit is contained in:
@@ -762,7 +762,7 @@ const formData = reactive<PrescriptionForm>({
|
||||
appointment_id: 0,
|
||||
prescription_name: '',
|
||||
prescription_type: '浓缩水丸',
|
||||
dosage_amount: 1,
|
||||
dosage_amount: 10,
|
||||
dosage_unit: 'g',
|
||||
need_decoction: false,
|
||||
bags_per_dose: 1,
|
||||
@@ -952,6 +952,8 @@ const open = async (data: any, options?: { templateId?: number; stationName?: st
|
||||
formData.appointment_id = appointmentId
|
||||
formData.prescription_name = ''
|
||||
formData.prescription_type = '浓缩水丸'
|
||||
formData.dosage_unit = 'g'
|
||||
formData.dosage_amount = 10
|
||||
formData.patient_name = data.patient_name || diagnosis.patient_name || ''
|
||||
formData.gender = data.patient_gender ?? diagnosis.gender ?? 0
|
||||
formData.gender_desc = formData.gender === 1 ? '男' : '女'
|
||||
@@ -969,8 +971,8 @@ const open = async (data: any, options?: { templateId?: number; stationName?: st
|
||||
formData.dose_count = 1
|
||||
formData.dose_unit = '剂'
|
||||
formData.usage_days = 7
|
||||
formData.usage_instruction = '水煎服,一日二次'
|
||||
formData.usage_time = '饭前'
|
||||
formData.usage_instruction = ''
|
||||
formData.usage_time = '饭后'
|
||||
formData.usage_way = '温水送服'
|
||||
formData.dietary_taboo = []
|
||||
formData.usage_notes = ''
|
||||
@@ -1203,7 +1205,7 @@ watch(showLibraryDialog, (newVal) => {
|
||||
watch(() => formData.prescription_type, (newType) => {
|
||||
if (newType === '浓缩水丸') {
|
||||
formData.dosage_unit = 'g'
|
||||
formData.dosage_amount = 1
|
||||
formData.dosage_amount = 10
|
||||
formData.need_decoction = false
|
||||
formData.bags_per_dose = 1
|
||||
} else if (newType === '饮片') {
|
||||
@@ -1349,6 +1351,16 @@ const handleNewPrescription = async () => {
|
||||
formData.appointment_id = saved.appointment_id ?? formData.appointment_id
|
||||
formData.prescription_name = ''
|
||||
formData.prescription_type = saved.prescription_type || '浓缩水丸'
|
||||
if (formData.prescription_type === '浓缩水丸') {
|
||||
formData.dosage_unit = 'g'
|
||||
formData.dosage_amount = 10
|
||||
} else if (formData.prescription_type === '饮片') {
|
||||
formData.dosage_unit = 'ml'
|
||||
formData.dosage_amount = 50
|
||||
} else {
|
||||
formData.dosage_unit = 'g'
|
||||
formData.dosage_amount = undefined
|
||||
}
|
||||
formData.patient_name = saved.patient_name || ''
|
||||
formData.gender = saved.gender ?? 0
|
||||
formData.gender_desc = saved.gender === 1 ? '男' : '女'
|
||||
@@ -1364,8 +1376,8 @@ const handleNewPrescription = async () => {
|
||||
formData.dose_count = 1
|
||||
formData.dose_unit = saved.dose_unit || '剂'
|
||||
formData.usage_days = 7
|
||||
formData.usage_instruction = '水煎服,一日二次'
|
||||
formData.usage_time = '饭前'
|
||||
formData.usage_instruction = ''
|
||||
formData.usage_time = '饭后'
|
||||
formData.usage_way = '温水送服'
|
||||
formData.dietary_taboo = []
|
||||
formData.usage_notes = ''
|
||||
|
||||
@@ -53,6 +53,15 @@
|
||||
</div>
|
||||
<div class="pl-toolbar__line pl-toolbar__line--second">
|
||||
<el-form :model="formData" inline class="pl-toolbar__form">
|
||||
<el-form-item label="处方编号" class="!mb-0">
|
||||
<el-input
|
||||
v-model="formData.sn"
|
||||
placeholder="编号"
|
||||
clearable
|
||||
class="pl-toolbar__input"
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="患者" class="!mb-0">
|
||||
<el-input
|
||||
v-model="formData.patient_name"
|
||||
@@ -559,9 +568,9 @@
|
||||
</el-button>
|
||||
<el-table :data="editForm.herbs" class="mt-2" border>
|
||||
<el-table-column label="序号" type="index" width="60" />
|
||||
<el-table-column label="药材名称" min-width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<el-input v-model="row.name" placeholder="请输入药材名称" />
|
||||
<el-table-column label="药材名称" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<MedicineNameSelect v-model="row.name" class="w-full" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剂量(克)" min-width="120">
|
||||
@@ -795,41 +804,22 @@
|
||||
<el-form-item label="医师姓名" prop="doctor_name">
|
||||
<el-input v-model="editForm.doctor_name" placeholder="请输入医师姓名" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否共享" prop="is_shared">
|
||||
<el-switch
|
||||
v-model="editForm.is_shared"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="所有人可见"
|
||||
inactive-text="仅自己可见"
|
||||
/>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
勾选后,所有医生都可以查看和使用此处方模板
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="指定可见角色" prop="visible_role_ids">
|
||||
<el-select
|
||||
v-model="editForm.visible_role_ids"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="不选则除创建者外仅按「是否共享」规则;可多选角色"
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="r in roleOptions"
|
||||
:key="r.id"
|
||||
:label="r.name"
|
||||
:value="r.id"
|
||||
<el-form-item label="医师签名" prop="doctor_signature" required>
|
||||
<div class="signature-pad-wrap">
|
||||
<canvas
|
||||
ref="signatureCanvasRef"
|
||||
class="signature-canvas"
|
||||
@pointerdown.prevent="onSignaturePointerDown"
|
||||
@pointermove.prevent="onSignaturePointerMove"
|
||||
@pointerup.prevent="onSignaturePointerUp"
|
||||
@pointercancel.prevent="onSignaturePointerUp"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
未共享时:所选角色下的账号也可在列表中查看此处方(与创建者并列)
|
||||
<div class="signature-actions">
|
||||
<el-button size="small" @click="clearSignature">清空签名</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-amber-600 mt-1">* 必填,请在框内手写医师签名</div>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
@@ -963,7 +953,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="疗程" class="create-order-form-item--inline-num">
|
||||
<el-form-item label="服用天数" class="create-order-form-item--inline-num">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="createOrderForm.medication_days"
|
||||
@@ -1282,6 +1272,7 @@ import useUserStore from '@/stores/modules/user'
|
||||
import feedback from '@/utils/feedback'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
||||
import MedicineNameSelect from '@/components/medicine-name-select/index.vue'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, reactive, ref, watch, nextTick, defineAsyncComponent } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -1310,6 +1301,8 @@ function goBusinessOrderList() {
|
||||
|
||||
// 表单数据(与列表接口查询参数一致)
|
||||
const formData = reactive({
|
||||
/** 处方编号(后台 sn,模糊匹配) */
|
||||
sn: '',
|
||||
patient_name: '',
|
||||
/** 处方创建人(医师账号 ID),多选 */
|
||||
creator_ids: [] as number[],
|
||||
@@ -1330,6 +1323,12 @@ const showEdit = ref(false)
|
||||
const editMode = ref<'add' | 'edit' | 'view'>('add')
|
||||
const submitLoading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const signatureCanvasRef = ref<HTMLCanvasElement>()
|
||||
/** 当前一笔的 pointerId(配合 setPointerCapture,移出画布仍跟手) */
|
||||
const signatureActivePointerId = ref<number | null>(null)
|
||||
/** 签名板 CSS 像素尺寸(内部分辨率按 DPR 放大,触摸更跟手) */
|
||||
const SIGNATURE_CSS_W = 560
|
||||
const SIGNATURE_CSS_H = 168
|
||||
/** 查看模式:处方笺数据源(先列表行,再详情覆盖) */
|
||||
const slipView = ref<Record<string, any> | null>(null)
|
||||
const viewDetailLoading = ref(false)
|
||||
@@ -1890,12 +1889,14 @@ const editForm = reactive({
|
||||
dose_count: 7,
|
||||
dose_unit: '剂',
|
||||
usage_days: 7,
|
||||
usage_instruction: '水煎服,一日二次',
|
||||
usage_time: '饭前',
|
||||
usage_instruction: '',
|
||||
usage_time: '饭后',
|
||||
usage_way: '温水送服',
|
||||
dietary_taboo: [] as string[],
|
||||
usage_notes: '',
|
||||
doctor_name: '',
|
||||
/** 医师电子签名 PNG data URL(与诊间处方一致) */
|
||||
doctor_signature: '',
|
||||
is_shared: 0,
|
||||
visible_role_ids: [] as number[],
|
||||
audit_status: 1,
|
||||
@@ -1973,6 +1974,19 @@ const rules: FormRules = {
|
||||
],
|
||||
doctor_name: [
|
||||
{ required: true, message: '请输入医师姓名', trigger: 'blur' }
|
||||
],
|
||||
doctor_signature: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule, v, cb) => {
|
||||
if (!v || !String(v).trim()) {
|
||||
cb(new Error('请使用电子签名板手写医师签名'))
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2079,6 +2093,7 @@ function formatListCreateTime(v: unknown) {
|
||||
function resetParams() {
|
||||
syncingDateTab = true
|
||||
dateQuickTab.value = 'all'
|
||||
formData.sn = ''
|
||||
formData.patient_name = ''
|
||||
formData.creator_ids = []
|
||||
formData.audit_filter = 'all'
|
||||
@@ -2186,6 +2201,124 @@ function rowToSlipView(row: any) {
|
||||
|
||||
function onEditDialogClosed() {
|
||||
slipView.value = null
|
||||
signatureActivePointerId.value = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名坐标:与 initSignatureCanvas 里 ctx.scale(dpr) 后的「逻辑像素」一致(0…SIGNATURE_CSS_W/H),
|
||||
* 不能用 canvas.width/rect.width(会把 dpr 乘两次,笔画和鼠标严重错位)。
|
||||
*/
|
||||
function getSignatureLocalPoint(e: PointerEvent) {
|
||||
const canvas = signatureCanvasRef.value
|
||||
if (!canvas) return { x: 0, y: 0 }
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
if (rect.width <= 0 || rect.height <= 0) return { x: 0, y: 0 }
|
||||
const x = ((e.clientX - rect.left) / rect.width) * SIGNATURE_CSS_W
|
||||
const y = ((e.clientY - rect.top) / rect.height) * SIGNATURE_CSS_H
|
||||
return {
|
||||
x: Math.max(0, Math.min(SIGNATURE_CSS_W, x)),
|
||||
y: Math.max(0, Math.min(SIGNATURE_CSS_H, y))
|
||||
}
|
||||
}
|
||||
|
||||
function commitSignatureImage() {
|
||||
const canvas = signatureCanvasRef.value
|
||||
if (canvas) {
|
||||
editForm.doctor_signature = canvas.toDataURL('image/png')
|
||||
}
|
||||
nextTick(() => {
|
||||
formRef.value?.validateField('doctor_signature').catch(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
function onSignaturePointerDown(e: PointerEvent) {
|
||||
if (e.pointerType === 'mouse' && e.button !== 0) return
|
||||
const canvas = signatureCanvasRef.value
|
||||
const ctx = canvas?.getContext('2d')
|
||||
if (!canvas || !ctx) return
|
||||
canvas.setPointerCapture(e.pointerId)
|
||||
signatureActivePointerId.value = e.pointerId
|
||||
const { x, y } = getSignatureLocalPoint(e)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, y)
|
||||
}
|
||||
|
||||
function onSignaturePointerMove(e: PointerEvent) {
|
||||
if (signatureActivePointerId.value !== e.pointerId) return
|
||||
const ctx = signatureCanvasRef.value?.getContext('2d')
|
||||
if (!ctx) return
|
||||
const { x, y } = getSignatureLocalPoint(e)
|
||||
ctx.lineTo(x, y)
|
||||
ctx.stroke()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, y)
|
||||
}
|
||||
|
||||
function onSignaturePointerUp(e: PointerEvent) {
|
||||
if (signatureActivePointerId.value !== e.pointerId) return
|
||||
signatureActivePointerId.value = null
|
||||
const canvas = signatureCanvasRef.value
|
||||
if (canvas) {
|
||||
try {
|
||||
canvas.releasePointerCapture(e.pointerId)
|
||||
} catch {
|
||||
/* 已释放 */
|
||||
}
|
||||
}
|
||||
commitSignatureImage()
|
||||
}
|
||||
|
||||
function clearSignature() {
|
||||
initSignatureCanvas()
|
||||
editForm.doctor_signature = ''
|
||||
nextTick(() => {
|
||||
formRef.value?.validateField('doctor_signature').catch(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
function initSignatureCanvas() {
|
||||
const canvas = signatureCanvasRef.value
|
||||
if (!canvas) return
|
||||
const dpr = Math.min(typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, 2.5)
|
||||
canvas.style.width = `${SIGNATURE_CSS_W}px`
|
||||
canvas.style.height = `${SIGNATURE_CSS_H}px`
|
||||
canvas.width = Math.round(SIGNATURE_CSS_W * dpr)
|
||||
canvas.height = Math.round(SIGNATURE_CSS_H * dpr)
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
ctx.scale(dpr, dpr)
|
||||
ctx.fillStyle = '#fff'
|
||||
ctx.fillRect(0, 0, SIGNATURE_CSS_W, SIGNATURE_CSS_H)
|
||||
ctx.strokeStyle = '#333'
|
||||
ctx.lineWidth = 2.5
|
||||
ctx.lineCap = 'round'
|
||||
ctx.lineJoin = 'round'
|
||||
}
|
||||
|
||||
/** 抽屉打开后初始化画布;编辑时若有已存签名则绘制到画布 */
|
||||
function scheduleEditSignatureInit() {
|
||||
if (editMode.value === 'view') return
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
initSignatureCanvas()
|
||||
const sig = editForm.doctor_signature
|
||||
if (sig && String(sig).trim().startsWith('data:image') && signatureCanvasRef.value) {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
const canvas = signatureCanvasRef.value
|
||||
const ctx = canvas?.getContext('2d')
|
||||
if (!canvas || !ctx) return
|
||||
ctx.drawImage(img, 0, 0, SIGNATURE_CSS_W, SIGNATURE_CSS_H)
|
||||
commitSignatureImage()
|
||||
}
|
||||
img.onerror = () => {
|
||||
commitSignatureImage()
|
||||
}
|
||||
img.src = sig
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
function formatVisibleRoleNames(ids: number[] | undefined) {
|
||||
@@ -2380,12 +2513,13 @@ const resetForm = () => {
|
||||
editForm.dose_count = 7
|
||||
editForm.dose_unit = '剂'
|
||||
editForm.usage_days = 7
|
||||
editForm.usage_instruction = '水煎服,一日二次'
|
||||
editForm.usage_time = '饭前'
|
||||
editForm.usage_instruction = ''
|
||||
editForm.usage_time = '饭后'
|
||||
editForm.usage_way = '温水送服'
|
||||
editForm.dietary_taboo = []
|
||||
editForm.usage_notes = ''
|
||||
editForm.doctor_name = ''
|
||||
editForm.doctor_signature = ''
|
||||
editForm.is_shared = 0
|
||||
editForm.visible_role_ids = []
|
||||
editForm.audit_status = 1
|
||||
@@ -2402,6 +2536,7 @@ const resetForm = () => {
|
||||
// 新增处方
|
||||
const handleAdd = () => {
|
||||
resetForm()
|
||||
editForm.doctor_name = String(userStore.userInfo?.name || '')
|
||||
editingWasVoid.value = false
|
||||
editingWasRejected.value = false
|
||||
editMode.value = 'add'
|
||||
@@ -2410,6 +2545,7 @@ const handleAdd = () => {
|
||||
showEdit.value = true
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
scheduleEditSignatureInit()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2437,28 +2573,40 @@ const handleView = async (row: any) => {
|
||||
}
|
||||
|
||||
// 编辑处方
|
||||
const handleEdit = (row: any) => {
|
||||
const handleEdit = async (row: any) => {
|
||||
editingWasVoid.value = Number(row.void_status) === 1
|
||||
editingWasRejected.value = Number(row.audit_status) === 2
|
||||
editMode.value = 'edit'
|
||||
|
||||
// 开始加载数据,暂时禁用watch自动重置
|
||||
isLoadingData.value = true
|
||||
|
||||
let src = row
|
||||
if (row?.id && !row.doctor_signature) {
|
||||
try {
|
||||
const d = await prescriptionDetail({ id: row.id })
|
||||
if (d && typeof d === 'object') {
|
||||
src = { ...row, ...d }
|
||||
}
|
||||
} catch {
|
||||
/* 保持列表行 */
|
||||
}
|
||||
}
|
||||
|
||||
// 深拷贝数据到表单
|
||||
editForm.id = row.id
|
||||
editForm.prescription_type = row.prescription_type || '浓缩水丸'
|
||||
editForm.id = src.id
|
||||
editForm.prescription_type = src.prescription_type || '浓缩水丸'
|
||||
|
||||
// 确保 dosage_amount 是数字类型,以匹配下拉框的 :value
|
||||
if (row.dosage_amount !== null && row.dosage_amount !== undefined && row.dosage_amount !== '') {
|
||||
editForm.dosage_amount = Number(row.dosage_amount)
|
||||
if (src.dosage_amount !== null && src.dosage_amount !== undefined && src.dosage_amount !== '') {
|
||||
editForm.dosage_amount = Number(src.dosage_amount)
|
||||
} else {
|
||||
editForm.dosage_amount = undefined
|
||||
}
|
||||
|
||||
// 如果数据库中没有 dosage_unit,根据 prescription_type 设置默认值
|
||||
if (row.dosage_unit) {
|
||||
editForm.dosage_unit = row.dosage_unit
|
||||
if (src.dosage_unit) {
|
||||
editForm.dosage_unit = src.dosage_unit
|
||||
} else {
|
||||
if (editForm.prescription_type === '饮片') {
|
||||
editForm.dosage_unit = 'ml'
|
||||
@@ -2467,40 +2615,43 @@ const handleEdit = (row: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
editForm.need_decoction = row.need_decoction === 1 || row.need_decoction === true
|
||||
editForm.bags_per_dose = row.bags_per_dose ? Number(row.bags_per_dose) : 1
|
||||
editForm.patient_name = row.patient_name || ''
|
||||
editForm.gender = row.gender ?? 1
|
||||
editForm.age = row.age ?? 0
|
||||
editForm.visit_no = row.visit_no || ''
|
||||
editForm.prescription_date = row.prescription_date || ''
|
||||
editForm.tongue = row.tongue || ''
|
||||
editForm.tongue_image = row.tongue_image || ''
|
||||
editForm.pulse = row.pulse || ''
|
||||
editForm.pulse_condition = row.pulse_condition || ''
|
||||
editForm.clinical_diagnosis = row.clinical_diagnosis || ''
|
||||
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
||||
editForm.dose_count = row.dose_count ?? 7
|
||||
editForm.dose_unit = row.dose_unit || '剂'
|
||||
editForm.usage_days = row.usage_days ?? 7
|
||||
editForm.times_per_day = row.times_per_day ?? 2
|
||||
editForm.usage_instruction = row.usage_instruction || '水煎服,一日二次'
|
||||
editForm.usage_time = row.usage_time || '饭前'
|
||||
editForm.usage_way = row.usage_way || '温水送服'
|
||||
editForm.dietary_taboo = row.dietary_taboo ? (Array.isArray(row.dietary_taboo) ? row.dietary_taboo : row.dietary_taboo.split(',').filter((v: string) => v)) : []
|
||||
editForm.usage_notes = row.usage_notes || ''
|
||||
editForm.doctor_name = row.doctor_name || ''
|
||||
editForm.is_shared = row.is_shared ?? 0
|
||||
editForm.visible_role_ids = normalizeVisibleRoleIds(row)
|
||||
editForm.audit_status = row.audit_status ?? 1
|
||||
editForm.audit_time = row.audit_time ?? null
|
||||
editForm.audit_by_name = row.audit_by_name || ''
|
||||
editForm.audit_remark = row.audit_remark || ''
|
||||
editForm.diagnosis_id = row.diagnosis_id ?? 0
|
||||
editForm.creator_id = Number(row.creator_id) > 0 ? Number(row.creator_id) : 0
|
||||
editForm.is_system_auto = Number(row.is_system_auto) === 1 ? 1 : 0
|
||||
editForm.business_prescription_audit_rejected = Number(row.business_prescription_audit_rejected) === 1 ? 1 : 0
|
||||
editForm.business_prescription_audit_remark = String(row.business_prescription_audit_remark || '')
|
||||
editForm.need_decoction = src.need_decoction === 1 || src.need_decoction === true
|
||||
editForm.bags_per_dose = src.bags_per_dose ? Number(src.bags_per_dose) : 1
|
||||
editForm.patient_name = src.patient_name || ''
|
||||
editForm.gender = src.gender ?? 1
|
||||
editForm.age = src.age ?? 0
|
||||
editForm.visit_no = src.visit_no || ''
|
||||
editForm.prescription_date = src.prescription_date || ''
|
||||
editForm.tongue = src.tongue || ''
|
||||
editForm.tongue_image = src.tongue_image || ''
|
||||
editForm.pulse = src.pulse || ''
|
||||
editForm.pulse_condition = src.pulse_condition || ''
|
||||
editForm.clinical_diagnosis = src.clinical_diagnosis || ''
|
||||
editForm.herbs = src.herbs ? JSON.parse(JSON.stringify(src.herbs)) : []
|
||||
editForm.dose_count = src.dose_count ?? 7
|
||||
editForm.dose_unit = src.dose_unit || '剂'
|
||||
editForm.usage_days = src.usage_days ?? 7
|
||||
editForm.times_per_day = src.times_per_day ?? 2
|
||||
editForm.usage_instruction = src.usage_instruction || ''
|
||||
editForm.usage_time = src.usage_time || '饭后'
|
||||
editForm.usage_way = src.usage_way || '温水送服'
|
||||
editForm.dietary_taboo = src.dietary_taboo
|
||||
? (Array.isArray(src.dietary_taboo) ? src.dietary_taboo : String(src.dietary_taboo).split(',').filter((v: string) => v))
|
||||
: []
|
||||
editForm.usage_notes = src.usage_notes || ''
|
||||
editForm.doctor_name = src.doctor_name || ''
|
||||
editForm.doctor_signature = String(src.doctor_signature || '')
|
||||
editForm.is_shared = src.is_shared ?? 0
|
||||
editForm.visible_role_ids = normalizeVisibleRoleIds(src)
|
||||
editForm.audit_status = src.audit_status ?? 1
|
||||
editForm.audit_time = src.audit_time ?? null
|
||||
editForm.audit_by_name = src.audit_by_name || ''
|
||||
editForm.audit_remark = src.audit_remark || ''
|
||||
editForm.diagnosis_id = src.diagnosis_id ?? 0
|
||||
editForm.creator_id = Number(src.creator_id) > 0 ? Number(src.creator_id) : 0
|
||||
editForm.is_system_auto = Number(src.is_system_auto) === 1 ? 1 : 0
|
||||
editForm.business_prescription_audit_rejected = Number(src.business_prescription_audit_rejected) === 1 ? 1 : 0
|
||||
editForm.business_prescription_audit_remark = String(src.business_prescription_audit_remark || '')
|
||||
|
||||
// 数据加载完成,重新启用watch
|
||||
// 使用 setTimeout 确保所有数据都已经渲染完成
|
||||
@@ -2511,6 +2662,7 @@ const handleEdit = (row: any) => {
|
||||
showEdit.value = true
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
scheduleEditSignatureInit()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3188,4 +3340,24 @@ onMounted(async () => {
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.signature-pad-wrap {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
.signature-canvas {
|
||||
display: block;
|
||||
cursor: crosshair;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.signature-actions {
|
||||
padding: 8px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -78,8 +78,20 @@
|
||||
<el-icon :size="16" class="text-sky-500"><Calendar /></el-icon>
|
||||
{{ listStats.orderHeading }}
|
||||
</div>
|
||||
<div class="mt-2 text-2xl sm:text-[26px] font-bold text-slate-800 tracking-tight tabular-nums">
|
||||
¥{{ listStats.orderStr }}
|
||||
<div class="mt-2 space-y-1.5 min-w-0">
|
||||
<div class="flex flex-col sm:flex-row sm:flex-wrap gap-x-4 gap-y-1 text-sm sm:text-base">
|
||||
<span class="text-slate-600">
|
||||
未取消
|
||||
<span class="text-xl sm:text-[22px] font-bold text-slate-800 tabular-nums"
|
||||
>¥{{ listStats.orderNotCancelledStr }}</span>
|
||||
</span>
|
||||
<span class="text-slate-600">
|
||||
已取消
|
||||
<span class="text-xl sm:text-[22px] font-bold text-slate-500 tabular-nums"
|
||||
>¥{{ listStats.orderCancelledStr }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 tabular-nums">合计 ¥{{ listStats.orderStr }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -92,6 +104,9 @@
|
||||
{{ listStats.periodLine }}
|
||||
</p>
|
||||
<p class="relative mt-1.5 text-xs text-slate-400 leading-relaxed">
|
||||
{{ listStats.orderSplitHint }}
|
||||
</p>
|
||||
<p class="relative mt-0.5 text-xs text-slate-400 leading-relaxed">
|
||||
{{ listStats.scopeHint }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -107,8 +122,20 @@
|
||||
<el-icon :size="16" class="text-emerald-500"><LinkIcon /></el-icon>
|
||||
{{ listStats.payHeading }}
|
||||
</div>
|
||||
<div class="mt-2 text-2xl sm:text-[26px] font-bold text-slate-800 tracking-tight tabular-nums">
|
||||
¥{{ listStats.payStr }}
|
||||
<div class="mt-2 space-y-1.5 min-w-0">
|
||||
<div class="flex flex-col sm:flex-row sm:flex-wrap gap-x-4 gap-y-1 text-sm sm:text-base">
|
||||
<span class="text-slate-600">
|
||||
未取消
|
||||
<span class="text-xl sm:text-[22px] font-bold text-slate-800 tabular-nums"
|
||||
>¥{{ listStats.payNotCancelledStr }}</span>
|
||||
</span>
|
||||
<span class="text-slate-600">
|
||||
已取消
|
||||
<span class="text-xl sm:text-[22px] font-bold text-slate-500 tabular-nums"
|
||||
>¥{{ listStats.payCancelledStr }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 tabular-nums">合计 ¥{{ listStats.payStr }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -121,7 +148,7 @@
|
||||
{{ listStats.periodLine }}
|
||||
</p>
|
||||
<p class="relative mt-1.5 text-xs text-slate-400 leading-relaxed">
|
||||
所选时间内创建订单所关联的支付单金额(与「实付(关联)」含义一致;不含创建时间时默认今日)
|
||||
所选时间内创建订单所关联的支付单金额,按业务订单是否已取消拆分(4=已取消;与「实付(关联)」含义一致;不含创建时间时默认今日)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,6 +200,21 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[220px] min-w-[200px]" label="医助部门">
|
||||
<el-tree-select
|
||||
v-model="queryParams.assistant_dept_id"
|
||||
:data="departmentTreeRaw"
|
||||
class="!w-full"
|
||||
clearable
|
||||
filterable
|
||||
check-strictly
|
||||
:default-expand-all="true"
|
||||
node-key="id"
|
||||
:props="assistantDeptTreeProps"
|
||||
placeholder="医助部门(选父级含子级)"
|
||||
@change="onAssistantDeptChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[200px]" label="医助">
|
||||
<el-select
|
||||
v-model="queryParams.assistant_id"
|
||||
@@ -182,7 +224,7 @@
|
||||
class="!w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="a in assistantOptions"
|
||||
v-for="a in filteredAssistantOptions"
|
||||
:key="a.id"
|
||||
:label="a.name"
|
||||
:value="a.id"
|
||||
@@ -1034,6 +1076,42 @@
|
||||
</el-button>
|
||||
<span class="text-xs text-gray-400">ID: {{ editForm.prescription_id || '—' }}</span>
|
||||
</div>
|
||||
<!-- 诊单创建人及所在部门(tcm_diagnosis.admin_id;接口为「全角;」多部门、每路径「父 / 子 / …」含上级) -->
|
||||
<div class="mt-2 space-y-2 text-xs sm:text-sm">
|
||||
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
||||
<span class="text-gray-500 shrink-0">诊单创建人</span>
|
||||
<span class="text-gray-800 font-medium">
|
||||
{{ editForm.diagnosis_creator_name || '—' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="editDiagnosisCreatorDeptBreadcrumbs.length" class="space-y-1.5">
|
||||
<div class="text-gray-500">所在部门(含上级)</div>
|
||||
<div
|
||||
v-for="(segments, idx) in editDiagnosisCreatorDeptBreadcrumbs"
|
||||
:key="idx"
|
||||
class="pl-0 min-w-0"
|
||||
>
|
||||
<el-breadcrumb
|
||||
v-if="segments.length"
|
||||
class="po-diagnosis-creator-dept-breadcrumb text-[13px] leading-relaxed"
|
||||
separator="/"
|
||||
>
|
||||
<el-breadcrumb-item
|
||||
v-for="(name, j) in segments"
|
||||
:key="j"
|
||||
>
|
||||
<span class="text-gray-800 break-all">{{ name }}</span>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="text-gray-400"
|
||||
>
|
||||
所在部门:—
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="复诊">
|
||||
@@ -1041,7 +1119,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="疗程" class="create-order-form-item--inline-num">
|
||||
<el-form-item label="服用天数" class="create-order-form-item--inline-num">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="editForm.medication_days"
|
||||
@@ -1665,7 +1743,7 @@
|
||||
<div class="cf-slip-footer-left">
|
||||
<div>
|
||||
服用{{ prescriptionViewData.medication_days ?? prescriptionViewData.usage_days ?? prescriptionViewData.dose_count }}天, {{ prescriptionViewData.prescription_type || '浓缩水丸' }}
|
||||
{{ prescriptionViewData.usage_instruction || '' }}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
服用时间: {{ prescriptionViewData.usage_time ? ' ' + prescriptionViewData.usage_time : '' }}
|
||||
@@ -1673,13 +1751,14 @@
|
||||
服用方式: {{ prescriptionViewData.usage_way ? ' ' + prescriptionViewData.usage_way : '' }}
|
||||
</div>
|
||||
<div v-if="slipChuwanVisible" class="space-y-0.5">
|
||||
<div>每次用量约:{{ slipPerBagDosageText }}</div>
|
||||
<div>用量约:{{ slipPerBagDosageText }} 一天{{ prescriptionViewData.times_per_day ?? 2 }}次</div>
|
||||
<div>
|
||||
出丸:{{ slipPillGrams === null ? '—' : slipPillGrams }} 克
|
||||
</div>
|
||||
</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>
|
||||
{{ prescriptionViewData.usage_instruction || '' }}
|
||||
</div>
|
||||
<div class="cf-slip-footer-right">
|
||||
<span class="cf-slip-sign">
|
||||
@@ -1698,7 +1777,7 @@
|
||||
<div>{{ SLIP_COMPANY_LINE }}</div>
|
||||
<div>{{ SLIP_ADDRESS_LINE }}</div>
|
||||
</div>
|
||||
<div
|
||||
<!-- <div
|
||||
v-if="prescriptionViewData.audit_by_name || prescriptionViewData.audit_remark"
|
||||
class="cf-slip-audit text-sm text-gray-600 mt-4 pt-3 border-t border-dashed border-gray-200"
|
||||
>
|
||||
@@ -1709,7 +1788,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="prescriptionViewData.audit_remark" class="mt-1">审核意见:{{ prescriptionViewData.audit_remark }}</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
@@ -1875,6 +1954,7 @@ import {
|
||||
import html2canvas from 'html2canvas'
|
||||
import { jsPDF } from 'jspdf'
|
||||
import { getDictData } from '@/api/app'
|
||||
import { deptAll } from '@/api/org/department'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
@@ -1924,16 +2004,68 @@ const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||
|
||||
/** 筛选:开方医生、诊单医助(与列表接口 doctor_id / assistant_id 一致) */
|
||||
const doctorOptions = ref<Array<{ id: number; name: string }>>([])
|
||||
const assistantOptions = ref<Array<{ id: number; name: string }>>([])
|
||||
const assistantOptions = ref<Array<{ id: number; name: string; dept_ids?: number[] }>>([])
|
||||
|
||||
/** 部门树(树形下拉里展示层级;与后台 DeptLogic::getSelfAndDescendantIds 选父级含子级一致) */
|
||||
const departmentTreeRaw = ref<unknown[]>([])
|
||||
|
||||
const assistantDeptTreeProps = {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
type DeptTreeNode = { id?: unknown; name?: unknown; children?: unknown[] }
|
||||
|
||||
function findDeptTreeNode(nodes: unknown[], targetId: number): DeptTreeNode | null {
|
||||
for (const raw of nodes || []) {
|
||||
if (raw == null || typeof raw !== 'object') continue
|
||||
const n = raw as DeptTreeNode
|
||||
if (Number(n.id) === targetId) {
|
||||
return n
|
||||
}
|
||||
const sub = findDeptTreeNode((n.children || []) as unknown[], targetId)
|
||||
if (sub) {
|
||||
return sub
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function collectDeptSubtreeIds(node: DeptTreeNode): number[] {
|
||||
const id = Number(node.id)
|
||||
const out: number[] = []
|
||||
if (Number.isFinite(id) && id > 0) {
|
||||
out.push(id)
|
||||
}
|
||||
for (const c of node.children || []) {
|
||||
if (c && typeof c === 'object') {
|
||||
out.push(...collectDeptSubtreeIds(c as DeptTreeNode))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** 选中部门及全部下级 id(与列表筛选 backend 含子级一致) */
|
||||
function getDeptSelfAndDescendantIdsFromTree(rootId: number, tree: unknown[]): number[] {
|
||||
const n = findDeptTreeNode(tree, rootId)
|
||||
if (!n) {
|
||||
return [rootId]
|
||||
}
|
||||
return collectDeptSubtreeIds(n)
|
||||
}
|
||||
|
||||
async function loadDoctorAssistantOptions() {
|
||||
try {
|
||||
const [docs, ast] = await Promise.all([getDoctors(), getAssistants()])
|
||||
const [docs, ast, deptTree] = await Promise.all([getDoctors(), getAssistants(), deptAll()])
|
||||
doctorOptions.value = Array.isArray(docs) ? docs : []
|
||||
assistantOptions.value = Array.isArray(ast) ? ast : []
|
||||
const tree = Array.isArray(deptTree) ? deptTree : []
|
||||
departmentTreeRaw.value = tree
|
||||
} catch {
|
||||
doctorOptions.value = []
|
||||
assistantOptions.value = []
|
||||
departmentTreeRaw.value = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2031,6 +2163,8 @@ const queryParams = reactive({
|
||||
doctor_id: '' as number | '',
|
||||
/** 诊单医助(后台:关联诊单 assistant_id) */
|
||||
assistant_id: '' as number | '',
|
||||
/** 诊单医助所属部门(后台:assistant_dept_id,la_admin_dept) */
|
||||
assistant_dept_id: '' as number | '',
|
||||
fulfillment_status: '' as number | '',
|
||||
prescription_audit_status: '' as number | '',
|
||||
payment_slip_audit_status: '' as number | ''
|
||||
@@ -2084,6 +2218,11 @@ async function fetchLists(params: Record<string, unknown>) {
|
||||
} else {
|
||||
p.assistant_id = Number(p.assistant_id)
|
||||
}
|
||||
if (p.assistant_dept_id === '' || p.assistant_dept_id === undefined || p.assistant_dept_id === null) {
|
||||
delete p.assistant_dept_id
|
||||
} else {
|
||||
p.assistant_dept_id = Number(p.assistant_dept_id)
|
||||
}
|
||||
if (!p.order_no) delete p.order_no
|
||||
if (!String(p.patient_keyword || '').trim()) delete p.patient_keyword
|
||||
if (!String(p.start_time || '').trim() || !String(p.end_time || '').trim()) {
|
||||
@@ -2098,6 +2237,36 @@ const { pager, getLists, resetPage, resetParams } = usePaging({
|
||||
params: queryParams
|
||||
})
|
||||
|
||||
/** 选医助部门后,医助下拉仅显示该部门及子部门成员(与后台含子级一致) */
|
||||
const filteredAssistantOptions = computed(() => {
|
||||
const deptRaw = queryParams.assistant_dept_id
|
||||
if (deptRaw === '' || deptRaw == null) {
|
||||
return assistantOptions.value
|
||||
}
|
||||
const deptId = Number(deptRaw)
|
||||
if (!Number.isFinite(deptId)) {
|
||||
return assistantOptions.value
|
||||
}
|
||||
const allowed = new Set(getDeptSelfAndDescendantIdsFromTree(deptId, departmentTreeRaw.value))
|
||||
return assistantOptions.value.filter((a) => {
|
||||
const ids = a.dept_ids
|
||||
if (!Array.isArray(ids) || ids.length === 0) return false
|
||||
return ids.map(Number).some((id) => allowed.has(id))
|
||||
})
|
||||
})
|
||||
|
||||
function onAssistantDeptChange() {
|
||||
const cur = queryParams.assistant_id
|
||||
if (cur !== '' && cur != null) {
|
||||
const num = Number(cur)
|
||||
const ok = filteredAssistantOptions.value.some((item) => Number(item.id) === num)
|
||||
if (!ok) {
|
||||
queryParams.assistant_id = ''
|
||||
}
|
||||
}
|
||||
resetPage()
|
||||
}
|
||||
|
||||
const listStats = computed(() => {
|
||||
const ex = pager.extend as Record<string, unknown> | undefined
|
||||
const o = ex?.stats_order_amount ?? ex?.today_order_amount
|
||||
@@ -2108,6 +2277,16 @@ const listStats = computed(() => {
|
||||
return Math.round(x * 100) / 100
|
||||
}
|
||||
const fmt = (v: number) => v.toFixed(2)
|
||||
const hasOrderSplit =
|
||||
ex?.stats_order_amount_not_cancelled !== undefined &&
|
||||
ex?.stats_order_amount_cancelled !== undefined
|
||||
const hasPaySplit =
|
||||
ex?.stats_linked_pay_amount_not_cancelled !== undefined &&
|
||||
ex?.stats_linked_pay_amount_cancelled !== undefined
|
||||
const oNc = hasOrderSplit ? n(ex?.stats_order_amount_not_cancelled) : n(o)
|
||||
const oC = hasOrderSplit ? n(ex?.stats_order_amount_cancelled) : 0
|
||||
const pNc = hasPaySplit ? n(ex?.stats_linked_pay_amount_not_cancelled) : n(pay)
|
||||
const pC = hasPaySplit ? n(ex?.stats_linked_pay_amount_cancelled) : 0
|
||||
const t0 = String(ex?.stats_time_start ?? '').trim()
|
||||
const t1 = String(ex?.stats_time_end ?? '').trim()
|
||||
const useRange = t0 && t1
|
||||
@@ -2118,7 +2297,7 @@ const listStats = computed(() => {
|
||||
let scopeHint = '统计口径:与当前列表数据可见范围一致'
|
||||
if (scope === 'all') {
|
||||
scopeHint =
|
||||
'统计口径:支付单审核白名单角色,按全量订单;与下方筛选项(医生/医助/患者等,不含「创建时间」)一致'
|
||||
'统计口径:支付单审核白名单角色,按全量订单;与下方筛选项(医生/医助/医助部门/患者等,不含「创建时间」)一致'
|
||||
} else if (scope === 'assistant') {
|
||||
scopeHint = '统计口径:仅计诊单医助为本人且满足下方其它筛选项的订单'
|
||||
}
|
||||
@@ -2130,10 +2309,15 @@ const listStats = computed(() => {
|
||||
pay: n(pay),
|
||||
orderStr: fmt(n(o)),
|
||||
payStr: fmt(n(pay)),
|
||||
orderNotCancelledStr: fmt(oNc),
|
||||
orderCancelledStr: fmt(oC),
|
||||
payNotCancelledStr: fmt(pNc),
|
||||
payCancelledStr: fmt(pC),
|
||||
orderHeading: `${headPrefix}业务订单额`,
|
||||
payHeading: `${headPrefix}关联实付`,
|
||||
periodLine,
|
||||
scopeHint
|
||||
scopeHint,
|
||||
orderSplitHint: '未取消与已取消按履约状态区分(4=已取消;上方合计为未取消+已取消)'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2156,6 +2340,7 @@ function handleReset() {
|
||||
queryParams.patient_keyword = ''
|
||||
queryParams.doctor_id = ''
|
||||
queryParams.assistant_id = ''
|
||||
queryParams.assistant_dept_id = ''
|
||||
queryParams.fulfillment_status = ''
|
||||
queryParams.prescription_audit_status = ''
|
||||
queryParams.payment_slip_audit_status = ''
|
||||
@@ -2694,7 +2879,38 @@ const editForm = reactive({
|
||||
amount: 0,
|
||||
remark_extra: '',
|
||||
pay_order_ids: [] as number[],
|
||||
internal_cost: undefined as number | string | undefined
|
||||
internal_cost: undefined as number | string | undefined,
|
||||
/** 关联诊单医助 id(只读,来自订单详情;列表筛选用) */
|
||||
assistant_id: 0,
|
||||
/** 诊单创建人(tcm_diagnosis.admin_id,只读) */
|
||||
diagnosis_creator_id: 0,
|
||||
diagnosis_creator_name: '',
|
||||
diagnosis_creator_dept_path: ''
|
||||
})
|
||||
|
||||
/** 诊单创建人部门:拆成面包屑。多部门为「;」分隔;路径内为「 / 」含父级(与后端 buildDeptPath 一致) */
|
||||
const editDiagnosisCreatorDeptBreadcrumbs = computed(() => {
|
||||
const raw = String(editForm.diagnosis_creator_dept_path || '').trim()
|
||||
if (!raw) {
|
||||
return [] as string[][]
|
||||
}
|
||||
return raw
|
||||
.split(';')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((p) => {
|
||||
if (p.includes(' / ')) {
|
||||
return p
|
||||
.split(' / ')
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
return p
|
||||
.split('/')
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean)
|
||||
})
|
||||
.filter((segs) => segs.length > 0)
|
||||
})
|
||||
|
||||
const editPaidOrders = ref<Array<{ id: number; order_no?: string; amount?: number | string; order_type?: number | string; remark?: string; create_time?: string }>>([])
|
||||
@@ -2854,6 +3070,16 @@ async function openEdit(row: { id: number }) {
|
||||
editForm.id = d.id
|
||||
editForm.prescription_id = Number(d.prescription_id) || 0
|
||||
editForm.diagnosis_id = Number(d.diagnosis_id) || 0
|
||||
editForm.assistant_id = Number(d.assistant_id) || 0
|
||||
editForm.diagnosis_creator_id = Number(d.diagnosis_creator_id) || 0
|
||||
editForm.diagnosis_creator_name =
|
||||
d.diagnosis_creator_name != null && d.diagnosis_creator_name !== ''
|
||||
? String(d.diagnosis_creator_name)
|
||||
: ''
|
||||
editForm.diagnosis_creator_dept_path =
|
||||
d.diagnosis_creator_dept_path != null && d.diagnosis_creator_dept_path !== ''
|
||||
? String(d.diagnosis_creator_dept_path)
|
||||
: ''
|
||||
editForm.recipient_name = d.recipient_name || ''
|
||||
editForm.recipient_phone = d.recipient_phone || ''
|
||||
|
||||
@@ -4342,4 +4568,14 @@ async function downloadPrescriptionSlipPdf() {
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.po-diagnosis-creator-dept-breadcrumb {
|
||||
:deep(.el-breadcrumb__item) {
|
||||
display: inline-flex;
|
||||
float: none;
|
||||
}
|
||||
:deep(.el-breadcrumb__separator) {
|
||||
margin: 0 2px 0 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
{
|
||||
active:
|
||||
formData.pending_assign !== '1' &&
|
||||
formData.pending_unscheduled !== '1' &&
|
||||
formData.pending_booking !== '1' &&
|
||||
formData.completed_appointment !== '1' &&
|
||||
formData.appointment_date === tab.value
|
||||
}
|
||||
]"
|
||||
@@ -22,11 +23,18 @@
|
||||
<em v-if="tab.value && dateCounts[tab.value] !== undefined">{{ dateCounts[tab.value] }}</em>
|
||||
</span>
|
||||
<span
|
||||
:class="['chip', 'chip-unscheduled', { active: formData.pending_unscheduled === '1' }]"
|
||||
@click="handlePendingUnscheduledTabClick"
|
||||
:class="['chip', 'chip-pending-booking', { active: formData.pending_booking === '1' }]"
|
||||
@click="handlePendingBookingTabClick"
|
||||
>
|
||||
待预约
|
||||
<em v-if="pendingUnscheduledCount !== undefined">{{ pendingUnscheduledCount }}</em>
|
||||
<em v-if="pendingBookingCount !== undefined">{{ pendingBookingCount }}</em>
|
||||
</span>
|
||||
<span
|
||||
:class="['chip', 'chip-completed-visit', { active: formData.completed_appointment === '1' }]"
|
||||
@click="handleCompletedVisitTabClick"
|
||||
>
|
||||
已完成
|
||||
<em v-if="completedVisitCount !== undefined">{{ completedVisitCount }}</em>
|
||||
</span>
|
||||
<span
|
||||
:class="['chip', 'chip-pending', { active: formData.pending_assign === '1' }]"
|
||||
@@ -599,7 +607,11 @@ const formData = reactive({
|
||||
diagnosis_confirmed: '' as '' | '0' | '1',
|
||||
appointment_date: '' as string,
|
||||
has_appointment: '' as '' | '0' | '1',
|
||||
pending_assign: '' as '' | '1'
|
||||
pending_assign: '' as '' | '1',
|
||||
/** 顶部「待预约」Tab:仅展示已建诊单且尚未挂号(无有效预约) */
|
||||
pending_booking: '' as '' | '1',
|
||||
/** 顶部「已完成」Tab:至少有一条挂号记录为已完成 status=3 */
|
||||
completed_appointment: '' as '' | '1'
|
||||
})
|
||||
|
||||
const activeTab = ref('all')
|
||||
@@ -618,6 +630,8 @@ const confirmedOptions = [
|
||||
|
||||
const setHasAppointment = (v: string) => {
|
||||
formData.has_appointment = v as '' | '0' | '1'
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
pager.page = 1
|
||||
getLists()
|
||||
fetchDateCounts()
|
||||
@@ -625,6 +639,8 @@ const setHasAppointment = (v: string) => {
|
||||
|
||||
const setConfirmed = (v: string) => {
|
||||
formData.diagnosis_confirmed = v as '' | '0' | '1'
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
pager.page = 1
|
||||
getLists()
|
||||
fetchDateCounts()
|
||||
@@ -645,31 +661,65 @@ const dateTabs = computed(() => [
|
||||
])
|
||||
|
||||
const dateCounts = ref<Record<string, number>>({})
|
||||
const pendingBookingCount = ref<number | undefined>(undefined)
|
||||
const completedVisitCount = ref<number | undefined>(undefined)
|
||||
const pendingAssignCount = ref<number | undefined>(undefined)
|
||||
|
||||
const handleDateTabClick = async (value: string) => {
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = value
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
/** 已创建诊单、尚未挂号的记录(与第二行「未挂号」同条件,便于从 Tab 一键进入) */
|
||||
const handlePendingBookingTabClick = async () => {
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = '1'
|
||||
formData.completed_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
formData.has_appointment = '0'
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
/** 至少有一条挂号为「已完成」(后端 appointment.status=3) */
|
||||
const handleCompletedVisitTabClick = async () => {
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = '1'
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
const handlePendingAssignTabClick = async () => {
|
||||
formData.pending_assign = '1'
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
// 获取各日期挂号数量 + 待分配医助数量
|
||||
// 获取各日期挂号数量 + 待预约 + 已完成 + 待分配医助数量
|
||||
const fetchDateCounts = async () => {
|
||||
try {
|
||||
const [today, tomorrow, dayAfter, pending] = await Promise.all([
|
||||
const [today, tomorrow, dayAfter, noApt, doneVisit, pending] = await Promise.all([
|
||||
tcmDiagnosisLists({ appointment_date: todayStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: tomorrowStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: dayAfterStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ pending_assign: 1, page_no: 1, page_size: 1 })
|
||||
])
|
||||
dateCounts.value = {
|
||||
@@ -677,6 +727,8 @@ const fetchDateCounts = async () => {
|
||||
[tomorrowStr.value]: tomorrow?.count ?? 0,
|
||||
[dayAfterStr.value]: dayAfter?.count ?? 0
|
||||
}
|
||||
pendingBookingCount.value = noApt?.count ?? 0
|
||||
completedVisitCount.value = doneVisit?.count ?? 0
|
||||
pendingAssignCount.value = pending?.count ?? 0
|
||||
} catch (e) {
|
||||
console.error('获取日期数量失败', e)
|
||||
@@ -725,6 +777,8 @@ const handleTabChange = (tabName: string | number) => {
|
||||
formData.diagnosis_confirmed = ''
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
if (tabName === 'unconfirmed') {
|
||||
formData.diagnosis_confirmed = '0'
|
||||
} else if (tabName === 'confirmed') {
|
||||
@@ -805,6 +859,8 @@ const handleReset = () => {
|
||||
formData.appointment_date = ''
|
||||
formData.has_appointment = ''
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
pager.page = 1
|
||||
getLists()
|
||||
fetchDateCounts()
|
||||
@@ -1532,6 +1588,36 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.chip-pending-booking {
|
||||
margin-left: 8px;
|
||||
color: var(--el-color-info);
|
||||
background: var(--el-color-info-light-9);
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-info-light-8);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: var(--el-color-info);
|
||||
}
|
||||
}
|
||||
|
||||
.chip-completed-visit {
|
||||
margin-left: 8px;
|
||||
color: var(--el-color-success);
|
||||
background: var(--el-color-success-light-9);
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-success-light-8);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: var(--el-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
.chip-pending {
|
||||
margin-left: 8px;
|
||||
color: var(--el-color-warning);
|
||||
|
||||
Reference in New Issue
Block a user