This commit is contained in:
Your Name
2026-07-02 14:35:20 +08:00
parent b6cdd20c56
commit efd058bce5
312 changed files with 1376 additions and 400 deletions
+5
View File
@@ -446,6 +446,11 @@ export function prescriptionOrderRevokeRxAudit(params: { id: number }) {
return request.post({ url: '/tcm.prescriptionOrder/revokeRxAudit', params })
}
/** 批量将处方业务订单(创建人/医助归属)改派给其他医助 */
export function prescriptionOrderBatchAssignAssistant(params: { order_ids: number[]; assistant_id: number }) {
return request.post({ url: '/tcm.prescriptionOrder/batchAssignAssistant', params })
}
/** 撤回支付单审核 */
export function prescriptionOrderRevokePayAudit(params: { id: number }) {
return request.post({ url: '/tcm.prescriptionOrder/revokePayAudit', params })
@@ -39,7 +39,7 @@
<el-radio-button label="rejected">驳回</el-radio-button>
</el-radio-group>
<span class="pl-toolbar__sep" aria-hidden="true" />
<span class="pl-toolbar__k" title="与列表「来源」列一致:系统代开 / 手工">来源</span>
<span class="pl-toolbar__k" title="与列表「来源」列一致:空白处方 / 手工">来源</span>
<el-radio-group
v-model="formData.source_filter"
size="small"
@@ -48,7 +48,7 @@
>
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="manual">手工</el-radio-button>
<el-radio-button label="system">系统代开</el-radio-button>
<el-radio-button label="system">空白处方</el-radio-button>
</el-radio-group>
</div>
<div class="pl-toolbar__line pl-toolbar__line--second">
@@ -146,7 +146,7 @@
size="small"
effect="plain"
>
系统代开
空白处方
</el-tag>
<span v-else class="text-gray-400 text-sm">手工</span>
</template>
@@ -290,7 +290,7 @@
type="warning"
size="small"
effect="plain"
>系统代开</el-tag>
>空白处方</el-tag>
<el-tag v-if="slipView && Number(slipView.void_status) === 1" type="danger" size="small">已作废</el-tag>
<el-tag
v-else-if="slipView && Number(slipView.business_prescription_audit_rejected) === 1"
@@ -1857,7 +1857,7 @@ const formData = reactive({
creator_ids: [] as number[],
/** all | passed | not_passed | pending | rejectedall 表示不限,接口传参会转为空) */
audit_filter: 'all' as string,
/** all | manual | system — 与 is_system_auto:手工(0) / 系统代开(1) */
/** all | manual | system — 与 is_system_auto:手工(0) / 空白处方(1) */
source_filter: 'all' as string,
start_time: '',
end_time: ''
@@ -283,6 +283,12 @@
<el-option label="未指派" value="0" />
</el-select>
</el-form-item>
<el-form-item class="w-[140px]" label="辅方">
<el-select v-model="queryParams.has_aux_formula" clearable placeholder="全部" class="!w-full">
<el-option label="含辅方" value="1" />
<el-option label="不含辅方" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="resetPage">查询</el-button>
<el-button @click="handleReset">重置</el-button>
@@ -303,6 +309,17 @@
<div class="po-table-toolbar__title-wrap">
<span class="po-table-title text-gray-800">订单列表</span>
<span class="po-table-hint text-gray-400"> {{ pager.count }} </span>
<el-button
v-if="canBatchReassign"
type="primary"
plain
size="small"
class="ml-3"
:disabled="selectedOrders.length === 0"
@click="openReassignDialog"
>
批量改派医助{{ selectedOrders.length ? `${selectedOrders.length}` : '' }}
</el-button>
</div>
<div class="po-focus-board">
<div
@@ -339,7 +356,15 @@
</div>
</div>
</div>
<el-table :data="pager.lists" size="default" stripe class="po-data-table" :row-class-name="orderRowClassName">
<el-table
:data="pager.lists"
size="default"
stripe
class="po-data-table"
:row-class-name="orderRowClassName"
@selection-change="onOrderSelectionChange"
>
<el-table-column v-if="canBatchReassign" type="selection" width="44" :selectable="() => true" />
<el-table-column label="订单号" min-width="178" show-overflow-tooltip>
<template #default="{ row }">
<div class="po-order-no-cell">
@@ -406,16 +431,41 @@
</div>
<div v-if="canViewFinanceFields() && row.internal_cost != null && row.internal_cost !== ''" class="flex justify-between">
<span class="text-gray-500">内部成本</span>
<span class="font-medium text-orange-600">¥{{ formatMoney(row.internal_cost) }}</span>
<span
class="font-medium cursor-pointer select-none"
:class="internalCostVisible ? 'text-orange-600' : 'text-gray-400'"
title="点击显示/隐藏"
@click.stop="internalCostVisible = !internalCostVisible"
>
{{ internalCostVisible ? `¥${formatMoney(row.internal_cost)}` : '****' }}
</span>
</div>
</div>
</el-popover>
</div>
</template>
</el-table-column>
<el-table-column v-if="canViewFinanceFields()" label="内部成本" width="92">
<el-table-column v-if="canViewFinanceFields()" width="100">
<template #header>
<span
class="cursor-pointer select-none hover:text-primary"
title="点击显示/隐藏内部成本"
@click.stop="internalCostVisible = !internalCostVisible"
>
内部成本
</span>
</template>
<template #default="{ row }">
{{ row.internal_cost != null && row.internal_cost !== '' ? `¥${row.internal_cost}` : '—' }}
<span
v-if="row.internal_cost != null && row.internal_cost !== ''"
class="cursor-pointer select-none"
:class="internalCostVisible ? 'text-orange-600 font-medium' : 'text-gray-400'"
title="点击显示/隐藏"
@click.stop="internalCostVisible = !internalCostVisible"
>
{{ internalCostVisible ? `¥${row.internal_cost}` : '****' }}
</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="处方审核" width="110">
@@ -2114,6 +2164,34 @@
</div>
</el-drawer>
<!-- 批量改派医助 -->
<el-dialog v-model="reassignVisible" title="批量改派医助" width="440px" append-to-body>
<div class="text-sm text-gray-600 mb-3">
已选 <strong class="text-primary">{{ selectedOrders.length }}</strong> 将订单创建人(医助归属)改派为
</div>
<el-select
v-model="reassignAssistantId"
clearable
filterable
placeholder="选择目标医助"
class="!w-full"
>
<el-option
v-for="a in assistantOptions"
:key="a.id"
:label="a.name"
:value="a.id"
/>
</el-select>
<p class="text-xs text-gray-400 mt-2 leading-relaxed">
仅变更订单的业务归属医助筛选 / 业绩口径并写入操作日志不影响处方支付单及审核状态已是该医助的订单会自动跳过
</p>
<template #footer>
<el-button @click="reassignVisible = false">取消</el-button>
<el-button type="primary" :loading="reassignSubmitting" @click="submitReassign">确定改派</el-button>
</template>
</el-dialog>
<!-- consumer/prescription/index诊单 edit 同一套界面只读诊单与全部分页签 -->
<TcmDiagnosisEditView ref="diagnosisViewRef" />
</div>
@@ -2169,6 +2247,7 @@ import {
prescriptionOrderRefund,
prescriptionOrderRevokeRxAudit,
prescriptionOrderRevokePayAudit,
prescriptionOrderBatchAssignAssistant,
prescriptionOrderPatchPrescriptionPatient,
prescriptionOrderLinkPayOrder,
prescriptionOrderSubmitGancaoRecipel,
@@ -2268,6 +2347,70 @@ const canViewFinanceFields = () => {
return FINANCE_ROLE_IDS.some((rid) => ids.includes(rid))
}
/** 内部成本默认隐藏,点击表头或单元格切换显示 */
const internalCostVisible = ref(false)
/** 是否可批量改派医助(与后端 canSeeAllPrescriptionOrders 一致:超管 / 全量编辑角色) */
const canBatchReassign = computed(() => {
const u = userStore.userInfo
if (!u) return false
if (Number(u.root) === 1) return true
const ids = Array.isArray(u.role_ids) ? u.role_ids.map((n: unknown) => Number(n)) : []
return ORDER_EDIT_ALL_ROLE_IDS.some((rid) => ids.includes(rid))
})
/** 批量改派:表格多选 */
const selectedOrders = ref<Array<{ id: number; order_no?: string; creator_id?: number }>>([])
function onOrderSelectionChange(rows: Array<{ id: number; order_no?: string; creator_id?: number }>) {
selectedOrders.value = Array.isArray(rows) ? rows : []
}
/** 批量改派弹窗 */
const reassignVisible = ref(false)
const reassignAssistantId = ref<number | ''>('')
const reassignSubmitting = ref(false)
function openReassignDialog() {
if (selectedOrders.value.length === 0) {
feedback.msgWarning('请先勾选要改派的订单')
return
}
reassignAssistantId.value = ''
reassignVisible.value = true
}
async function submitReassign() {
const assistantId = Number(reassignAssistantId.value)
if (!assistantId) {
feedback.msgWarning('请选择目标医助')
return
}
const orderIds = selectedOrders.value.map((r) => Number(r.id)).filter((id) => id > 0)
if (orderIds.length === 0) {
feedback.msgWarning('请先勾选要改派的订单')
return
}
reassignSubmitting.value = true
try {
const res: any = await prescriptionOrderBatchAssignAssistant({
order_ids: orderIds,
assistant_id: assistantId
})
const ok = Number(res?.success ?? 0)
const errs: string[] = Array.isArray(res?.errors) ? res.errors : []
let msg = `已改派 ${ok}`
if (errs.length) msg += `${errs.join('')}`
feedback.msgSuccess(msg)
reassignVisible.value = false
selectedOrders.value = []
getLists()
} catch {
// request 拦截器已弹错误提示
} finally {
reassignSubmitting.value = false
}
}
// 省市区数据
const regionOptions = ref([])
@@ -2471,6 +2614,8 @@ const queryParams = reactive({
supply_mode: '' as '' | 'gancao' | 'self',
/** 服务渠道:'' 不限;'0' 未指派(库内 '' 或 '0' */
service_channel: '' as '' | '0',
/** 是否含辅方:'' 不限;'1' 含辅方;'0' 不含辅方 */
has_aux_formula: '' as '' | '0' | '1',
/** 列表排除履约已取消(4):重点看板「待处方审核」等用 */
exclude_fulfillment_cancelled: 0 as number,
/** 下单人(关联操作日志 audit_rx_* / audit_pay_* */
@@ -2618,6 +2763,11 @@ function normalizePrescriptionOrderListQuery(params: Record<string, unknown>): R
p.service_channel = String(p.service_channel).trim()
if (p.service_channel === '') delete p.service_channel
}
if (p.has_aux_formula === '' || p.has_aux_formula === undefined || p.has_aux_formula === null) {
delete p.has_aux_formula
} else {
p.has_aux_formula = Number(p.has_aux_formula)
}
if (Number(p.exclude_fulfillment_cancelled) !== 1) delete p.exclude_fulfillment_cancelled
if (!String(p.start_time || '').trim() || !String(p.end_time || '').trim()) {
delete p.start_time
@@ -2856,6 +3006,7 @@ function handleReset() {
queryParams.payment_slip_audit_status = ''
queryParams.supply_mode = ''
queryParams.service_channel = ''
queryParams.has_aux_formula = ''
queryParams.audit_admin_id = ''
queryParams.exclude_fulfillment_cancelled = 0
resetParams()
+7 -2
View File
@@ -556,7 +556,11 @@ const validatePhone = (rule: any, value: any, callback: any) => {
}
const validateIdCard = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(value)) {
if (!value) {
callback(new Error('请输入身份证号'))
return
}
if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(value)) {
callback(new Error('身份证号格式不正确'))
return
}
@@ -565,13 +569,14 @@ const validateIdCard = (rule: any, value: any, callback: any) => {
const formRules = {
patient_name: [{ required: true, message: '请输入患者姓名', trigger: 'blur' }],
id_card: [{ validator: validateIdCard, trigger: 'blur' }],
id_card: [{ required: true, validator: validateIdCard, trigger: 'blur' }],
phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
diagnosis_type: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
syndrome_type: [{ required: true, message: '请选择证型', trigger: 'change' }],
diabetes_type: [{ required: true, message: '请选择糖尿病期数', trigger: 'change' }],
local_hospital_name: [{ required: true, message: '请输入当地就诊医院名称', trigger: 'blur' }],
diabetes_discovery_year: [{ max: 50, message: '最多50个字符', trigger: 'blur' }]
}
+79 -25
View File
@@ -103,7 +103,26 @@
</el-col>
<el-col :span="12">
<el-form-item label="身份证号" prop="id_card">
<template v-if="showIdCardMaskedEdit">
<el-input
:model-value="
idCardRevealUnlocked ? originalIdCard || '' : maskIdCard(originalIdCard || '')
"
readonly
placeholder="点击可查看完整身份证号"
maxlength="18"
class="cursor-pointer phone-mask-toggle"
@click="toggleIdCardReveal"
/>
<p
v-if="originalIdCard && !idCardRevealUnlocked"
class="text-xs text-gray-400 mt-1"
>
当前为脱敏显示点击输入框可查看完整号码
</p>
</template>
<el-input
v-else
v-model="formData.id_card"
placeholder="请输入身份证号"
maxlength="18"
@@ -822,6 +841,13 @@ const showPhoneMaskedEdit = computed(() => {
return !hasPhonePlainPermission.value
})
const phoneRevealUnlocked = ref(false)
/** 编辑且无明文权限或基本信息锁定:只读脱敏,点击切换查看完整身份证号 */
const showIdCardMaskedEdit = computed(() => {
if (mode.value !== 'edit') return false
if (!canEditPatientBasicFields.value) return true
return !hasPhonePlainPermission.value
})
const idCardRevealUnlocked = ref(false)
const activeTab = ref('basic')
const drawerTitle = computed(() => {
if (viewOnly.value) return '诊单详情'
@@ -961,6 +987,11 @@ const togglePhoneReveal = () => {
phoneRevealUnlocked.value = !phoneRevealUnlocked.value
}
const toggleIdCardReveal = () => {
if (!originalIdCard.value) return
idCardRevealUnlocked.value = !idCardRevealUnlocked.value
}
const maskIdCard = (idCard: string) => {
if (!idCard) return idCard
if (idCard.length === 15) {
@@ -1031,43 +1062,40 @@ const handlePhoneBlur = async () => {
}
}
// 身份证聚焦 - 显示完整数据
// 身份证聚焦 - 显示完整数据(无「明文」权限时仅新增模式在失焦后可再次展开编辑)
const handleIdCardFocus = () => {
isIdCardFocused.value = true
if (originalIdCard.value) {
if (!originalIdCard.value) return
if (hasPhonePlainPermission.value) {
formData.value.id_card = originalIdCard.value
return
}
if (mode.value === 'add') {
formData.value.id_card = originalIdCard.value
}
}
// 身份证失焦 - 恢复脱敏并验证
// 身份证失焦:有明文权限则保持明文并校验;新增且无明文权限时仍脱敏展示
const handleIdCardBlur = async () => {
isIdCardFocused.value = false
// 保存原始数据并脱敏显示
if (formData.value.id_card && (formData.value.id_card.length === 15 || formData.value.id_card.length === 18)) {
originalIdCard.value = formData.value.id_card
// 验证身份证格式并计算年龄
if (/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(formData.value.id_card)) {
// 从身份证提取出生日期计算年龄
const id = formData.value.id_card
const birthYear = parseInt(id.substring(6, 10))
const birthMonth = parseInt(id.substring(10, 12))
const birthDay = parseInt(id.substring(12, 14))
const processIdCard = async (idCard: string) => {
if (/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(idCard)) {
const birthYear = parseInt(idCard.substring(6, 10))
const birthMonth = parseInt(idCard.substring(10, 12))
const birthDay = parseInt(idCard.substring(12, 14))
const today = new Date()
let age = today.getFullYear() - birthYear
if (today.getMonth() + 1 < birthMonth || (today.getMonth() + 1 === birthMonth && today.getDate() < birthDay)) {
age--
}
formData.value.age = age
// 验证身份证唯一性
try {
const result = await checkIdCard({
id_card: formData.value.id_card,
id_card: idCard,
id: formData.value.id || ''
})
if (result.exists) {
ElMessage.warning(result.message)
}
@@ -1075,8 +1103,23 @@ const handleIdCardBlur = async () => {
console.error('检查身份证号失败:', error)
}
}
// 脱敏显示
}
if (hasPhonePlainPermission.value) {
if (formData.value.id_card && (formData.value.id_card.length === 15 || formData.value.id_card.length === 18)) {
originalIdCard.value = formData.value.id_card
await processIdCard(formData.value.id_card)
}
return
}
if (mode.value !== 'add') {
return
}
if (formData.value.id_card && (formData.value.id_card.length === 15 || formData.value.id_card.length === 18)) {
originalIdCard.value = formData.value.id_card
await processIdCard(formData.value.id_card)
formData.value.id_card = maskIdCard(formData.value.id_card)
}
}
@@ -1099,7 +1142,11 @@ const validatePhone = (rule: any, value: any, callback: any) => {
const validateIdCard = (rule: any, value: any, callback: any) => {
// 使用原始数据进行验证
const idCardToValidate = originalIdCard.value || value
if (idCardToValidate && !/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(idCardToValidate)) {
if (!idCardToValidate) {
callback(new Error('请输入身份证号'))
return
}
if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(idCardToValidate)) {
callback(new Error('身份证号格式不正确'))
return
}
@@ -1108,12 +1155,13 @@ const validateIdCard = (rule: any, value: any, callback: any) => {
const formRules = {
patient_name: [{ required: true, message: '请输入患者姓名', trigger: 'blur' }],
id_card: [{ validator: validateIdCard, trigger: 'blur' }],
id_card: [{ required: true, validator: validateIdCard, trigger: 'blur' }],
phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
fasting_blood_sugar: [{ required: true, message: '请输入空腹血糖', trigger: 'blur' }],
diagnosis_type: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],
local_hospital_name: [{ required: true, message: '请输入当地就诊医院名称', trigger: 'blur' }],
diabetes_discovery_year: [{ max: 50, message: '最多50个字符', trigger: 'blur' }],
}
@@ -1213,7 +1261,7 @@ async function loadDiagnosisDetailIntoForm(id: number): Promise<void> {
latestPrescriptionOrder.value = data.latest_prescription_order ?? null
data.phone = hasPhonePlainPermission.value ? data.phone || '' : maskPhone(data.phone || '')
data.id_card = maskIdCard(data.id_card || '')
data.id_card = hasPhonePlainPermission.value ? data.id_card || '' : maskIdCard(data.id_card || '')
formData.value = data
formData.value.create_source = data.create_source ?? ''
@@ -1234,6 +1282,7 @@ const open = async (type: string, id?: number) => {
visible.value = true
activeTab.value = 'basic' // 重置到基本信息标签页
phoneRevealUnlocked.value = false
idCardRevealUnlocked.value = false
resetPatientBasicLockState()
// 加载字典数据
@@ -1257,6 +1306,7 @@ const openViewOnly = async (id: number) => {
visible.value = true
activeTab.value = 'basic'
phoneRevealUnlocked.value = false
idCardRevealUnlocked.value = false
resetPatientBasicLockState()
await getDictOptions()
await loadDiagnosisDetailIntoForm(id)
@@ -1282,6 +1332,7 @@ const handleSubmit = async () => {
if (result && result.id) {
mode.value = 'edit'
phoneRevealUnlocked.value = false
idCardRevealUnlocked.value = false
formData.value.id = result.id
// 重新获取详情,确保patient_id等字段正确
@@ -1296,7 +1347,9 @@ const handleSubmit = async () => {
formData.value.phone = hasPhonePlainPermission.value
? detail.phone || ''
: maskPhone(detail.phone || '')
formData.value.id_card = maskIdCard(detail.id_card || '')
formData.value.id_card = hasPhonePlainPermission.value
? detail.id_card || ''
: maskIdCard(detail.id_card || '')
} catch (error) {
console.error('获取详情失败:', error)
}
@@ -1398,6 +1451,7 @@ const handleClose = () => {
isPhoneFocused.value = false
isIdCardFocused.value = false
phoneRevealUnlocked.value = false
idCardRevealUnlocked.value = false
viewOnly.value = false
visible.value = false