Files
zyt/admin/src/views/consumer/prescription/index.vue
T
2026-04-18 11:08:44 +08:00

2943 lines
114 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 处方管理 -->
<template>
<div class="prescription-list">
<el-card class="filter-toolbar" shadow="never" :body-style="{ padding: '10px 14px' }">
<div class="pl-toolbar">
<div class="pl-toolbar__line">
<span class="pl-toolbar__k" title="按处方创建时间">创建时间</span>
<el-radio-group
v-model="dateQuickTab"
size="small"
class="pl-toolbar__rg"
@change="onDateQuickTabChange"
>
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="today">今日</el-radio-button>
<el-radio-button label="yesterday">昨日</el-radio-button>
<el-radio-button label="before_yesterday">前天</el-radio-button>
</el-radio-group>
<DaterangePicker
class="pl-toolbar__range"
v-model:startTime="formData.start_time"
v-model:endTime="formData.end_time"
/>
<span class="pl-toolbar__sep" aria-hidden="true" />
<span
class="pl-toolbar__k"
title="「未通过」含待审核与已驳回"
>审核</span>
<el-radio-group
v-model="formData.audit_filter"
size="small"
class="pl-toolbar__rg pl-toolbar__rg--audit"
@change="onAuditFilterChange"
>
<el-radio-button label="pending" class="pl-audit-pending-btn">待审核</el-radio-button>
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="passed">已通过</el-radio-button>
<el-radio-button label="not_passed">未通过</el-radio-button>
<el-radio-button label="rejected">驳回</el-radio-button>
</el-radio-group>
</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.patient_name"
placeholder="姓名"
clearable
class="pl-toolbar__input"
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item label="医师" class="!mb-0">
<el-select
v-model="formData.creator_ids"
multiple
collapse-tags
collapse-tags-tooltip
filterable
clearable
placeholder="全部医师"
class="pl-toolbar__select pl-toolbar__select--doctors"
>
<el-option
v-for="d in doctorOptions"
:key="d.id"
:label="d.name"
:value="Number(d.id)"
/>
</el-select>
</el-form-item>
</el-form>
<div class="pl-toolbar__actions">
<el-button type="primary" @click="resetPage">查询</el-button>
<el-button @click="resetParams">重置</el-button>
</div>
</div>
</div>
</el-card>
<el-card v-loading="pager.loading" class="mt-3 table-card" shadow="never">
<div class="pl-table-head">
<span class="pl-table-head__title">处方列表</span>
<div class="flex items-center gap-2">
<el-button
v-perms="['tcm.prescriptionOrder/lists']"
@click="goBusinessOrderList"
>
业务订单
</el-button>
<el-button type="primary" @click="handleAdd" v-perms="['cf.prescription/add']">
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增处方
</el-button>
</div>
</div>
<div class="mt-3">
<el-table :data="pager.lists" size="default" stripe>
<el-table-column label="处方编号" min-width="160">
<template #default="{ row }">
<span class="font-medium">{{ row.sn || '—' }}</span>
<div class="text-xs text-gray-400 mt-0.5">ID: {{ row.id }}</div>
</template>
</el-table-column>
<el-table-column label="处方类型" min-width="100">
<template #default="{ row }">
<el-tag type="info" size="small">{{ row.prescription_type || '—' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="患者信息" min-width="110">
<template #default="{ row }">
<div class="font-medium">{{ row.patient_name || '—' }}</div>
<div class="text-xs text-gray-500 mt-0.5">
{{ row.gender === 1 ? '男' : (row.gender === 0 ? '女' : '未知') }} · {{ row.age ? row.age + '岁' : '—' }}
</div>
</template>
</el-table-column>
<el-table-column label="审核状态" min-width="200">
<template #default="{ row }">
<div class="flex flex-col gap-1">
<div class="flex flex-wrap items-center gap-1">
<el-tag :type="listAuditStatusTagType(row)">
{{ listAuditStatusLabel(row) }}
</el-tag>
<span
v-if="Number(row.business_prescription_audit_rejected) === 1"
class="text-xs text-gray-500"
>
业务订单审核
</span>
</div>
<template v-if="listRejectReasonLines(row).length">
<div
v-for="(line, idx) in listRejectReasonLines(row)"
:key="idx"
class="text-xs text-red-600 leading-snug line-clamp-3"
:title="line"
>
{{ line }}
</div>
</template>
</div>
</template>
</el-table-column>
<el-table-column label="作废" min-width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.void_status === 1" type="danger" size="small">作废</el-tag>
<span v-else class="text-gray-400"></span>
</template>
</el-table-column>
<el-table-column label="医师信息" min-width="120">
<template #default="{ row }">
<div class="font-medium">{{ row.doctor_name || '—' }}</div>
<div class="text-xs text-gray-500 mt-0.5">{{ row.prescription_date || '—' }}</div>
</template>
</el-table-column>
<el-table-column label="医助" min-width="90">
<template #default="{ row }">
<div class="text-sm">{{ row.assistant_name || '—' }}</div>
</template>
</el-table-column>
<el-table-column label="创建时间" min-width="160">
<template #default="{ row }">
{{ formatListCreateTime(row.create_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleView(row)" v-perms="['cf.prescription/read']">
查看
</el-button>
<el-button
v-if="!Number(row.has_prescription_order)"
type="success"
link
v-perms="['tcm.prescriptionOrder/create']"
@click="openCreateOrderFromPrescription(row)"
>
创建订单
</el-button>
<el-button
v-if="!isApprovedActivePrescription(row)"
type="primary"
link
@click="handleEdit(row)"
v-perms="['cf.prescription/edit']"
>
编辑
</el-button>
<el-button
v-if="row.audit_status === 0 && row.void_status !== 1"
type="warning"
link
v-perms="['cf.prescription/audit']"
@click="openAuditDialog(row)"
>
审核
</el-button>
<el-button
v-if="!isApprovedActivePrescription(row)"
type="danger"
link
@click="handleDelete(row.id)"
v-perms="['cf.prescription/del']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="flex mt-2 justify-end">
<pagination v-model="pager" @change="getLists" />
</div>
</el-card>
<!-- 新增/编辑/查看抽屉 -->
<el-drawer
v-model="showEdit"
:title="editMode === 'add' ? '新增处方' : editMode === 'edit' ? '编辑处方' : '查看处方'"
:size="editMode === 'view' ? '920px' : '860px'"
class="cf-prescription-drawer"
:close-on-click-modal="false"
destroy-on-close
@closed="onEditDialogClosed"
>
<!-- 查看互联网医院处方笺 -->
<div v-if="editMode === 'view'" v-loading="viewDetailLoading" class="cf-slip-wrap">
<div v-if="slipView" 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(slipView.void_status) === 1" type="danger" size="small">已作废</el-tag>
<el-tag
v-else-if="Number(slipView.business_prescription_audit_rejected) === 1"
type="danger"
size="small"
class="mr-1"
>已驳回</el-tag>
<el-tag
v-else-if="Number(slipView.audit_status) === 1"
type="success"
size="small"
class="mr-1"
>已通过</el-tag>
<el-tag
v-else-if="Number(slipView.audit_status) === 0"
type="warning"
size="small"
class="mr-1"
>待审核</el-tag>
<el-tag v-else type="danger" size="small" class="mr-1">已驳回</el-tag>
<template v-if="Number(slipView.void_status) !== 1">
<div>普通处方</div>
<div class="text-sm">当日有效</div>
</template>
</div>
</div>
<div v-if="Number(slipView.void_status) === 1" class="cf-slip-void">
作废人{{ slipView.void_by_name || '—' }}作废时间{{ formatVoidTime(slipView.void_time) }}
</div>
<div
v-else-if="Number(slipView.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"
>
<div class="font-medium">审核状态已驳回业务订单·处方审核</div>
<div v-if="slipView.business_prescription_audit_remark" class="mt-1">
驳回意见{{ slipView.business_prescription_audit_remark }}
</div>
<div v-else class="mt-1 text-red-600/80">暂无驳回意见说明</div>
</div>
<div
v-else-if="Number(slipView.audit_status) === 2"
class="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="slipView.audit_remark" class="mt-1">驳回意见{{ slipView.audit_remark }}</div>
<div v-else class="mt-1 text-red-600/80">暂无文字意见请联系审核人员</div>
</div>
<div class="cf-slip-info">
<div class="cf-slip-row">
<span class="cf-slip-item"><em>日期</em>{{ slipView.prescription_date || '—' }}</span>
<span class="cf-slip-item"><em>电话</em>{{ slipView.phone || '—' }}</span>
<span class="cf-slip-item"><em>门诊号</em>{{ slipView.visit_no || '—' }}</span>
</div>
<div class="cf-slip-row">
<span class="cf-slip-item"><em>姓名</em>{{ slipView.patient_name || '—' }}</span>
<span class="cf-slip-item"><em>性别</em>{{ slipGenderLabel(slipView.gender) }}</span>
<span class="cf-slip-item"><em>年龄</em>{{ slipAgeText(slipView.age) }}</span>
</div>
<div v-if="slipView.pulse || slipView.tongue" class="cf-slip-row">
<span v-if="slipView.pulse" class="cf-slip-item"><em>面象</em>{{ slipView.pulse }}</span>
<span v-if="slipView.tongue" class="cf-slip-item"><em>舌象</em>{{ slipView.tongue }}</span>
</div>
<div class="cf-slip-row cf-slip-row-full">
<span class="cf-slip-item"><em>临床诊断</em>{{ slipView.clinical_diagnosis || '—' }}</span>
</div>
</div>
<div class="cf-slip-rp">
<div class="cf-slip-rp-label">RP</div>
<div class="cf-slip-herbs">
<div v-for="(h, i) in slipHerbsList" :key="i" class="cf-slip-herb">
{{ h.name }} {{ h.dosage }}
</div>
</div>
</div>
<div class="cf-slip-footer">
<div class="cf-slip-footer-left">
<div>
服用{{ slipView.usage_days ?? slipView.dose_count }}, {{ slipView.prescription_type || '浓缩水丸' }}
{{ slipView.usage_instruction || '' }}
</div>
<div>
服用时间: {{ slipView.usage_time ? ' ' + slipView.usage_time : '' }}
<span class="cf-slip-inline-gap" />
服用方式: {{ slipView.usage_way ? ' ' + slipView.usage_way : '' }}
</div>
<div v-if="slipDietaryText" class="cf-slip-dietary">忌口{{ slipDietaryText }}</div>
<div v-if="slipView.usage_notes" class="cf-slip-notes">{{ slipView.usage_notes }}</div>
</div>
<div class="cf-slip-footer-right">
<span class="cf-slip-sign">
医师
<img
v-if="slipView.doctor_signature"
:src="slipView.doctor_signature"
alt="医师签名"
class="cf-slip-sign-img"
/>
<span v-else>{{ slipView.doctor_name || '—' }}</span>
</span>
</div>
</div>
<div class="cf-slip-hospital">
<div>{{ SLIP_COMPANY_LINE }}</div>
<div>{{ SLIP_ADDRESS_LINE }}</div>
</div>
<div
v-if="slipView.audit_by_name || slipView.audit_remark"
class="cf-slip-audit text-sm text-gray-600 mt-4 pt-3 border-t border-dashed border-gray-200"
>
<div v-if="slipView.audit_by_name">
审核人{{ slipView.audit_by_name }}
<span v-if="slipView.audit_time" class="ml-2 text-gray-400">
{{ formatAuditTime(slipView.audit_time) }}
</span>
</div>
<div v-if="slipView.audit_remark" class="mt-1">审核意见{{ slipView.audit_remark }}</div>
</div>
</div>
</div>
<el-form
v-else
ref="formRef"
:model="editForm"
:rules="rules"
label-width="120px"
>
<el-alert
v-if="editMode === 'edit'"
type="info"
:closable="false"
show-icon
class="mb-4"
title="保存修改后,该处方将重新进入「待审核」,需审核人员再次通过后方可生效。"
/>
<el-alert
v-if="editMode === 'edit' && Number(editForm.audit_status) === 2"
type="error"
:closable="false"
show-icon
class="mb-4"
>
<template #title>当前消费者处方为已驳回</template>
<div class="text-sm mt-1">
保存修改后将清除驳回并变为待审核」。{{
editForm.audit_remark ? `上次意见:${editForm.audit_remark}` : ''
}}
</div>
</el-alert>
<el-alert
v-if="
editMode === 'edit' &&
Number(editForm.business_prescription_audit_rejected) === 1 &&
Number(editForm.audit_status) === 1
"
type="warning"
:closable="false"
show-icon
class="mb-4"
>
<template #title>业务订单侧「处方审核」已驳回</template>
<div class="text-sm mt-1">
消费者处方仍为「已通过」;您修改并保存后,将重置关联业务订单的处方/支付单审核为「待审」,请在「处方业务订单」中继续处理。
</div>
<div
v-if="String(editForm.business_prescription_audit_remark || '').trim()"
class="text-sm mt-2 text-gray-800 font-medium"
>
驳回意见:{{ editForm.business_prescription_audit_remark }}
</div>
</el-alert>
<el-alert
v-if="editMode === 'edit' && editReviveHint"
type="warning"
:closable="false"
show-icon
class="mb-4"
:title="editReviveHint"
/>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="患者姓名" prop="patient_name">
<el-input v-model="editForm.patient_name" placeholder="请输入患者姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="门诊号" prop="visit_no">
<el-input v-model="editForm.visit_no" placeholder="自动生成或手动输入" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="editForm.gender">
<el-radio :label="1">男</el-radio>
<el-radio :label="0">女</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄" prop="age">
<el-input-number
v-model="editForm.age"
:min="0"
:max="150"
placeholder="请输入年龄"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="处方日期" prop="prescription_date">
<el-date-picker
v-model="editForm.prescription_date"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="面象" prop="tongue">
<el-input v-model="editForm.tongue" placeholder="请输入面象" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="舌象" prop="tongue_image">
<el-input v-model="editForm.tongue_image" placeholder="请输入舌象" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="脉象" prop="pulse">
<el-input v-model="editForm.pulse" placeholder="请输入脉象" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="脉象详情" prop="pulse_condition">
<el-input v-model="editForm.pulse_condition" placeholder="请输入脉象详情" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="临床诊断" prop="clinical_diagnosis">
<el-input
v-model="editForm.clinical_diagnosis"
type="textarea"
:rows="3"
placeholder="请输入临床诊断"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="药材配方" prop="herbs" required>
<div class="w-full">
<el-button type="primary" size="small" @click="addHerb">
添加药材
</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="请输入药材名称" />
</template>
</el-table-column>
<el-table-column label="剂量(克)" min-width="120">
<template #default="{ row, $index }">
<el-input-number
v-model="row.dosage"
:min="0"
:precision="1"
:step="0.5"
placeholder="剂量"
class="w-full"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ $index }">
<el-button
type="danger"
link
@click="removeHerb($index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="剂数" prop="dose_count">
<el-input-number
v-model="editForm.dose_count"
:min="1"
placeholder="剂数"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="剂量单位" prop="dose_unit">
<el-select v-model="editForm.dose_unit" placeholder="请选择" class="w-full">
<el-option label="剂" value="剂" />
<el-option label="丸" value="丸" />
<el-option label="袋" value="袋" />
<el-option label="盒" value="盒" />
<el-option label="瓶" value="瓶" />
<el-option label="膏" value="膏" />
<el-option label="贴" value="贴" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="处方类型" prop="prescription_type">
<el-select v-model="editForm.prescription_type" placeholder="请选择处方类型" class="w-full">
<el-option label="浓缩水丸" value="浓缩水丸" />
<el-option label="饮片" value="饮片" />
<el-option label="颗粒" value="颗粒" />
<el-option label="丸剂" value="丸剂" />
<el-option label="散剂" value="散剂" />
<el-option label="膏方" value="膏方" />
<el-option label="汤剂" value="汤剂" />
</el-select>
</el-form-item> </el-col>
<!-- 用量字段 -->
<el-col :span="8">
<el-form-item label="用量" prop="dosage_amount">
<!-- 浓缩水丸:1-10g 下拉选择 -->
<el-select
v-if="editForm.prescription_type === '浓缩水丸'"
v-model="editForm.dosage_amount"
placeholder="请选择用量"
class="w-full"
>
<el-option :label="1 + 'g'" :value="1" />
<el-option :label="2 + 'g'" :value="2" />
<el-option :label="3 + 'g'" :value="3" />
<el-option :label="4 + 'g'" :value="4" />
<el-option :label="5 + 'g'" :value="5" />
<el-option :label="6 + 'g'" :value="6" />
<el-option :label="7 + 'g'" :value="7" />
<el-option :label="8 + 'g'" :value="8" />
<el-option :label="9 + 'g'" :value="9" />
<el-option :label="10 + 'g'" :value="10" />
</el-select>
<!-- 饮片:50-250ml 下拉选择,步进50 -->
<el-select
v-else-if="editForm.prescription_type === '饮片'"
v-model="editForm.dosage_amount"
placeholder="请选择用量"
class="w-full"
>
<el-option label="50ml" :value="50" />
<el-option label="100ml" :value="100" />
<el-option label="120ml" :value="120" />
<el-option label="150ml" :value="150" />
<el-option label="180ml" :value="180" />
<el-option label="200ml" :value="200" />
<el-option label="250ml" :value="250" />
</el-select>
<!-- 其他类型:自由输入 -->
<el-input-number
v-else
v-model="editForm.dosage_amount"
:min="0"
:precision="2"
class="w-full"
/>
</el-form-item>
</el-col>
<!-- 饮片代煎选项 -->
<el-col v-if="editForm.prescription_type === '饮片'" :span="8">
<el-form-item label="是否代煎" prop="need_decoction">
<el-radio-group v-model="editForm.need_decoction">
<el-radio :label="true">代煎</el-radio>
<el-radio :label="false">不代煎</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 饮片每贴出包数 -->
<el-col v-if="editForm.prescription_type === '饮片'" :span="8">
<el-form-item label="每贴出包数" prop="bags_per_dose">
<el-select v-model="editForm.bags_per_dose" placeholder="请选择" class="w-full">
<el-option label="1包" :value="1" />
<el-option label="2包" :value="2" />
<el-option label="3包" :value="3" />
<el-option label="4包" :value="4" />
<el-option label="5包" :value="5" />
<el-option label="6包" :value="6" />
<el-option label="7包" :value="7" />
<el-option label="8包" :value="8" />
<el-option label="9包" :value="9" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="每天几次" prop="times_per_day">
<el-input-number v-model="editForm.times_per_day" :min="1" :max="6" class="!w-full" placeholder="每天服用次数" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="服用天数" prop="usage_days">
<el-input-number
v-model="editForm.usage_days"
:min="1"
:max="365"
placeholder="服用天数"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="用法" prop="usage_instruction">
<el-input
v-model="editForm.usage_instruction"
placeholder="例如:水煎服,一日二次"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="服用时间" prop="usage_time">
<el-select v-model="editForm.usage_time" placeholder="请选择服用时间" class="w-full">
<el-option label="饭前" value="饭前" />
<el-option label="饭后" value="饭后" />
<el-option label="饭中" value="饭中" />
<el-option label="空腹" value="空腹" />
<el-option label="睡前" value="睡前" />
<el-option label="晨起" value="晨起" />
<el-option label="随时" value="随时" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="服用方式" prop="usage_way">
<el-select v-model="editForm.usage_way" placeholder="请选择服用方式" class="w-full">
<el-option label="温水送服" value="温水送服" />
<el-option label="开水冲服" value="开水冲服" />
<el-option label="黄酒送服" value="黄酒送服" />
<el-option label="淡盐水送服" value="淡盐水送服" />
<el-option label="米汤送服" value="米汤送服" />
<el-option label="嚼服" value="嚼服" />
<el-option label="含化" value="含化" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="忌口" prop="dietary_taboo">
<el-select
v-model="editForm.dietary_taboo"
multiple
placeholder="请选择忌口内容(可多选)"
class="w-full"
>
<el-option label="辛辣食物" value="辛辣食物" />
<el-option label="生冷食物" value="生冷食物" />
<el-option label="油腻食物" value="油腻食物" />
<el-option label="海鲜" value="海鲜" />
<el-option label="牛羊肉" value="牛羊肉" />
<el-option label="鸡蛋" value="鸡蛋" />
<el-option label="豆制品" value="豆制品" />
<el-option label="酒类" value="酒类" />
<el-option label="浓茶" value="浓茶" />
<el-option label="咖啡" value="咖啡" />
<el-option label="烟草" value="烟草" />
<el-option label="萝卜" value="萝卜" />
</el-select>
</el-form-item>
<el-form-item label="其他说明" prop="usage_notes">
<el-input
v-model="editForm.usage_notes"
type="textarea"
:rows="2"
placeholder="其他服用注意事项"
maxlength="200"
show-word-limit
/>
</el-form-item>
<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-select>
<div class="text-xs text-gray-400 mt-1">
未共享时:所选角色下的账号也可在列表中查看此处方(与创建者并列)
</div>
</el-form-item>
</el-form>
<template #footer>
<template v-if="editMode === 'view'">
<el-button @click="showEdit = false">关闭</el-button>
</template>
<template v-else>
<el-button @click="showEdit = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">
确定
</el-button>
</template>
</template>
</el-drawer>
<!-- 从消费者处方创建业务订单(zyt_tcm_prescription_order,与支付单 zyt_order 分离) -->
<el-dialog
v-model="createOrderVisible"
title="创建业务订单"
width="940px"
top="4vh"
:close-on-click-modal="false"
@closed="resetCreateOrderForm"
class="create-order-dialog"
>
<div class="create-order-dialog__inner">
<!-- 关联处方:卡片式信息区,层次更清晰 -->
<div v-if="createOrderPrescription" class="create-order-rx-card">
<div class="create-order-rx-card__head">
<span class="create-order-rx-card__badge">关联处方</span>
<div class="create-order-rx-card__meta">
<span class="create-order-rx-card__id">#{{ createOrderPrescription.id }}</span>
<span v-if="createOrderPrescription.sn" class="create-order-rx-card__sn">{{ createOrderPrescription.sn }}</span>
<span class="create-order-rx-card__patient">{{ createOrderPrescription.patient_name || '—' }}</span>
</div>
</div>
<el-tooltip :content="createOrderHerbSummary" placement="bottom" :show-after="300">
<div class="create-order-rx-card__herbs">{{ createOrderHerbSummary }}</div>
</el-tooltip>
</div>
<el-steps
:active="createOrderStep"
finish-status="success"
align-center
class="create-order-steps"
>
<el-step title="患者与收货" description="诊单与物流地址" />
<el-step title="服务与支付单" description="套餐与关联收款" />
<el-step title="金额与确认" description="费用与备注" />
</el-steps>
<p class="create-order-step-lead">{{ createOrderStepLead }}</p>
<el-form
ref="createOrderFormRef"
:model="createOrderForm"
:rules="createOrderRules"
label-width="108px"
class="create-order-form"
>
<div v-show="createOrderStep === 0" class="create-order-step-panel">
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="诊单患者" prop="patient_id">
<el-select
v-model="createOrderForm.patient_id"
class="w-full"
placeholder="搜索姓名/手机/身份证"
filterable
remote
:remote-method="searchPatientsForOrder"
:loading="createOrderPatientLoading"
@change="onCreateOrderPatientChange"
>
<el-option
v-for="item in createOrderPatientList"
:key="item.id"
:label="`${item.patient_name} (${item.phone || '无手机'})`"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货人" prop="recipient_name">
<el-input v-model="createOrderForm.recipient_name" placeholder="姓名" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货手机" prop="recipient_phone">
<el-input v-model="createOrderForm.recipient_phone" placeholder="物流联系" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="省市区" prop="region">
<el-cascader
v-model="createOrderForm.region"
:options="regionOptions"
:props="{ value: 'name', label: 'name', children: 'children', emitPath: true, checkStrictly: false }"
placeholder="省 / 市 / 区"
clearable
filterable
class="w-full"
@change="handleRegionChange"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="详细地址" prop="shipping_address">
<el-input
v-model="createOrderForm.shipping_address"
type="textarea"
:rows="3"
placeholder="街道、小区、门牌号等详细地址"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div v-show="createOrderStep === 1" class="create-order-step-panel">
<el-row :gutter="20">
<el-col :xs="24" :sm="8">
<el-form-item label="复诊">
<el-switch v-model="createOrderForm.is_follow_up" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8">
<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"
:min="1"
:max="999"
:step="1"
controls-position="right"
class="flex-1 min-w-0"
/>
<span class="create-order-unit-suffix">天</span>
</div>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8">
<el-form-item label="上次医生">
<el-input v-model="createOrderForm.prev_staff" placeholder="可选" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="服务渠道">
<el-input v-model="createOrderForm.service_channel" placeholder="可选" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="服务套餐">
<el-select
v-model="createOrderForm.service_package"
multiple
collapse-tags
collapse-tags-tooltip
placeholder="请选择服务套餐"
clearable
class="w-full"
>
<el-option
v-for="item in servicePackageOptions"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="关联支付单" prop="pay_order_ids">
<el-select
v-model="createOrderForm.pay_order_ids"
class="w-full"
multiple
collapse-tags
collapse-tags-tooltip
:placeholder="
createOrderDepositMin > 0
? '请至少选择一笔本诊单支付单(已开启门槛)'
: '多选本诊单支付单(可选)'
"
:loading="createOrderPaidOrdersLoading"
>
<el-option
v-for="o in createOrderPaidOrders"
:key="o.id"
:label="`${o.order_no} · ¥${o.amount} · ${formatPoCategory(o.order_type)}`"
:value="o.id"
class="!h-auto py-1.5"
>
<div class="flex flex-col gap-0.5" style="line-height: normal;">
<div class="flex items-center justify-between">
<span class="font-medium text-[13px] text-gray-800">{{ o.order_no }}</span>
<span class="text-orange-500 font-medium ml-4">¥{{ o.amount }}</span>
</div>
<div class="flex items-center justify-between text-xs text-gray-400 mt-1">
<span class="truncate pr-2">{{ formatPoCategory(o.order_type) }}<template v-if="o.remark"> · {{ o.remark }}</template></span>
<span class="shrink-0">{{ String(o.create_time || '').substring(0, 16) }}</span>
</div>
</div>
</el-option>
</el-select>
<div class="create-order-pay-hint text-gray-500">
<template v-if="createOrderDepositMin > 0">
<span class="text-amber-600 font-medium">当前已开启关联支付门槛(¥{{ formatPoDepositMoney(createOrderDepositMin) }}),须至少选择一笔支付单。</span>
<span class="mx-1"> </span>
</template>
主管/本诊单医助可见全部支付单;否则仅本人收款单。已被占用的支付单不列出。
<el-tooltip
content="主管或本诊单医助可见该诊单全部支付单(待支付/已支付),否则仅本人创建的收款单。已被其他有效业务订单占用的支付单不会出现在此列表。"
placement="top"
>
<span class="text-primary cursor-help ml-0.5">详情</span>
</el-tooltip>
</div>
</el-form-item>
</el-col>
</el-row>
</div>
<div v-show="createOrderStep === 2" class="create-order-step-panel">
<!-- 横向概览,一屏内更省高度 -->
<div
class="create-order-deposit-bar"
:class="createOrderLinkedPaidTotal >= createOrderDepositMin ? 'is-ok' : 'is-warn'"
>
<div class="create-order-deposit-bar__cell">
<span class="create-order-deposit-bar__label">定金门槛</span>
<strong>¥{{ formatPoDepositMoney(createOrderDepositMin) }}</strong>
<span class="create-order-deposit-bar__sub">{{ createOrderDepositMin > 0 ? '须 ≥ 此值' : '未设置' }}</span>
</div>
<div class="create-order-deposit-bar__sep" />
<div class="create-order-deposit-bar__cell">
<span class="create-order-deposit-bar__label">已选收款</span>
<strong>¥{{ formatPoDepositMoney(createOrderLinkedPaidTotal) }}</strong>
<span class="create-order-deposit-bar__sub">
{{ createOrderLinkedPaidTotal >= createOrderDepositMin ? '已达标' : '未达标' }}
</span>
</div>
</div>
<el-row :gutter="20" class="create-order-step-panel__row-after-bar">
<el-col :span="12">
<el-form-item label="费用类别" prop="fee_type">
<el-select v-model="createOrderForm.fee_type" placeholder="请选择" class="w-full">
<el-option label="挂号费" :value="1" />
<el-option label="问诊费" :value="2" />
<el-option label="药品费用" :value="3" />
<el-option label="首付费用" :value="4" />
<el-option label="尾款费用" :value="5" />
<el-option label="其他费用" :value="6" />
<el-option label="全部费用" :value="7" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="总金额" prop="amount">
<el-input-number
v-model="createOrderForm.amount"
:min="createOrderAmountInputMin"
:step="0.01"
:precision="2"
class="w-full"
controls-position="right"
placeholder="元"
/>
</el-form-item>
</el-col>
<el-col v-perms="['finance.account_log/lists']" :span="12">
<el-form-item label="内部成本">
<el-input-number
v-model="createOrderForm.internal_cost"
:min="0"
:step="0.01"
:precision="2"
class="w-full"
controls-position="right"
placeholder="财务可见"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input
v-model="createOrderForm.remark_extra"
type="textarea"
:rows="3"
placeholder="选填,对内说明"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</div>
<template #footer>
<div class="create-order-footer">
<div class="create-order-footer__hint">
<span class="create-order-footer__step">第 {{ createOrderStep + 1 }} / 3 步</span>
<span v-if="createOrderStep === 2 && createOrderDepositMin > 0" class="create-order-footer__warn">
订单金额与关联支付单合计须 ≥ ¥{{ formatPoDepositMoney(createOrderDepositMin) }}
</span>
</div>
<div class="create-order-footer__actions">
<el-button size="large" @click="createOrderVisible = false">取消</el-button>
<el-button v-if="createOrderStep > 0" size="large" @click="goCreateOrderPrevStep">上一步</el-button>
<el-button
v-if="createOrderStep < 2"
type="primary"
size="large"
@click="goCreateOrderNextStep"
>
下一步
</el-button>
<el-button
v-if="createOrderStep === 2"
type="primary"
size="large"
:loading="createOrderSubmitLoading"
@click="submitCreateOrderFromPrescription"
>
创建业务订单
</el-button>
</div>
</div>
</template>
</el-dialog>
<el-dialog v-model="auditDialogVisible" title="处方审核" width="480px" destroy-on-close @closed="auditRemark = ''">
<p class="text-sm text-gray-600 mb-3">
通过:处方保持有效。驳回:将同时作废此处方(与诊间「驳回/作废处方」一致)。
</p>
<el-form label-width="80px">
<el-form-item label="审核意见">
<el-input
v-model="auditRemark"
type="textarea"
:rows="3"
maxlength="500"
show-word-limit
placeholder="通过可简要说明;驳回必填"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="auditLoading" @click="submitAudit('approve')">审核通过</el-button>
<el-button type="danger" :loading="auditLoading" @click="submitAudit('reject')">驳回处方</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="prescriptionList">
import {
prescriptionLists,
prescriptionAdd,
prescriptionEdit,
prescriptionDelete,
prescriptionAudit,
prescriptionDetail,
prescriptionOrderCreate,
prescriptionOrderPaidPayOrders,
getDoctors
} from '@/api/tcm'
import { searchPatients as searchPatientsAPI } from '@/api/order'
import { getDictData } from '@/api/app'
import { roleAll } from '@/api/perms/role'
import { usePaging } from '@/hooks/usePaging'
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 { computed, onMounted, reactive, ref, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'
/** 与 server/config/project.php prescription_audit_roles 保持一致 */
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3]
/** 处方笺抬头/页脚(与诊间打印一致) */
const SLIP_HOSPITAL_NAME = '成都双流甄养堂互联网医院'
const SLIP_COMPANY_LINE = '成都双流甄养堂互联网医院有限公司 联系方式:4001667339'
const SLIP_ADDRESS_LINE = '地址:四川省成都市双流区黄甲街道黄龙大道二段280号'
const userStore = useUserStore()
const router = useRouter()
function goBusinessOrderList() {
const hit = router.getRoutes().find((r) => r.meta?.perms === 'tcm.prescriptionOrder/lists')
if (hit?.path) {
router.push(hit.path)
} else {
feedback.msgWarning('未配置「处方业务订单」菜单,请在权限管理中执行菜单 SQL 或手动添加路由')
}
}
// 表单数据(与列表接口查询参数一致)
const formData = reactive({
patient_name: '',
/** 处方创建人(医师账号 ID),多选 */
creator_ids: [] as number[],
/** all | passed | not_passed | pending | rejectedall 表示不限,接口传参会转为空) */
audit_filter: 'all' as string,
start_time: '',
end_time: ''
})
const doctorOptions = ref<Array<{ id: number; name: string; account?: string }>>([])
/** 创建时间快捷:all | today | yesterday | before_yesterday | custom | '' */
const dateQuickTab = ref<string>('all')
const showEdit = ref(false)
const editMode = ref<'add' | 'edit' | 'view'>('add')
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
/** 查看模式:处方笺数据源(先列表行,再详情覆盖) */
const slipView = ref<Record<string, any> | null>(null)
const viewDetailLoading = ref(false)
const roleOptions = ref<Array<{ id: number; name: string }>>([])
const auditDialogVisible = ref(false)
const auditTargetId = ref(0)
const auditRemark = ref('')
const auditLoading = ref(false)
/** 从消费者处方创建订单 */
const createOrderVisible = ref(false)
/** 创建订单分步向导:0 患者与收货 / 1 服务与支付单 / 2 金额与确认 */
const createOrderStep = ref(0)
const createOrderStepLead = computed(() => {
const texts = [
'请核对诊单患者并填写完整收货信息,便于发货与物流联系。',
'按需填写服务相关信息,并选择本诊单下需要关联的支付单。',
'确认费用类别与订单金额;提交前请对照定金门槛是否已满足。'
]
return texts[createOrderStep.value] ?? ''
})
/** 第 1 步:患者与收货(分步校验) */
const CREATE_ORDER_STEP0_FIELDS = ['patient_id', 'recipient_name', 'recipient_phone', 'region', 'shipping_address'] as const
const createOrderPrescription = ref<any>(null)
const createOrderFormRef = ref<FormInstance>()
const createOrderSubmitLoading = ref(false)
const createOrderPatientLoading = ref(false)
const createOrderPatientList = ref<any[]>([])
const createOrderPaidOrders = ref<Array<{ id: number; order_no?: string; amount?: number | string; order_type?: number | string; remark?: string; create_time?: string }>>([])
const createOrderPaidOrdersLoading = ref(false)
const createOrderDepositMin = ref(0)
function formatPoDepositMoney(v: unknown) {
const n = Number(v)
if (Number.isNaN(n)) return '—'
return n.toFixed(2)
}
function formatPoCategory(t: unknown) {
const m: Record<number, string> = {
1: '挂号费',
2: '问诊费',
3: '药品费用',
4: '首付',
5: '尾款',
6: '其他'
}
return m[Number(t)] || '未知'
}
const createOrderForm = reactive({
patient_id: '' as number | string,
recipient_name: '',
recipient_phone: '',
region: [] as string[],
shipping_address: '',
is_follow_up: 0,
medication_days: undefined as number | undefined,
prev_staff: '',
service_channel: '',
service_package: [] as string[],
express_company: 'auto',
tracking_number: '',
fee_type: 3,
amount: 0,
internal_cost: undefined as number | undefined,
remark_extra: '',
pay_order_ids: [] as number[]
})
// 省市区数据(简化版,实际项目中应该从 API 获取或使用完整的省市区数据)
const regionOptions = ref([])
/** 服务套餐(字典 server_order,与业务订单编辑一致) */
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
// 转换省市区数据格式
const transformRegionData = (data: any[]) => {
return data.map((province: any) => ({
name: province.province,
code: province.code,
children: province.citys?.map((city: any) => ({
name: city.city,
code: city.code,
children: city.areas?.map((area: any) => ({
name: area.area,
code: area.code
})) || []
})) || []
}))
}
// 加载省市区数据
const loadRegionData = async () => {
try {
console.log('开始加载省市区数据...')
// 开发环境使用代理,生产环境使用环境变量中的基础URL
const apiUrl = import.meta.env.DEV
? '/api-proxy/area/ChinaCitys.json'
: `https://cos.zhenyangtang.com.cn/area/ChinaCitys.json`
const response = await fetch(apiUrl)
console.log('响应状态:', response.status)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
console.log('加载的数据条数:', data?.length)
// 转换数据格式
regionOptions.value = transformRegionData(data)
console.log('regionOptions 已设置,条数:', regionOptions.value.length)
} catch (error) {
console.error('加载省市区数据失败:', error)
// 如果加载失败,使用基本的省市数据作为后备
regionOptions.value = [
{
name: '四川省',
children: [
{
name: '成都市',
children: [
{ name: '锦江区' },
{ name: '青羊区' },
{ name: '金牛区' },
{ name: '武侯区' },
{ name: '成华区' },
{ name: '龙泉驿区' },
{ name: '青白江区' },
{ name: '新都区' },
{ name: '温江区' },
{ name: '双流区' }
]
},
{
name: '绵阳市',
children: [
{ name: '涪城区' },
{ name: '游仙区' },
{ name: '安州区' }
]
}
]
},
{
name: '北京市',
children: [
{
name: '北京市',
children: [
{ name: '东城区' },
{ name: '西城区' },
{ name: '朝阳区' },
{ name: '丰台区' },
{ name: '海淀区' }
]
}
]
},
{
name: '上海市',
children: [
{
name: '上海市',
children: [
{ name: '黄浦区' },
{ name: '徐汇区' },
{ name: '长宁区' },
{ name: '静安区' },
{ name: '普陀区' }
]
}
]
},
{
name: '广东省',
children: [
{
name: '广州市',
children: [
{ name: '荔湾区' },
{ name: '越秀区' },
{ name: '海珠区' },
{ name: '天河区' },
{ name: '白云区' }
]
},
{
name: '深圳市',
children: [
{ name: '罗湖区' },
{ name: '福田区' },
{ name: '南山区' },
{ name: '宝安区' }
]
}
]
}
]
console.log('使用后备数据,条数:', regionOptions.value.length)
}
}
const loadServicePackageOptions = async () => {
try {
const data = await getDictData({ type: 'server_order' })
servicePackageOptions.value = (data?.server_order || []).filter((item: { status?: number }) => item.status !== 0)
} catch {
servicePackageOptions.value = []
}
}
// 处理省市区选择变化
const handleRegionChange = (value: string[]) => {
console.log('选择的省市区:', value)
// value is an array like ['四川省', '成都市', '锦江区']
// We'll extract these in the submit function
}
const createOrderLinkedPaidTotal = computed(() => {
const ids = new Set(createOrderForm.pay_order_ids.map((x) => Number(x)))
let s = 0
for (const o of createOrderPaidOrders.value) {
if (ids.has(Number(o.id))) {
s += Number(o.amount) || 0
}
}
return Math.round(s * 100) / 100
})
const createOrderAmountInputMin = computed(() => {
const m = createOrderDepositMin.value
if (m > 0) {
return Math.round(m * 100) / 100
}
return 0
})
const createOrderRules = computed<FormRules>(() => {
const rules: FormRules = {
patient_id: [{ required: true, message: '请选择诊单患者', trigger: 'change' }],
recipient_name: [{ required: true, message: '请输入收货人', trigger: 'blur' }],
recipient_phone: [{ required: true, message: '请输入收货手机', trigger: 'blur' }],
region: [{ required: true, message: '请选择省/市/区', trigger: 'change', type: 'array' }],
shipping_address: [{ required: true, message: '请输入收货地址', trigger: 'blur' }],
fee_type: [{ required: true, message: '请选择费用类别', trigger: 'change' }],
amount: [
{ required: true, message: '请输入订单金额', trigger: 'blur' },
{
validator: (_rule, v, cb) => {
const min = createOrderDepositMin.value
const num = Number(v)
if (min > 0 && (!Number.isFinite(num) || num < min)) {
cb(new Error(`订单金额须大于等于定金门槛 ¥${formatPoDepositMoney(min)}`))
return
}
cb()
},
trigger: 'blur'
}
]
}
// 与 .env PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT 一致(接口 deposit_min_amount
// 勿在 el-form-item 上设 required,否则会多出一条默认英文「pay_order_ids is required」
if (createOrderDepositMin.value > 0) {
const depMsg = `已开启关联支付门槛(¥${formatPoDepositMoney(createOrderDepositMin.value)}),请至少选择一笔支付单`
rules.pay_order_ids = [
{
type: 'array',
required: true,
min: 1,
message: depMsg,
trigger: 'change'
}
]
}
return rules
})
const createOrderHerbSummary = computed(() => {
const row = createOrderPrescription.value
const herbs = normalizeSlipHerbs(row?.herbs)
if (!herbs.length) return '暂无药材明细'
const s = herbs.map((h) => `${h.name} ${h.dosage}g`).join('、')
return s.length > 120 ? `${s.slice(0, 120)}…` : s
})
function resetCreateOrderForm() {
createOrderStep.value = 0
createOrderForm.patient_id = ''
createOrderForm.recipient_name = ''
createOrderForm.recipient_phone = ''
createOrderForm.shipping_address = ''
createOrderForm.is_follow_up = 0
createOrderForm.medication_days = undefined
createOrderForm.prev_staff = ''
createOrderForm.service_channel = ''
createOrderForm.service_package = []
createOrderForm.express_company = 'auto'
createOrderForm.tracking_number = ''
createOrderForm.fee_type = 3
createOrderForm.amount = 0
createOrderForm.internal_cost = undefined
createOrderForm.remark_extra = ''
createOrderForm.pay_order_ids = []
createOrderPatientList.value = []
createOrderPaidOrders.value = []
createOrderDepositMin.value = 0
createOrderPrescription.value = null
createOrderFormRef.value?.clearValidate()
}
function syncCreateOrderAmountFromRules() {
const min = createOrderDepositMin.value
const paid = createOrderLinkedPaidTotal.value
const floor = min > 0 ? Math.round(min * 100) / 100 : 0
const need = min > 0 ? Math.max(paid, floor) : paid > 0 ? paid : 0
if (need > 0 && Number(createOrderForm.amount) < need) {
createOrderForm.amount = Math.round(need * 100) / 100
}
}
async function loadCreateOrderPaidOrders() {
const did = Number(createOrderForm.patient_id)
if (!did) {
createOrderPaidOrders.value = []
createOrderDepositMin.value = 0
return
}
try {
createOrderPaidOrdersLoading.value = true
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
createOrderDepositMin.value = dep
syncCreateOrderAmountFromRules()
} catch {
createOrderPaidOrders.value = []
createOrderDepositMin.value = 0
} finally {
createOrderPaidOrdersLoading.value = false
}
}
watch(
() => createOrderForm.patient_id,
() => {
createOrderForm.pay_order_ids = []
void loadCreateOrderPaidOrders()
}
)
watch(
() => [...createOrderForm.pay_order_ids],
() => {
syncCreateOrderAmountFromRules()
},
{ deep: true }
)
async function openCreateOrderFromPrescription(row: any) {
resetCreateOrderForm()
createOrderPrescription.value = row
const did = Number(row.diagnosis_id) || 0
if (did > 0) {
createOrderForm.patient_id = did
createOrderForm.recipient_name = row.patient_name || ''
createOrderForm.recipient_phone = String(row.phone || '').trim()
createOrderPatientList.value = [
{
id: did,
patient_name: row.patient_name || '',
phone: String(row.phone || '').trim()
}
]
}
const ud = Number(row.usage_days)
if (ud > 0) {
createOrderForm.medication_days = ud
}
createOrderVisible.value = true
void loadCreateOrderPaidOrders()
const rxId = row.id
if (rxId && !createOrderForm.recipient_phone) {
try {
const d = (await prescriptionDetail({ id: rxId })) as Record<string, unknown> | null
const phone = d && typeof d.phone === 'string' ? d.phone.trim() : ''
if (phone && createOrderPrescription.value?.id === rxId) {
createOrderForm.recipient_phone = phone
const first = createOrderPatientList.value[0]
if (first && Number(first.id) === did) {
first.phone = phone
}
}
} catch {
/* 列表无电话时尽力补全,失败忽略 */
}
}
}
async function searchPatientsForOrder(query: string) {
if (!query) {
createOrderPatientList.value = []
return
}
try {
createOrderPatientLoading.value = true
const res: any = await searchPatientsAPI({
keyword: query,
page_no: 1,
page_size: 10
})
createOrderPatientList.value = res?.lists || []
} catch {
createOrderPatientList.value = []
} finally {
createOrderPatientLoading.value = false
}
}
function onCreateOrderPatientChange(id: number | string) {
const p = createOrderPatientList.value.find((x) => Number(x.id) === Number(id))
if (p) {
createOrderForm.recipient_name = p.patient_name || ''
createOrderForm.recipient_phone = p.phone || ''
}
}
function goCreateOrderPrevStep() {
if (createOrderStep.value > 0) {
createOrderStep.value -= 1
}
}
async function goCreateOrderNextStep() {
const form = createOrderFormRef.value
if (!form) return
try {
if (createOrderStep.value === 0) {
await form.validateField([...CREATE_ORDER_STEP0_FIELDS])
} else if (createOrderStep.value === 1 && createOrderDepositMin.value > 0) {
await form.validateField(['pay_order_ids'])
}
if (createOrderStep.value < 2) {
createOrderStep.value += 1
}
} catch {
/* 校验未通过,留在当前步 */
}
}
async function submitCreateOrderFromPrescription() {
if (!createOrderFormRef.value || !createOrderPrescription.value) return
if (createOrderStep.value !== 2) return
try {
await createOrderFormRef.value.validate()
createOrderSubmitLoading.value = true
const diagnosisId = Number(createOrderForm.patient_id)
const payload: Record<string, unknown> = {
prescription_id: createOrderPrescription.value.id,
diagnosis_id: diagnosisId,
recipient_name: createOrderForm.recipient_name,
recipient_phone: createOrderForm.recipient_phone,
shipping_address: createOrderForm.shipping_address,
is_follow_up: createOrderForm.is_follow_up,
prev_staff: createOrderForm.prev_staff || '',
service_channel: createOrderForm.service_channel || '',
service_package:
Array.isArray(createOrderForm.service_package) && createOrderForm.service_package.length > 0
? createOrderForm.service_package.join(',')
: '',
express_company: createOrderForm.express_company || 'auto',
tracking_number: createOrderForm.tracking_number || '',
fee_type: createOrderForm.fee_type,
amount: createOrderForm.amount,
remark_extra: createOrderForm.remark_extra || ''
}
// 添加省市区字段
if (createOrderForm.region && createOrderForm.region.length >= 3) {
payload.shipping_province = createOrderForm.region[0]
payload.shipping_city = createOrderForm.region[1]
payload.shipping_district = createOrderForm.region[2]
}
if (createOrderForm.medication_days != null && createOrderForm.medication_days > 0) {
payload.medication_days = createOrderForm.medication_days
}
if (createOrderForm.internal_cost != null && createOrderForm.internal_cost > 0) {
payload.internal_cost = createOrderForm.internal_cost
}
if (createOrderForm.pay_order_ids.length) {
payload.pay_order_ids = [...createOrderForm.pay_order_ids]
}
const res: any = await prescriptionOrderCreate(payload)
const row = res?.data ?? res
const orderNo = row?.order_no
feedback.msgSuccess(orderNo ? `业务订单已创建,单号:${orderNo}` : '业务订单已创建')
createOrderVisible.value = false
getLists()
} catch (e) {
console.error(e)
} finally {
createOrderSubmitLoading.value = false
}
}
/** 正在编辑的这条是否原为已作废 / 已驳回(用于提示) */
const editingWasVoid = ref(false)
const editingWasRejected = ref(false)
const editReviveHint = computed(() => {
const v = editingWasVoid.value
const r = editingWasRejected.value
if (!v && !r) return ''
if (v && r) return '当前处方已作废且已驳回。保存后将取消作废、清除驳回,并重新进入待审核。'
if (v) return '当前为已作废处方。保存后将取消作废,并重新进入待审核。'
return '当前为已驳回处方。保存后将清除驳回,并重新进入待审核。'
})
const slipHerbsList = computed(() => {
const h = slipView.value?.herbs
return Array.isArray(h) ? h : []
})
const slipDietaryText = computed(() => {
const d = slipView.value?.dietary_taboo
if (Array.isArray(d)) return d.filter(Boolean).join('、')
if (typeof d === 'string' && d.trim()) return d.trim()
return ''
})
// 编辑表单
const editForm = reactive({
id: 0,
prescription_type: '浓缩水丸',
dosage_amount: 1 as number | undefined,
dosage_unit: 'g',
need_decoction: false,
bags_per_dose: 1,
patient_name: '',
gender: 1,
age: 0,
visit_no: '',
prescription_date: '',
tongue: '',
tongue_image: '',
pulse: '',
pulse_condition: '',
clinical_diagnosis: '',
herbs: [] as Array<{ name: string; dosage: number }>,
dose_count: 7,
dose_unit: '剂',
usage_days: 7,
usage_instruction: '水煎服,一日二次',
usage_time: '饭前',
usage_way: '温水送服',
dietary_taboo: [] as string[],
usage_notes: '',
doctor_name: '',
is_shared: 0,
visible_role_ids: [] as number[],
audit_status: 1,
audit_time: null as number | null,
audit_by_name: '',
audit_remark: '',
diagnosis_id: 0,
/** 列表带入:业务订单「处方审核」驳回(消费者处方本身可能仍为已通过) */
business_prescription_audit_rejected: 0,
business_prescription_audit_remark: '',
times_per_day: 2
})
// 标志:是否正在加载数据(防止watch函数覆盖已加载的值)
const isLoadingData = ref(false)
// 监听处方类型变化,自动调整用量单位和默认值
watch(() => editForm.prescription_type, (newType) => {
// 如果正在加载数据,不要自动重置值
if (isLoadingData.value) {
return
}
if (newType === '浓缩水丸') {
editForm.dosage_unit = 'g'
editForm.dosage_amount = 1
editForm.need_decoction = false
editForm.bags_per_dose = 1
} else if (newType === '饮片') {
editForm.dosage_unit = 'ml'
editForm.dosage_amount = 50
editForm.bags_per_dose = 1
// need_decoction 保持用户选择
} else {
editForm.dosage_unit = 'g'
editForm.dosage_amount = undefined
editForm.need_decoction = false
editForm.bags_per_dose = 1
}
})
// 表单验证规则
const rules: FormRules = {
patient_name: [
{ required: true, message: '请输入患者姓名', trigger: 'blur' }
],
gender: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
prescription_date: [
{ required: true, message: '请选择处方日期', trigger: 'change' }
],
clinical_diagnosis: [
{ required: true, message: '请输入临床诊断', trigger: 'blur' }
],
dose_count: [
{ required: true, message: '请输入剂数', trigger: 'blur' }
],
doctor_name: [
{ required: true, message: '请输入医师姓名', trigger: 'blur' }
]
}
async function fetchPrescriptionLists(params: Record<string, unknown>) {
const { audit_filter, creator_ids, ...rest } = params
const payload: Record<string, unknown> = {
...rest,
audit_filter: audit_filter === 'all' || audit_filter === '' ? '' : audit_filter
}
if (Array.isArray(creator_ids) && creator_ids.length > 0) {
payload.creator_ids = creator_ids
}
return prescriptionLists(payload)
}
const { pager, getLists, resetPage } = usePaging({
fetchFun: fetchPrescriptionLists,
params: formData
})
function pad2(n: number) {
return String(n).padStart(2, '0')
}
/** 本地日历日的 00:00:00 ~ 23:59:59 字符串(与 daterange-picker 一致) */
function dayRangeStrings(d: Date) {
const y = d.getFullYear()
const m = pad2(d.getMonth() + 1)
const day = pad2(d.getDate())
const base = `${y}-${m}-${day}`
return { start: `${base} 00:00:00`, end: `${base} 23:59:59` }
}
function onDateQuickTabChange(val: string | number | boolean | undefined) {
const tab = String(val ?? 'all')
if (tab === 'all') {
formData.start_time = ''
formData.end_time = ''
} else {
const now = new Date()
const t = new Date(now.getFullYear(), now.getMonth(), now.getDate())
if (tab === 'yesterday') {
t.setDate(t.getDate() - 1)
} else if (tab === 'before_yesterday') {
t.setDate(t.getDate() - 2)
}
const { start, end } = dayRangeStrings(t)
formData.start_time = start
formData.end_time = end
}
resetPage()
}
function onAuditFilterChange() {
resetPage()
}
let syncingDateTab = false
watch(
() => [formData.start_time, formData.end_time],
() => {
if (syncingDateTab) return
const s = formData.start_time
const e = formData.end_time
if (!s && !e) {
dateQuickTab.value = 'all'
return
}
if (!s || !e) {
dateQuickTab.value = ''
return
}
const now = new Date()
const matchOffset = (offset: number) => {
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - offset)
const { start, end } = dayRangeStrings(d)
return s === start && e === end
}
if (matchOffset(0)) dateQuickTab.value = 'today'
else if (matchOffset(1)) dateQuickTab.value = 'yesterday'
else if (matchOffset(2)) dateQuickTab.value = 'before_yesterday'
else dateQuickTab.value = ''
}
)
function formatListCreateTime(v: unknown) {
if (v === null || v === undefined || v === '') return '—'
if (typeof v === 'number' && v >= 1e9 && v < 1e11) {
return formatAuditTime(v)
}
const s = String(v).trim()
if (/^\d{10}$/.test(s)) {
return formatAuditTime(parseInt(s, 10))
}
return s
}
function resetParams() {
syncingDateTab = true
dateQuickTab.value = 'all'
formData.patient_name = ''
formData.creator_ids = []
formData.audit_filter = userCanAudit() ? 'pending' : 'all'
formData.start_time = ''
formData.end_time = ''
syncingDateTab = false
resetPage()
}
function userCanAudit() {
const u = userStore.userInfo
if (!u) return false
if (Number(u.root) === 1) return true
const raw = u.role_ids ?? u.role_id
const ids = Array.isArray(raw) ? raw.map((n: unknown) => Number(n)) : []
return PRESCRIPTION_AUDIT_ROLE_IDS.some((rid) => ids.includes(rid))
}
function auditStatusLabel(v: number | undefined) {
if (v === 0) return '待审核'
if (v === 2) return '已驳回'
return '已通过'
}
function auditStatusTagType(v: number | undefined) {
if (v === 0) return 'warning'
if (v === 2) return 'danger'
return 'success'
}
/** 业务订单处方审核驳回时,列表「审核状态」主标签显示为已驳回 */
function listAuditStatusLabel(row: { audit_status?: number; business_prescription_audit_rejected?: number }) {
if (Number(row.business_prescription_audit_rejected) === 1) return '已驳回'
return auditStatusLabel(row.audit_status)
}
function listAuditStatusTagType(row: { audit_status?: number; business_prescription_audit_rejected?: number }) {
if (Number(row.business_prescription_audit_rejected) === 1) return 'danger'
return auditStatusTagType(row.audit_status)
}
function listRejectReasonLines(row: {
audit_status?: number
audit_remark?: string
business_prescription_audit_rejected?: number
business_prescription_audit_remark?: string
}): string[] {
const out: string[] = []
if (Number(row.business_prescription_audit_rejected) === 1) {
const t = String(row.business_prescription_audit_remark || '').trim()
out.push(t ? `业务订单审核驳回意见:${t}` : '业务订单审核驳回意见:—')
}
if (Number(row.audit_status) === 2) {
const t = String(row.audit_remark || '').trim()
out.push(t ? `消费者处方审核驳回意见:${t}` : '消费者处方审核驳回意见:—')
}
return out
}
/** 已通过审核且未作废:仅可查看,不可编辑/删除(与预约列表「查看」一致) */
function isApprovedActivePrescription(row: { audit_status?: number; void_status?: number }) {
return Number(row.audit_status) === 1 && Number(row.void_status) !== 1
}
function formatAuditTime(ts: number | null | undefined) {
if (!ts) return ''
const d = new Date(ts * 1000)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
}
function formatVoidTime(ts: number | null | undefined) {
if (!ts) return '—'
return formatAuditTime(ts)
}
function slipGenderLabel(g: unknown) {
if (g === 1 || g === '1') return '男'
if (g === 0 || g === '0') return '女'
return '—'
}
function slipAgeText(age: unknown) {
if (age === null || age === undefined || age === '') return '—'
return `${age}岁`
}
function normalizeSlipHerbs(raw: unknown): Array<{ name: string; dosage: number }> {
if (!raw) return []
if (Array.isArray(raw)) {
return raw.map((x: any) => ({
name: String(x?.name ?? '').trim(),
dosage: Number(x?.dosage) || 0
}))
}
return []
}
function rowToSlipView(row: any) {
return {
...row,
herbs: normalizeSlipHerbs(row?.herbs)
}
}
function onEditDialogClosed() {
slipView.value = null
}
function formatVisibleRoleNames(ids: number[] | undefined) {
if (!ids || !ids.length) return '—'
const names = ids
.map((id) => roleOptions.value.find((r) => r.id === id)?.name)
.filter(Boolean)
return names.length ? names.join('、') : ids.join('、')
}
function normalizeVisibleRoleIds(row: any): number[] {
const v = row?.visible_role_ids
if (Array.isArray(v)) return v.map((n) => Number(n)).filter((n) => !Number.isNaN(n))
if (typeof v === 'string' && v)
return v
.split(',')
.map((s) => parseInt(s.trim(), 10))
.filter((n) => !Number.isNaN(n))
return []
}
function openAuditDialog(row: any) {
auditTargetId.value = row.id
auditRemark.value = ''
auditDialogVisible.value = true
}
async function submitAudit(action: 'approve' | 'reject') {
if (action === 'reject' && !auditRemark.value.trim()) {
feedback.msgError('驳回时请填写审核意见')
return
}
auditLoading.value = true
try {
const auditData = (await prescriptionAudit({
id: auditTargetId.value,
action,
remark: auditRemark.value.trim()
})) as { wecom_notify_ok?: boolean; wecom_notify_hint?: string } | void
feedback.msgSuccess(action === 'reject' ? '已驳回并作废处方' : '审核通过')
if (auditData && auditData.wecom_notify_ok === false && auditData.wecom_notify_hint) {
feedback.msgWarning(auditData.wecom_notify_hint)
}
auditDialogVisible.value = false
getLists()
} catch {
/* 拦截器已提示 */
} finally {
auditLoading.value = false
}
}
async function loadRoleOptions() {
try {
const list = await roleAll({})
roleOptions.value = Array.isArray(list) ? list : []
} catch {
roleOptions.value = []
}
}
// 添加药材
const addHerb = () => {
editForm.herbs.push({
name: '',
dosage: 0
})
}
// 删除药材
const removeHerb = (index: number) => {
editForm.herbs.splice(index, 1)
}
// 重置表单
const resetForm = () => {
editForm.id = 0
editForm.prescription_type = '浓缩水丸'
editForm.patient_name = ''
editForm.gender = 1
editForm.age = 0
editForm.visit_no = ''
editForm.prescription_date = new Date().toISOString().split('T')[0]
editForm.tongue = ''
editForm.tongue_image = ''
editForm.pulse = ''
editForm.pulse_condition = ''
editForm.clinical_diagnosis = ''
editForm.herbs = []
editForm.dose_count = 7
editForm.dose_unit = '剂'
editForm.usage_days = 7
editForm.usage_instruction = '水煎服,一日二次'
editForm.usage_time = '饭前'
editForm.usage_way = '温水送服'
editForm.dietary_taboo = []
editForm.usage_notes = ''
editForm.doctor_name = ''
editForm.is_shared = 0
editForm.visible_role_ids = []
editForm.audit_status = 1
editForm.audit_time = null
editForm.audit_by_name = ''
editForm.audit_remark = ''
editForm.diagnosis_id = 0
editForm.business_prescription_audit_rejected = 0
editForm.business_prescription_audit_remark = ''
}
// 新增处方
const handleAdd = () => {
resetForm()
editingWasVoid.value = false
editingWasRejected.value = false
editMode.value = 'add'
// 新建时允许watch自动设置默认值
isLoadingData.value = false
showEdit.value = true
nextTick(() => {
formRef.value?.clearValidate()
})
}
// 查看处方(处方笺样式,详情接口补全电话、签名等)
const handleView = async (row: any) => {
editingWasVoid.value = false
editingWasRejected.value = false
editMode.value = 'view'
slipView.value = rowToSlipView(row)
showEdit.value = true
viewDetailLoading.value = true
try {
const d = await prescriptionDetail({ id: row.id })
if (d && typeof d === 'object') {
slipView.value = {
...d,
herbs: normalizeSlipHerbs((d as any).herbs)
}
}
} catch (e: any) {
feedback.msgError(e?.msg || '加载处方详情失败')
} finally {
viewDetailLoading.value = false
}
}
// 编辑处方
const handleEdit = (row: any) => {
editingWasVoid.value = Number(row.void_status) === 1
editingWasRejected.value = Number(row.audit_status) === 2
editMode.value = 'edit'
// 开始加载数据,暂时禁用watch自动重置
isLoadingData.value = true
// 深拷贝数据到表单
editForm.id = row.id
editForm.prescription_type = row.prescription_type || '浓缩水丸'
// 确保 dosage_amount 是数字类型,以匹配下拉框的 :value
if (row.dosage_amount !== null && row.dosage_amount !== undefined && row.dosage_amount !== '') {
editForm.dosage_amount = Number(row.dosage_amount)
} else {
editForm.dosage_amount = undefined
}
// 如果数据库中没有 dosage_unit,根据 prescription_type 设置默认值
if (row.dosage_unit) {
editForm.dosage_unit = row.dosage_unit
} else {
if (editForm.prescription_type === '饮片') {
editForm.dosage_unit = 'ml'
} else {
editForm.dosage_unit = 'g'
}
}
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.business_prescription_audit_rejected = Number(row.business_prescription_audit_rejected) === 1 ? 1 : 0
editForm.business_prescription_audit_remark = String(row.business_prescription_audit_remark || '')
// 数据加载完成,重新启用watch
// 使用 setTimeout 确保所有数据都已经渲染完成
setTimeout(() => {
isLoadingData.value = false
}, 0)
showEdit.value = true
nextTick(() => {
formRef.value?.clearValidate()
})
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
// 验证药材
if (!editForm.herbs || editForm.herbs.length === 0) {
feedback.msgError('请至少添加一味药材')
return
}
for (let i = 0; i < editForm.herbs.length; i++) {
const herb = editForm.herbs[i]
if (!herb.name || !herb.name.trim()) {
feedback.msgError(`第${i + 1}味药材名称不能为空`)
return
}
if (!herb.dosage || herb.dosage <= 0) {
feedback.msgError(`第${i + 1}味药材剂量必须大于0`)
return
}
}
submitLoading.value = true
try {
const params: Record<string, unknown> = { ...editForm }
if (editMode.value === 'add') {
params.audit_status = 0
await prescriptionAdd(params)
feedback.msgSuccess('添加成功,已提交审核')
} else {
await prescriptionEdit(params)
feedback.msgSuccess('保存成功,处方已重新进入待审核')
}
showEdit.value = false
getLists()
} catch (error) {
console.error(error)
} finally {
submitLoading.value = false
}
}
// 删除处方
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除该处方吗?')
await prescriptionDelete({ id })
feedback.msgSuccess('删除成功')
getLists()
}
onMounted(async () => {
await loadRoleOptions()
await loadRegionData()
await loadServicePackageOptions()
try {
const list = await getDoctors()
doctorOptions.value = Array.isArray(list) ? list : []
} catch {
doctorOptions.value = []
}
try {
if (!userStore.userInfo?.id) {
await userStore.getUserInfo()
}
} catch {
/* 已登录页仍可无用户信息,保持默认筛选 */
}
if (userCanAudit()) {
formData.audit_filter = 'pending'
}
getLists()
})
</script>
<style scoped lang="scss">
.prescription-list {
padding: 12px 16px 20px;
}
/* 紧凑检索条少占纵向空间表格才是主体 */
.filter-toolbar {
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.pl-toolbar {
display: flex;
flex-direction: column;
gap: 8px;
}
.pl-toolbar__line {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
}
.pl-toolbar__line--second {
padding-top: 2px;
border-top: 1px dashed var(--el-border-color-lighter);
justify-content: flex-start;
}
.pl-toolbar__k {
flex-shrink: 0;
font-size: 12px;
font-weight: 500;
color: var(--el-text-color-secondary);
line-height: 1;
}
.pl-toolbar__sep {
width: 1px;
height: 18px;
background: var(--el-border-color);
opacity: 0.7;
margin: 0 2px;
}
.pl-toolbar__rg {
flex-wrap: wrap;
}
.pl-toolbar__rg :deep(.el-radio-button__inner) {
padding: 5px 10px;
font-size: 12px;
}
/* 审核人员最常用待审核 */
.pl-toolbar__rg--audit :deep(.pl-audit-pending-btn .el-radio-button__inner) {
font-weight: 600;
padding: 6px 14px;
font-size: 13px;
}
.pl-toolbar__rg--audit :deep(.pl-audit-pending-btn:not(.is-active) .el-radio-button__inner) {
color: var(--el-color-primary);
border-color: var(--el-color-primary-light-5);
background: var(--el-color-primary-light-9);
}
.pl-toolbar__rg--audit :deep(.pl-audit-pending-btn.is-active .el-radio-button__inner) {
background: var(--el-color-primary) !important;
border-color: var(--el-color-primary) !important;
color: #fff !important;
box-shadow: none;
}
.pl-toolbar__range {
flex: 1 1 240px;
min-width: 220px;
max-width: 380px;
}
.pl-toolbar__range :deep(.el-date-editor) {
width: 100%;
}
.pl-toolbar__range :deep(.el-date-editor.el-input__wrapper) {
min-height: 30px;
padding: 2px 8px;
}
.pl-toolbar__form {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 0 4px;
flex: 1;
min-width: 0;
}
.pl-toolbar__form :deep(.el-form-item) {
margin-bottom: 0;
margin-right: 12px;
}
.pl-toolbar__form :deep(.el-form-item__label) {
font-size: 12px;
padding-right: 6px;
}
.pl-toolbar__input {
width: 132px;
}
.pl-toolbar__select {
width: 108px;
}
.pl-toolbar__select--doctors {
width: min(320px, 100%);
min-width: 200px;
}
.pl-toolbar__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-left: auto;
}
.table-card {
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
}
.pl-table-head {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.pl-table-head__title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
letter-spacing: 0.02em;
}
/* 查看处方互联网医院处方笺对齐诊间 tcm-prescription 打印版式 */
.cf-prescription-drawer :deep(.el-drawer__body) {
padding-top: 8px;
overflow-y: auto;
}
.cf-slip-wrap {
min-height: 160px;
}
.cf-slip-print {
width: 100%;
max-width: 148mm;
min-height: 210mm;
margin: 0 auto;
padding: 20px 24px;
background: #fff;
font-size: 14px;
color: #333;
box-sizing: border-box;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
}
.cf-slip-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
position: relative;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #ddd;
}
.cf-slip-title {
flex: 1;
text-align: center;
font-size: 20px;
font-weight: 600;
margin: 0;
padding: 0 120px 0 0;
line-height: 1.35;
}
.cf-slip-type {
position: absolute;
right: 0;
top: 0;
text-align: right;
font-size: 14px;
line-height: 1.4;
}
.cf-slip-void {
font-size: 13px;
color: var(--el-color-danger);
margin-bottom: 12px;
padding: 8px 12px;
background: #fef0f0;
border-radius: 4px;
}
.cf-slip-info {
margin-bottom: 16px;
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.cf-slip-row {
display: flex;
flex-wrap: wrap;
margin-bottom: 10px;
gap: 8px 0;
&:last-child {
margin-bottom: 0;
}
}
.cf-slip-row-full .cf-slip-item {
flex: 1 1 100%;
min-width: 100%;
}
.cf-slip-item {
flex: 1;
min-width: 0;
display: inline-flex;
align-items: baseline;
gap: 6px;
font-size: 14px;
em {
font-style: normal;
color: #909399;
flex-shrink: 0;
min-width: 56px;
}
}
.cf-slip-rp {
margin: 16px 0;
padding: 12px;
border: 1px solid #eee;
}
.cf-slip-rp-label {
font-weight: 600;
margin-bottom: 8px;
}
.cf-slip-herbs {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 16px;
}
.cf-slip-herb {
white-space: nowrap;
font-size: 14px;
}
.cf-slip-footer {
margin-top: 16px;
padding-top: 12px;
border-top: 1px dashed #ddd;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 24px;
}
.cf-slip-footer-left {
flex: 1;
min-width: 0;
line-height: 1.6;
}
.cf-slip-footer-right {
flex-shrink: 0;
}
.cf-slip-inline-gap {
display: inline-block;
width: 20px;
}
.cf-slip-dietary,
.cf-slip-notes {
margin-top: 6px;
font-size: 13px;
}
.cf-slip-sign {
display: inline-flex;
align-items: center;
gap: 8px;
}
.cf-slip-sign-img {
height: 32px;
max-width: 120px;
object-fit: contain;
vertical-align: middle;
}
.cf-slip-hospital {
border-top: 1px dashed #ddd;
padding-top: 12px;
margin-top: 16px;
font-size: 13px;
color: #606266;
line-height: 1.65;
}
/* 创建业务订单弹窗宽松留白默认字号卡片化信息区 */
.create-order-dialog {
:deep(.el-dialog__header) {
padding: 20px 24px 12px;
margin-right: 0;
}
:deep(.el-dialog__title) {
font-size: 18px;
font-weight: 600;
letter-spacing: 0.02em;
}
:deep(.el-dialog__body) {
padding: 8px 24px 20px;
max-height: calc(100vh - 200px);
overflow: hidden;
}
:deep(.el-dialog__footer) {
padding: 16px 24px 20px;
border-top: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
}
.create-order-dialog__inner {
display: flex;
flex-direction: column;
gap: 0;
}
.create-order-rx-card {
border-radius: 10px;
border: 1px solid var(--el-color-primary-light-5);
background: linear-gradient(135deg, var(--el-color-primary-light-9) 0%, var(--el-fill-color-blank) 100%);
padding: 16px 18px;
margin-bottom: 20px;
box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
}
.create-order-rx-card__head {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px 14px;
margin-bottom: 10px;
}
.create-order-rx-card__badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
color: #fff;
background: var(--el-color-primary);
letter-spacing: 0.04em;
}
.create-order-rx-card__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 12px;
font-size: 14px;
color: var(--el-text-color-regular);
}
.create-order-rx-card__id {
font-weight: 600;
color: var(--el-text-color-primary);
}
.create-order-rx-card__sn {
padding: 2px 8px;
border-radius: 4px;
background: var(--el-fill-color);
font-size: 13px;
color: var(--el-text-color-secondary);
}
.create-order-rx-card__patient {
font-weight: 500;
color: var(--el-text-color-primary);
}
.create-order-rx-card__herbs {
font-size: 13px;
line-height: 1.55;
color: var(--el-text-color-secondary);
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
cursor: default;
padding-top: 2px;
}
.create-order-steps {
margin-bottom: 8px;
padding: 8px 12px 4px;
:deep(.el-step__title) {
font-size: 14px;
font-weight: 500;
line-height: 1.4;
}
:deep(.el-step__description) {
font-size: 12px;
margin-top: 4px;
line-height: 1.35;
padding-right: 6px;
}
:deep(.el-step__head) {
padding-right: 8px;
}
:deep(.el-step.is-horizontal .el-step__line) {
top: 14px;
}
}
.create-order-step-lead {
margin: 0 0 20px;
padding: 12px 16px;
font-size: 13px;
line-height: 1.6;
color: var(--el-text-color-secondary);
background: var(--el-fill-color-light);
border-radius: 8px;
border-left: 3px solid var(--el-color-primary-light-5);
}
.create-order-step-panel {
max-height: min(56vh, 480px);
overflow-y: auto;
overflow-x: hidden;
padding: 4px 8px 12px 4px;
margin-right: -4px;
}
.create-order-step-panel__row-after-bar {
margin-top: 20px;
}
.create-order-unit-suffix {
flex-shrink: 0;
font-size: 13px;
color: var(--el-text-color-secondary);
padding-left: 4px;
}
.create-order-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 16px;
width: 100%;
}
.create-order-footer__hint {
flex: 1;
min-width: 200px;
display: flex;
flex-direction: column;
gap: 6px;
font-size: 13px;
line-height: 1.5;
color: var(--el-text-color-secondary);
}
.create-order-footer__step {
color: var(--el-text-color-placeholder);
}
.create-order-footer__warn {
color: var(--el-color-warning);
font-weight: 500;
}
.create-order-footer__actions {
flex-shrink: 0;
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: flex-end;
}
.create-order-deposit-bar {
display: flex;
align-items: stretch;
border-radius: 10px;
border: 1px solid var(--el-border-color-lighter);
overflow: hidden;
font-size: 13px;
box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
}
.create-order-deposit-bar__cell {
flex: 1;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 6px;
background: var(--el-fill-color-light);
strong {
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
letter-spacing: 0.02em;
}
}
.create-order-deposit-bar__label {
font-size: 13px;
color: var(--el-text-color-secondary);
}
.create-order-deposit-bar__sub {
font-size: 12px;
color: var(--el-text-color-placeholder);
}
.create-order-deposit-bar__sep {
width: 1px;
background: var(--el-border-color-lighter);
flex-shrink: 0;
}
.create-order-deposit-bar.is-ok .create-order-deposit-bar__cell:last-child strong {
color: var(--el-color-success);
}
.create-order-deposit-bar.is-warn .create-order-deposit-bar__cell:last-child strong {
color: var(--el-color-danger);
}
.create-order-pay-hint {
font-size: 12px;
line-height: 1.55;
margin-top: 8px;
color: var(--el-text-color-secondary);
}
.create-order-form {
:deep(.el-form-item) {
margin-bottom: 20px;
}
:deep(.el-form-item__label) {
font-size: 14px;
padding-right: 12px;
}
:deep(.el-input__inner),
:deep(.el-textarea__inner) {
font-size: 14px;
}
}
.create-order-form-item--inline-num {
:deep(.el-form-item__content) {
line-height: normal;
}
}
</style>