2636 lines
94 KiB
Vue
2636 lines
94 KiB
Vue
<!-- 中试服务申请单详情页 -->
|
||
<template>
|
||
<div class="page-container">
|
||
<!-- 顶部导航栏 -->
|
||
<header class="page-head">
|
||
<div class="detail-head-row">
|
||
<div class="detail-head-left">
|
||
<el-button class="btn-ghost" @click="router.back()">
|
||
<el-icon><ArrowLeft /></el-icon>
|
||
返回
|
||
</el-button>
|
||
<h1 class="detail-title">
|
||
申请单详情
|
||
<span v-if="applicationDetail.applicationNo" class="detail-code">
|
||
{{ applicationDetail.applicationNo }}
|
||
</span>
|
||
</h1>
|
||
<el-tag
|
||
v-if="applicationDetail.applicationStatus"
|
||
:type="applicationStatusTagType(applicationDetail.applicationStatus)"
|
||
size="large"
|
||
>
|
||
{{ applicationStatusLabel(applicationDetail.applicationStatus) }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="detail-head-right">
|
||
<el-button
|
||
v-if="applicationDetail.applicationStatus === 'dks'"
|
||
class="btn-main"
|
||
type="primary"
|
||
@click="handleStart"
|
||
>
|
||
开始
|
||
</el-button>
|
||
<el-button
|
||
v-if="applicationDetail.applicationStatus === 'jxz'"
|
||
class="btn-main"
|
||
type="primary"
|
||
@click="handleComplete"
|
||
>
|
||
完成
|
||
</el-button>
|
||
<el-button
|
||
v-if="applicationDetail.applicationStatus === 'dks' || applicationDetail.applicationStatus === 'jxz'"
|
||
class="btn-op-item op-warn"
|
||
type="danger"
|
||
@click="handleVoid"
|
||
>
|
||
作废
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="panel">
|
||
<el-tabs v-model="activeTab" class="detail-tabs">
|
||
<!-- ═══ Tab 1: 基本信息 ═══ -->
|
||
<el-tab-pane label="基本信息" name="basic">
|
||
<!-- 申请单信息(只读) -->
|
||
<div class="detail-section">
|
||
<h3 class="detail-section-title">申请单信息</h3>
|
||
<el-descriptions :column="3" border>
|
||
<el-descriptions-item label="申请单号">
|
||
{{ displayValue(applicationDetail.applicationNo) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="渠道名称">
|
||
{{ displayValue(applicationDetail.channelName) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="关联服务订单">
|
||
{{ displayValue(applicationDetail.serviceOrderNo) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="状态">
|
||
<el-tag
|
||
v-if="applicationDetail.applicationStatus"
|
||
:type="applicationStatusTagType(applicationDetail.applicationStatus)"
|
||
size="small"
|
||
>
|
||
{{ applicationStatusLabel(applicationDetail.applicationStatus) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">
|
||
{{ displayValue(applicationDetail.createTime) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="备注">
|
||
{{ displayValue(applicationDetail.remark) }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
|
||
<!-- 患者信息(可编辑) -->
|
||
<div class="detail-section">
|
||
<div class="detail-section-header">
|
||
<h3 class="detail-section-title">患者信息</h3>
|
||
<el-button type="primary" plain size="small" @click="openCustomerDialog">
|
||
<el-icon><User /></el-icon>
|
||
选择客户
|
||
</el-button>
|
||
</div>
|
||
<el-form
|
||
ref="patientFormRef"
|
||
:model="patientForm"
|
||
label-position="top"
|
||
class="dialog-form-layout"
|
||
>
|
||
<div class="form-grid-2">
|
||
<el-form-item label="姓名" prop="patientName">
|
||
<el-input v-model="patientForm.patientName" placeholder="请输入患者姓名" />
|
||
</el-form-item>
|
||
<el-form-item label="性别" prop="sex">
|
||
<el-select v-model="patientForm.sex" placeholder="请选择" clearable>
|
||
<el-option label="男" value="男" />
|
||
<el-option label="女" value="女" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="年龄" prop="age">
|
||
<el-input-number
|
||
v-model="patientForm.age"
|
||
:min="0"
|
||
:max="200"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="身份证号" prop="idCardNo">
|
||
<el-input v-model="patientForm.idCardNo" placeholder="请输入身份证号" />
|
||
</el-form-item>
|
||
<el-form-item label="联系电话" prop="phoneNo">
|
||
<el-input v-model="patientForm.phoneNo" placeholder="请输入联系电话" />
|
||
</el-form-item>
|
||
<el-form-item label="紧急联系人" prop="emergencyContact">
|
||
<el-input v-model="patientForm.emergencyContact" placeholder="请输入紧急联系人" />
|
||
</el-form-item>
|
||
<el-form-item label="紧急联系人电话" prop="emergencyPhone">
|
||
<el-input v-model="patientForm.emergencyPhone" placeholder="请输入紧急联系人电话" />
|
||
</el-form-item>
|
||
<el-form-item label="是否需要体检" prop="needCheckup">
|
||
<el-switch
|
||
v-model="patientForm.needCheckup"
|
||
:active-value="1"
|
||
:inactive-value="0"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="临床诊断" prop="clinicalDiagnosis" class="form-col-span-2">
|
||
<el-input
|
||
v-model="patientForm.clinicalDiagnosis"
|
||
type="textarea"
|
||
placeholder="请输入临床诊断"
|
||
:rows="3"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="诊断附件" class="form-col-span-2">
|
||
<div class="attachment-upload-row">
|
||
<div
|
||
v-for="(file, idx) in diagnosisFileList"
|
||
:key="idx"
|
||
class="attachment-card"
|
||
>
|
||
<el-icon class="attachment-icon"><Document /></el-icon>
|
||
<span class="attachment-name" :title="file.name">{{ file.name }}</span>
|
||
<el-button
|
||
link
|
||
type="danger"
|
||
size="small"
|
||
@click="removeDiagnosisFile(idx)"
|
||
>
|
||
<el-icon><Delete /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
<el-upload
|
||
:show-file-list="false"
|
||
:auto-upload="false"
|
||
:on-change="handleDiagnosisFileChange"
|
||
class="attachment-trigger"
|
||
>
|
||
<div class="attachment-trigger-card">
|
||
<el-icon size="20"><Plus /></el-icon>
|
||
<span>上传附件</span>
|
||
</div>
|
||
</el-upload>
|
||
</div>
|
||
<div class="form-item-hint">支持图片、PDF 等格式,单文件不超过 10MB</div>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="detail-section-footer">
|
||
<el-button
|
||
type="primary"
|
||
:loading="patientSaving"
|
||
@click="handleSavePatient"
|
||
>
|
||
保存患者信息
|
||
</el-button>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<!-- ═══ Tab 2: 输注方案 ═══ -->
|
||
<el-tab-pane label="输注方案" name="infusion">
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row">
|
||
<el-button class="btn-main" type="primary" @click="openPlanDialog()">
|
||
<el-icon><Plus /></el-icon>
|
||
新增输注方案
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table
|
||
:data="planList"
|
||
v-loading="planLoading"
|
||
stripe
|
||
row-key="id"
|
||
class="list-table"
|
||
>
|
||
<el-table-column type="expand">
|
||
<template #default="{ row: plan }">
|
||
<div class="expand-content">
|
||
<el-table
|
||
:data="scheduleMap[plan.id] || []"
|
||
v-loading="scheduleLoadingMap[plan.id]"
|
||
stripe
|
||
size="small"
|
||
class="list-table inner-table"
|
||
>
|
||
<el-table-column prop="orderNum" label="序号" min-width="60" align="center" />
|
||
<el-table-column prop="cellType" label="细胞类型" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="specText" label="规格" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="infusionTime" label="输注时间" min-width="160" align="center">
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.infusionTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
v-if="row.scheduleStatus && row.scheduleStatus !== 'dsc'"
|
||
:type="scheduleStatusTagType(row.scheduleStatus)"
|
||
size="small"
|
||
>
|
||
{{ scheduleStatusLabel(row.scheduleStatus) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="320" align="center">
|
||
<template #default="{ row }">
|
||
<el-button
|
||
class="btn-op-item"
|
||
@click="openScheduleDialog(row, plan)"
|
||
>
|
||
编辑
|
||
</el-button>
|
||
<el-popconfirm
|
||
title="确定删除该计划吗?"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeleteSchedule(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn">删除</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
<el-button
|
||
v-if="row.scheduleStatus === 'dsc'"
|
||
class="btn-op-item primary"
|
||
type="primary"
|
||
@click="handleMarkScheduleComplete(row, plan)"
|
||
>
|
||
完成标记
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.scheduleStatus === 'dsc'"
|
||
class="btn-op-item"
|
||
type="primary"
|
||
@click="handleCreateOutboundProduction(row, plan)"
|
||
>
|
||
创建生产工单
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.scheduleStatus === 'ysc'"
|
||
class="btn-op-item"
|
||
@click="handleCreateOutboundQc(row)"
|
||
>
|
||
创建质检工单
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="expand-footer">
|
||
<el-button
|
||
type="primary"
|
||
plain
|
||
size="small"
|
||
@click="openScheduleDialog(null, plan)"
|
||
>
|
||
<el-icon><Plus /></el-icon>
|
||
手动添加计划
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="cellType" label="细胞类型" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="specText" label="规格" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="totalCourseCount" label="总疗程数" min-width="80" align="center" />
|
||
<el-table-column prop="courseInterval" label="疗程间隔" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="infusionRoute" label="输注途径" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="infusionStartDate" label="开始日期" min-width="120" align="center">
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.infusionStartDate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="进度" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
{{ getCompletedCount(row.id) }}/{{ getScheduleCount(row.id) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="160" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button class="btn-op-item" @click="openPlanDialog(row)">编辑</el-button>
|
||
<el-popconfirm
|
||
title="确定删除该方案吗?将同时删除其下所有计划。"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeletePlan(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn">删除</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
|
||
<!-- ═══ Tab 3: 体检信息 ═══ -->
|
||
<el-tab-pane label="体检信息" name="checkup">
|
||
<!-- 体检信息卡片 -->
|
||
<div class="detail-section">
|
||
<div class="detail-section-header">
|
||
<h3 class="detail-section-title">体检信息</h3>
|
||
<div v-if="checkupData" class="detail-section-actions">
|
||
<el-button type="primary" plain size="small" @click="openCheckupDialog">
|
||
编辑
|
||
</el-button>
|
||
<el-button
|
||
v-if="checkupData.checkupStatus === 'dtj'"
|
||
type="warning"
|
||
plain
|
||
size="small"
|
||
@click="handleMarkChecked"
|
||
>
|
||
标记已体检
|
||
</el-button>
|
||
<el-button
|
||
v-if="checkupData.feeStatus === 'wjs'"
|
||
type="success"
|
||
plain
|
||
size="small"
|
||
@click="handleSettleFee"
|
||
>
|
||
结算费用
|
||
</el-button>
|
||
</div>
|
||
<el-button
|
||
v-else
|
||
type="primary"
|
||
size="small"
|
||
@click="openCheckupDialog"
|
||
>
|
||
<el-icon><Plus /></el-icon>
|
||
新增体检信息
|
||
</el-button>
|
||
</div>
|
||
|
||
<template v-if="checkupData">
|
||
<el-descriptions :column="3" border>
|
||
<el-descriptions-item label="体检机构">
|
||
{{ displayValue(checkupData.institutionName) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="预约时间">
|
||
{{ displayValue(checkupData.reserveTime) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="预约费用">
|
||
{{ checkupData.reserveFee != null ? `${checkupData.reserveFee} 元` : '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="实际体检时间">
|
||
{{ displayValue(checkupData.actualCheckupTime) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="实际体检费用">
|
||
{{ checkupData.actualCheckupFee != null ? `${checkupData.actualCheckupFee} 元` : '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="体检状态">
|
||
<el-tag
|
||
v-if="checkupData.checkupStatus"
|
||
:type="checkupStatusTagType(checkupData.checkupStatus)"
|
||
size="small"
|
||
>
|
||
{{ checkupStatusLabel(checkupData.checkupStatus) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="费用状态">
|
||
<el-tag
|
||
v-if="checkupData.feeStatus"
|
||
:type="feeStatusTagType(checkupData.feeStatus)"
|
||
size="small"
|
||
>
|
||
{{ feeStatusLabel(checkupData.feeStatus) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="体检备注" :span="2">
|
||
{{ displayValue(checkupData.checkupRemark) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="体检报告" :span="3">
|
||
<template v-if="checkupReportFiles.length">
|
||
<div class="attachment-upload-row">
|
||
<a
|
||
v-for="(file, idx) in checkupReportFiles"
|
||
:key="idx"
|
||
:href="file.url"
|
||
target="_blank"
|
||
class="attachment-card attachment-link"
|
||
>
|
||
<el-icon class="attachment-icon"><Document /></el-icon>
|
||
<span class="attachment-name" :title="file.name">{{ file.name }}</span>
|
||
</a>
|
||
</div>
|
||
</template>
|
||
<span v-else>-</span>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</template>
|
||
<el-empty v-else description="暂无体检信息,请点击上方按钮新增" />
|
||
</div>
|
||
|
||
<!-- 预约项目明细 -->
|
||
<div v-if="checkupData" class="detail-section">
|
||
<div class="detail-section-header">
|
||
<h3 class="detail-section-title">预约项目明细</h3>
|
||
<el-button type="primary" plain size="small" @click="openReserveItemDialog">
|
||
<el-icon><Plus /></el-icon>
|
||
添加预约项目
|
||
</el-button>
|
||
</div>
|
||
<el-table
|
||
:data="reserveItems"
|
||
v-loading="reserveItemsLoading"
|
||
stripe
|
||
class="list-table"
|
||
>
|
||
<el-table-column prop="itemName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||
<el-table-column label="项目费用(元)" min-width="140" align="center">
|
||
<template #default="{ row }">
|
||
{{ row.itemFee != null ? row.itemFee : '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-popconfirm
|
||
title="确定删除该项目吗?"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeleteReserveItem(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn" size="small">删除</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 实际项目明细 -->
|
||
<div v-if="checkupData && checkupData.checkupStatus === 'ytj'" class="detail-section">
|
||
<div class="detail-section-header">
|
||
<h3 class="detail-section-title">实际项目明细</h3>
|
||
<el-button type="primary" plain size="small" @click="openActualItemDialog">
|
||
<el-icon><Plus /></el-icon>
|
||
添加实际项目
|
||
</el-button>
|
||
</div>
|
||
<el-table
|
||
:data="actualItems"
|
||
v-loading="actualItemsLoading"
|
||
stripe
|
||
class="list-table"
|
||
>
|
||
<el-table-column prop="itemName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||
<el-table-column label="项目费用(元)" min-width="140" align="center">
|
||
<template #default="{ row }">
|
||
{{ row.itemFee != null ? row.itemFee : '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-popconfirm
|
||
title="确定删除该项目吗?"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeleteActualItem(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn" size="small">删除</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<!-- ═══ Tab 4: 交付信息 ═══ -->
|
||
<el-tab-pane label="交付信息" name="delivery">
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row" />
|
||
</div>
|
||
|
||
<el-table
|
||
:data="deliveryList"
|
||
v-loading="deliveryLoading"
|
||
stripe
|
||
row-key="scheduleId"
|
||
class="list-table"
|
||
>
|
||
<el-table-column prop="planOrderNum" label="方案序号" min-width="80" align="center" />
|
||
<el-table-column prop="scheduleOrderNum" label="计划序号" min-width="80" align="center" />
|
||
<el-table-column prop="cellType" label="细胞类型" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="specText" label="规格" min-width="120" show-overflow-tooltip />
|
||
<el-table-column label="输注时间" min-width="160" align="center">
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.infusionTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="计划状态" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
v-if="row.scheduleStatus && row.scheduleStatus !== 'dsc'"
|
||
:type="scheduleStatusTagType(row.scheduleStatus)"
|
||
size="small"
|
||
>
|
||
{{ scheduleStatusLabel(row.scheduleStatus) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="交付编号" min-width="140" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.deliveryNo) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="交付方式" min-width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
v-if="row.deliveryMode"
|
||
type="primary"
|
||
size="small"
|
||
>
|
||
{{ deliveryModeLabel(row.deliveryMode) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="260" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<template v-if="!row.deliveryNo">
|
||
<el-button
|
||
class="btn-main"
|
||
type="primary"
|
||
size="small"
|
||
@click="handleCreateDelivery(row)"
|
||
>
|
||
创建交付
|
||
</el-button>
|
||
</template>
|
||
<template v-else>
|
||
<el-button
|
||
class="btn-op-item"
|
||
size="small"
|
||
@click="openDeliveryDetailDialog(row)"
|
||
>
|
||
查看交付
|
||
</el-button>
|
||
<el-popconfirm
|
||
title="确定删除该交付吗?"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeleteDelivery(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn" size="small">删除交付</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
|
||
<!-- ═══ Tab: 放行审核 ═══ -->
|
||
<el-tab-pane label="放行审核" name="releaseReview">
|
||
<el-table :data="releaseReviewList" v-loading="releaseReviewLoading" stripe class="list-table">
|
||
<el-table-column prop="infusionScheduleId" label="输注计划ID" min-width="120" show-overflow-tooltip />
|
||
<el-table-column label="审核结论" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
v-if="row.reviewConclusion"
|
||
:type="releaseReviewConclusionTagType(row.reviewConclusion)"
|
||
size="small"
|
||
>
|
||
{{ releaseReviewConclusionLabel(row.reviewConclusion) }}
|
||
</el-tag>
|
||
<span v-else>待审核</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="reviewRemark" label="审核备注" min-width="180" show-overflow-tooltip />
|
||
<el-table-column label="审核时间" min-width="160" align="center">
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.reviewTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="260" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button v-if="!row.reviewConclusion" class="btn-op-item" type="primary" @click="handleReviewConclusion(row, 'tg')">
|
||
通过
|
||
</el-button>
|
||
<el-button v-if="!row.reviewConclusion" class="btn-op-item op-warn" @click="handleReviewConclusion(row, 'btg')">
|
||
不通过
|
||
</el-button>
|
||
<el-popconfirm
|
||
title="确定删除该审核记录吗?"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeleteReleaseReview(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn">删除</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
|
||
<!-- ═══ Tab: 应用确认 ═══ -->
|
||
<el-tab-pane label="应用确认" name="infusionConfirm">
|
||
<el-table :data="infusionConfirmList" v-loading="infusionConfirmLoading" stripe class="list-table">
|
||
<el-table-column prop="infusionScheduleId" label="输注计划ID" min-width="120" show-overflow-tooltip />
|
||
<el-table-column label="确认状态" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.confirmTime ? 'success' : 'info'" size="small">
|
||
{{ row.confirmTime ? '已确认' : '待确认' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="confirmDesc" label="确认说明" min-width="180" show-overflow-tooltip />
|
||
<el-table-column label="确认时间" min-width="160" align="center">
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.confirmTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="260" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button v-if="!row.confirmTime" class="btn-op-item" type="primary" @click="handleConfirmInfusion(row)">
|
||
确认
|
||
</el-button>
|
||
<el-button class="btn-op-item" @click="handleEditConfirmDesc(row)">编辑</el-button>
|
||
<el-popconfirm
|
||
title="确定删除该确认记录吗?"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeleteInfusionConfirm(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn">删除</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</section>
|
||
|
||
<!-- ═══ 体检信息编辑弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="checkupDialogVisible"
|
||
:title="checkupData ? '编辑体检信息' : '新增体检信息'"
|
||
width="640px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="dialog-form-layout">
|
||
<el-form
|
||
ref="checkupFormRef"
|
||
:model="checkupForm"
|
||
label-position="top"
|
||
class="dialog-form"
|
||
>
|
||
<div class="form-grid-2">
|
||
<el-form-item label="预约时间">
|
||
<el-date-picker
|
||
v-model="checkupForm.reserveTime"
|
||
type="datetime"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
placeholder="请选择预约时间"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预约费用(元)">
|
||
<el-input-number
|
||
v-model="checkupForm.reserveFee"
|
||
:min="0"
|
||
:precision="2"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="实际体检时间">
|
||
<el-date-picker
|
||
v-model="checkupForm.actualCheckupTime"
|
||
type="datetime"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
placeholder="请选择实际体检时间"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="实际体检费用(元)">
|
||
<el-input-number
|
||
v-model="checkupForm.actualCheckupFee"
|
||
:min="0"
|
||
:precision="2"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="体检备注" class="form-col-span-2">
|
||
<el-input
|
||
v-model="checkupForm.checkupRemark"
|
||
type="textarea"
|
||
placeholder="请输入体检备注"
|
||
:rows="3"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="体检报告" class="form-col-span-2">
|
||
<div class="attachment-upload-row">
|
||
<div
|
||
v-for="(file, idx) in checkupDialogFileList"
|
||
:key="idx"
|
||
class="attachment-card"
|
||
>
|
||
<el-icon class="attachment-icon"><Document /></el-icon>
|
||
<span class="attachment-name" :title="file.name">{{ file.name }}</span>
|
||
<el-button
|
||
link
|
||
type="danger"
|
||
size="small"
|
||
@click="removeCheckupDialogFile(idx)"
|
||
>
|
||
<el-icon><Delete /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
<el-upload
|
||
:show-file-list="false"
|
||
:auto-upload="false"
|
||
:on-change="handleCheckupDialogFileChange"
|
||
class="attachment-trigger"
|
||
>
|
||
<div class="attachment-trigger-card">
|
||
<el-icon size="20"><Plus /></el-icon>
|
||
<span>上传附件</span>
|
||
</div>
|
||
</el-upload>
|
||
</div>
|
||
<div class="form-item-hint">支持图片、PDF 等格式,单文件不超过 10MB</div>
|
||
</el-form-item>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="checkupDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="checkupSaving" @click="handleSaveCheckup">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ═══ 预约项目添加弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="reserveItemDialogVisible"
|
||
title="添加预约项目"
|
||
width="420px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="dialog-form-layout">
|
||
<el-form :model="reserveItemForm" label-position="top" class="dialog-form">
|
||
<el-form-item label="项目名称">
|
||
<el-input v-model="reserveItemForm.itemName" placeholder="请输入项目名称" />
|
||
</el-form-item>
|
||
<el-form-item label="项目费用(元)">
|
||
<el-input-number
|
||
v-model="reserveItemForm.itemFee"
|
||
:min="0"
|
||
:precision="2"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="reserveItemDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="reserveItemSaving" @click="handleSaveReserveItem">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ═══ 实际项目添加弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="actualItemDialogVisible"
|
||
title="添加实际项目"
|
||
width="420px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="dialog-form-layout">
|
||
<el-form :model="actualItemForm" label-position="top" class="dialog-form">
|
||
<el-form-item label="项目名称">
|
||
<el-input v-model="actualItemForm.itemName" placeholder="请输入项目名称" />
|
||
</el-form-item>
|
||
<el-form-item label="项目费用(元)">
|
||
<el-input-number
|
||
v-model="actualItemForm.itemFee"
|
||
:min="0"
|
||
:precision="2"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="actualItemDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="actualItemSaving" @click="handleSaveActualItem">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ═══ 交付详情弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="deliveryDetailDialogVisible"
|
||
title="交付详情"
|
||
width="720px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
>
|
||
<el-tabs v-model="deliveryDetailTab">
|
||
<el-tab-pane label="物料清单" name="materials">
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row">
|
||
<el-button type="primary" plain size="small" @click="openAddMaterialDialog">
|
||
<el-icon><Plus /></el-icon>
|
||
添加物料
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<el-table
|
||
:data="deliveryMaterials"
|
||
v-loading="deliveryMaterialsLoading"
|
||
stripe
|
||
class="list-table"
|
||
>
|
||
<el-table-column prop="materialName" label="物料名称" min-width="160" show-overflow-tooltip />
|
||
<el-table-column label="数量" min-width="80" align="center">
|
||
<template #default="{ row }">
|
||
{{ row.quantity ?? '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="单位" min-width="80" align="center">
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.unitName) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" min-width="140" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ displayValue(row.remark) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-popconfirm
|
||
title="确定删除该物料吗?"
|
||
confirm-button-text="确定"
|
||
cancel-button-text="取消"
|
||
@confirm="handleDeleteMaterial(row)"
|
||
>
|
||
<template #reference>
|
||
<el-button class="btn-op-item op-warn" size="small">删除</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="交付方式" name="method">
|
||
<el-form :model="deliveryMethodForm" label-position="top" class="dialog-form">
|
||
<el-form-item label="交付方式">
|
||
<el-radio-group v-model="deliveryMethodForm.deliveryMode">
|
||
<el-radio value="qdzt">渠道自提</el-radio>
|
||
<el-radio value="kdfh">快递发货</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<div class="form-grid-2">
|
||
<el-form-item label="交付附件" class="form-col-span-2">
|
||
<div class="attachment-upload-row">
|
||
<div
|
||
v-for="(file, idx) in deliveryMethodFileList"
|
||
:key="idx"
|
||
class="attachment-card"
|
||
>
|
||
<el-icon class="attachment-icon"><Document /></el-icon>
|
||
<span class="attachment-name" :title="file.name">{{ file.name }}</span>
|
||
<el-button
|
||
link
|
||
type="danger"
|
||
size="small"
|
||
@click="removeDeliveryMethodFile(idx)"
|
||
>
|
||
<el-icon><Delete /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
<el-upload
|
||
:show-file-list="false"
|
||
:auto-upload="false"
|
||
:on-change="handleDeliveryMethodFileChange"
|
||
class="attachment-trigger"
|
||
>
|
||
<div class="attachment-trigger-card">
|
||
<el-icon size="20"><Plus /></el-icon>
|
||
<span>上传附件</span>
|
||
</div>
|
||
</el-upload>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="发运时间">
|
||
<el-date-picker
|
||
v-model="deliveryMethodForm.shipTime"
|
||
type="datetime"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
placeholder="请选择发运时间"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="交接温度">
|
||
<el-input v-model="deliveryMethodForm.handoverTemperature" placeholder="请输入交接温度" />
|
||
</el-form-item>
|
||
<template v-if="deliveryMethodForm.deliveryMode === 'qdzt'">
|
||
<el-form-item label="自提人员">
|
||
<el-input v-model="deliveryMethodForm.pickupPerson" placeholder="请输入自提人员" />
|
||
</el-form-item>
|
||
<el-form-item label="联系电话">
|
||
<el-input v-model="deliveryMethodForm.pickupPhone" placeholder="请输入联系电话" />
|
||
</el-form-item>
|
||
</template>
|
||
<template v-if="deliveryMethodForm.deliveryMode === 'kdfh'">
|
||
<el-form-item label="发货人">
|
||
<el-input v-model="deliveryMethodForm.senderName" placeholder="请输入发货人" />
|
||
</el-form-item>
|
||
<el-form-item label="快递公司">
|
||
<el-input v-model="deliveryMethodForm.expressCompany" placeholder="请输入快递公司" />
|
||
</el-form-item>
|
||
<el-form-item label="快递单号">
|
||
<el-input v-model="deliveryMethodForm.expressNo" placeholder="请输入快递单号" />
|
||
</el-form-item>
|
||
<el-form-item label="快递费用(元)">
|
||
<el-input-number
|
||
v-model="deliveryMethodForm.expressFee"
|
||
:min="0"
|
||
:precision="2"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计到达时间">
|
||
<el-date-picker
|
||
v-model="deliveryMethodForm.estimatedArrivalTime"
|
||
type="datetime"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
placeholder="请选择预计到达时间"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</template>
|
||
</div>
|
||
</el-form>
|
||
<div class="detail-section-footer">
|
||
<el-button
|
||
type="primary"
|
||
:loading="deliveryMethodSaving"
|
||
@click="handleSaveDeliveryMethod"
|
||
>
|
||
保存交付方式
|
||
</el-button>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-dialog>
|
||
|
||
<!-- ═══ 物料添加弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="addMaterialDialogVisible"
|
||
title="添加物料"
|
||
width="420px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="dialog-form-layout">
|
||
<el-form :model="addMaterialForm" label-position="top" class="dialog-form">
|
||
<el-form-item label="物料名称">
|
||
<el-input v-model="addMaterialForm.materialName" placeholder="请输入物料名称" />
|
||
</el-form-item>
|
||
<el-form-item label="数量">
|
||
<el-input-number
|
||
v-model="addMaterialForm.quantity"
|
||
:min="1"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="单位">
|
||
<el-input v-model="addMaterialForm.unitName" placeholder="请输入单位" />
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="addMaterialForm.remark" placeholder="请输入备注" />
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="addMaterialDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="addMaterialSaving" @click="handleSaveMaterial">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ═══ 客户选择弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="customerDialogVisible"
|
||
title="选择客户"
|
||
width="800px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="customer-dialog-content">
|
||
<el-form :inline="true" class="filter-form" @submit.prevent>
|
||
<el-form-item label="姓名">
|
||
<el-input
|
||
v-model="customerSearch.keyword"
|
||
placeholder="客户姓名"
|
||
clearable
|
||
@keyup.enter="loadCustomerList"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="手机号">
|
||
<el-input
|
||
v-model="customerSearch.phoneNo"
|
||
placeholder="手机号"
|
||
clearable
|
||
@keyup.enter="loadCustomerList"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="loadCustomerList">
|
||
<el-icon><Search /></el-icon>搜索
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-table
|
||
:data="customerList"
|
||
v-loading="customerLoading"
|
||
stripe
|
||
highlight-current-row
|
||
@current-change="handleCustomerSelect"
|
||
class="list-table"
|
||
>
|
||
<el-table-column prop="customerName" label="姓名" min-width="120" />
|
||
<el-table-column prop="sex" label="性别" min-width="80" align="center" />
|
||
<el-table-column prop="phoneNo" label="手机号" min-width="130" />
|
||
<el-table-column prop="idCardNo" label="身份证号" min-width="180" show-overflow-tooltip />
|
||
</el-table>
|
||
<div v-if="customerTotal > customerPageSize" class="pagination list-pager-bar">
|
||
<el-pagination
|
||
:current-page="customerPage"
|
||
:page-size="customerPageSize"
|
||
:total="customerTotal"
|
||
layout="prev, pager, next"
|
||
@current-change="(val: number) => { customerPage = val; loadCustomerList() }"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="customerDialogVisible = false">取消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!selectedCustomer"
|
||
@click="applyCustomerToPatient"
|
||
>
|
||
确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ═══ 输注方案弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="planDialogVisible"
|
||
:title="planDialogMode === 'add' ? '新增输注方案' : '编辑输注方案'"
|
||
width="640px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
@close="planFormRef?.resetFields()"
|
||
>
|
||
<div class="dialog-form-layout">
|
||
<el-form
|
||
ref="planFormRef"
|
||
:model="planForm"
|
||
:rules="planRules"
|
||
label-position="top"
|
||
class="dialog-form"
|
||
>
|
||
<div class="form-grid-2">
|
||
<el-form-item label="细胞类型" prop="cellType">
|
||
<el-input v-model="planForm.cellType" placeholder="请输入细胞类型" />
|
||
</el-form-item>
|
||
<el-form-item label="规格" prop="specText">
|
||
<el-input v-model="planForm.specText" placeholder="请输入规格" />
|
||
</el-form-item>
|
||
<el-form-item label="总疗程数" prop="totalCourseCount">
|
||
<el-input-number
|
||
v-model="planForm.totalCourseCount"
|
||
:min="1"
|
||
:max="999"
|
||
controls-position="right"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="疗程间隔" prop="courseInterval">
|
||
<el-input v-model="planForm.courseInterval" placeholder="请输入疗程间隔" />
|
||
</el-form-item>
|
||
<el-form-item label="输注途径" prop="infusionRoute">
|
||
<el-input v-model="planForm.infusionRoute" placeholder="请输入输注途径" />
|
||
</el-form-item>
|
||
<el-form-item label="开始日期" prop="infusionStartDate">
|
||
<el-date-picker
|
||
v-model="planForm.infusionStartDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择开始日期"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="planDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="planSaving" @click="handleSavePlan">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ═══ 输注计划编辑弹窗 ═══ -->
|
||
<el-dialog
|
||
v-model="scheduleDialogVisible"
|
||
:title="scheduleDialogMode === 'add' ? '添加输注计划' : '编辑输注计划'"
|
||
width="500px"
|
||
destroy-on-close
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="dialog-form-layout">
|
||
<el-form
|
||
ref="scheduleFormRef"
|
||
:model="scheduleForm"
|
||
label-position="top"
|
||
class="dialog-form"
|
||
>
|
||
<el-form-item label="输注时间" prop="infusionTime">
|
||
<el-date-picker
|
||
v-model="scheduleForm.infusionTime"
|
||
type="datetime"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
placeholder="请选择输注时间"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input
|
||
v-model="scheduleForm.remark"
|
||
type="textarea"
|
||
placeholder="请输入备注"
|
||
:rows="3"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="附件">
|
||
<div class="attachment-upload-row">
|
||
<div
|
||
v-for="(file, idx) in scheduleFileList"
|
||
:key="idx"
|
||
class="attachment-card"
|
||
>
|
||
<el-icon class="attachment-icon"><Document /></el-icon>
|
||
<span class="attachment-name" :title="file.name">{{ file.name }}</span>
|
||
<el-button
|
||
link
|
||
type="danger"
|
||
size="small"
|
||
@click="removeScheduleFile(idx)"
|
||
>
|
||
<el-icon><Delete /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
<el-upload
|
||
:show-file-list="false"
|
||
:auto-upload="false"
|
||
:on-change="handleScheduleFileChange"
|
||
class="attachment-trigger"
|
||
>
|
||
<div class="attachment-trigger-card">
|
||
<el-icon size="20"><Plus /></el-icon>
|
||
<span>上传附件</span>
|
||
</div>
|
||
</el-upload>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="scheduleDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="scheduleSaving" @click="handleSaveSchedule">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||
import {
|
||
ArrowLeft,
|
||
Plus,
|
||
Search,
|
||
User,
|
||
Document,
|
||
Delete,
|
||
} from '@element-plus/icons-vue'
|
||
import {
|
||
fetchPilotApplicationDetail,
|
||
startPilotApplication,
|
||
completePilotApplication,
|
||
voidPilotApplication,
|
||
fetchPilotPatient,
|
||
savePilotPatient,
|
||
fetchPilotInfusionPlanList,
|
||
createPilotInfusionPlan,
|
||
updatePilotInfusionPlan,
|
||
deletePilotInfusionPlan,
|
||
fetchPilotInfusionScheduleList,
|
||
autoGenerateSchedules,
|
||
updatePilotInfusionSchedule,
|
||
deletePilotInfusionSchedule,
|
||
markScheduleComplete,
|
||
fetchCustomerList,
|
||
fetchPilotCheckupDetail,
|
||
savePilotCheckup,
|
||
markPilotCheckupChecked,
|
||
settlePilotCheckupFee,
|
||
fetchPilotCheckupReserveItems,
|
||
savePilotCheckupReserveItems,
|
||
deletePilotCheckupReserveItem,
|
||
fetchPilotCheckupActualItems,
|
||
savePilotCheckupActualItems,
|
||
deletePilotCheckupActualItem,
|
||
fetchPilotDeliveryListByApplication,
|
||
createPilotDelivery,
|
||
deletePilotDelivery,
|
||
fetchPilotDeliveryMaterials,
|
||
savePilotDeliveryMaterials,
|
||
fetchPilotDeliveryMethod,
|
||
savePilotDeliveryMethod,
|
||
createOutboundProductionWorkOrder,
|
||
createOutboundQcWorkOrder,
|
||
createReleaseReview,
|
||
fetchReleaseReviewList,
|
||
updateReleaseReview,
|
||
deleteReleaseReview,
|
||
createInfusionConfirm,
|
||
fetchInfusionConfirmList,
|
||
confirmInfusion,
|
||
updateInfusionConfirm,
|
||
deleteInfusionConfirm,
|
||
} from '@/api/admin'
|
||
import { fetchUploadAttachment } from '@/api/business'
|
||
import {
|
||
applicationStatusLabel,
|
||
applicationStatusTagType,
|
||
checkupStatusLabel,
|
||
checkupStatusTagType,
|
||
feeStatusLabel,
|
||
feeStatusTagType,
|
||
deliveryModeLabel,
|
||
scheduleStatusLabel,
|
||
scheduleStatusTagType,
|
||
releaseReviewConclusionLabel,
|
||
releaseReviewConclusionTagType,
|
||
} from '@/utils/status-tag-registry'
|
||
|
||
defineOptions({ name: 'PilotApplicationDetail' })
|
||
|
||
// ── 工具函数 ──────────────────────────────────────────────────────────
|
||
const displayValue = (v: any): string => {
|
||
if (v == null || v === '' || v === undefined) return '-'
|
||
return String(v)
|
||
}
|
||
|
||
const unwrapApiList = (res: any): any[] => {
|
||
const d = res?.data ?? res
|
||
if (Array.isArray(d)) return d
|
||
return d?.list ?? d?.records ?? []
|
||
}
|
||
|
||
// ── 路由 ──────────────────────────────────────────────────────────────
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const applicationId = computed(() => String(route.query.id || ''))
|
||
|
||
// ── 数据 ──────────────────────────────────────────────────────────────
|
||
const activeTab = ref('basic')
|
||
watch(activeTab, (name) => {
|
||
if (name === 'releaseReview') loadReleaseReviews()
|
||
if (name === 'infusionConfirm') loadInfusionConfirms()
|
||
})
|
||
const applicationDetail = ref<any>({})
|
||
|
||
// ── 加载申请单详情 ────────────────────────────────────────────────────
|
||
const loadApplicationDetail = async () => {
|
||
if (!applicationId.value) return
|
||
try {
|
||
const res: any = await fetchPilotApplicationDetail({ id: applicationId.value })
|
||
const data = res?.data ?? res
|
||
applicationDetail.value = data || {}
|
||
} catch (e) {
|
||
console.error('加载申请单详情失败:', e)
|
||
}
|
||
}
|
||
|
||
// ── 状态操作 ──────────────────────────────────────────────────────────
|
||
const handleStart = async () => {
|
||
try {
|
||
await ElMessageBox.confirm('确定开始该申请单吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await startPilotApplication({ id: applicationId.value })
|
||
ElMessage.success('操作成功')
|
||
loadApplicationDetail()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleComplete = async () => {
|
||
try {
|
||
await ElMessageBox.confirm('确定完成该申请单吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await completePilotApplication({ id: applicationId.value })
|
||
ElMessage.success('操作成功')
|
||
loadApplicationDetail()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleVoid = async () => {
|
||
try {
|
||
await ElMessageBox.confirm('确定作废该申请单吗?作废后不可恢复。', '提示', {
|
||
confirmButtonText: '确定作废',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
await voidPilotApplication({ id: applicationId.value })
|
||
ElMessage.success('操作成功')
|
||
loadApplicationDetail()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// 患者信息
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
const patientFormRef = ref<FormInstance>()
|
||
const patientSaving = ref(false)
|
||
|
||
interface DiagnosisFile {
|
||
name: string
|
||
url: string
|
||
}
|
||
|
||
const diagnosisFileList = ref<DiagnosisFile[]>([])
|
||
|
||
const initPatientForm = () => ({
|
||
customerId: '' as string,
|
||
patientName: '' as string,
|
||
sex: '' as string,
|
||
age: null as number | null,
|
||
idCardNo: '' as string,
|
||
phoneNo: '' as string,
|
||
emergencyContact: '' as string,
|
||
emergencyPhone: '' as string,
|
||
clinicalDiagnosis: '' as string,
|
||
diagnosisAttachments: '' as string,
|
||
needCheckup: 1 as number,
|
||
})
|
||
|
||
const patientForm = reactive(initPatientForm())
|
||
|
||
const loadPatient = async () => {
|
||
if (!applicationId.value) return
|
||
try {
|
||
const res: any = await fetchPilotPatient({ pilotApplicationId: applicationId.value })
|
||
const data = res?.data ?? res
|
||
if (data && Object.keys(data).length > 0) {
|
||
patientForm.customerId = data.customerId || ''
|
||
patientForm.patientName = data.patientName || ''
|
||
patientForm.sex = data.sex || ''
|
||
patientForm.age = data.age ?? null
|
||
patientForm.idCardNo = data.idCardNo || ''
|
||
patientForm.phoneNo = data.phoneNo || ''
|
||
patientForm.emergencyContact = data.emergencyContact || ''
|
||
patientForm.emergencyPhone = data.emergencyPhone || ''
|
||
patientForm.clinicalDiagnosis = data.clinicalDiagnosis || ''
|
||
patientForm.diagnosisAttachments = data.diagnosisAttachments || ''
|
||
patientForm.needCheckup = data.needCheckup ?? 1
|
||
// 解析附件
|
||
diagnosisFileList.value = parseAttachmentUrls(patientForm.diagnosisAttachments)
|
||
}
|
||
} catch (e) {
|
||
console.error('加载患者信息失败:', e)
|
||
}
|
||
}
|
||
|
||
const parseAttachmentUrls = (urls: string): DiagnosisFile[] => {
|
||
if (!urls) return []
|
||
return urls.split(',').filter(Boolean).map((url) => {
|
||
const parts = url.split('/')
|
||
const name = decodeURIComponent(parts[parts.length - 1] || '附件')
|
||
return { name, url }
|
||
})
|
||
}
|
||
|
||
const handleDiagnosisFileChange = async (uploadFile: any) => {
|
||
if (!uploadFile?.raw) return
|
||
try {
|
||
const formData = new FormData()
|
||
formData.append('file', uploadFile.raw)
|
||
const res: any = await fetchUploadAttachment(formData)
|
||
const fileUrl = res?.data?.fileUrl || res?.fileUrl || res?.data?.url || ''
|
||
if (fileUrl) {
|
||
diagnosisFileList.value.push({ name: uploadFile.name, url: fileUrl })
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '上传失败')
|
||
}
|
||
}
|
||
|
||
const removeDiagnosisFile = (idx: number) => {
|
||
diagnosisFileList.value.splice(idx, 1)
|
||
}
|
||
|
||
const handleSavePatient = async () => {
|
||
patientSaving.value = true
|
||
try {
|
||
const attachments = diagnosisFileList.value.map((f) => f.url).join(',')
|
||
await savePilotPatient({
|
||
pilotApplicationId: applicationId.value,
|
||
customerId: patientForm.customerId || undefined,
|
||
patientName: patientForm.patientName,
|
||
sex: patientForm.sex || undefined,
|
||
age: patientForm.age ?? undefined,
|
||
idCardNo: patientForm.idCardNo || undefined,
|
||
phoneNo: patientForm.phoneNo || undefined,
|
||
emergencyContact: patientForm.emergencyContact || undefined,
|
||
emergencyPhone: patientForm.emergencyPhone || undefined,
|
||
clinicalDiagnosis: patientForm.clinicalDiagnosis || undefined,
|
||
diagnosisAttachments: attachments || undefined,
|
||
needCheckup: patientForm.needCheckup,
|
||
})
|
||
ElMessage.success('保存成功')
|
||
loadApplicationDetail()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '保存失败')
|
||
} finally {
|
||
patientSaving.value = false
|
||
}
|
||
}
|
||
|
||
// ── 客户选择弹窗 ──────────────────────────────────────────────────────
|
||
const customerDialogVisible = ref(false)
|
||
const customerLoading = ref(false)
|
||
const customerList = ref<any[]>([])
|
||
const customerPage = ref(1)
|
||
const customerPageSize = ref(10)
|
||
const customerTotal = ref(0)
|
||
const selectedCustomer = ref<any>(null)
|
||
const customerSearch = reactive({ keyword: '', phoneNo: '' })
|
||
|
||
const openCustomerDialog = () => {
|
||
customerSearch.keyword = ''
|
||
customerSearch.phoneNo = ''
|
||
customerPage.value = 1
|
||
selectedCustomer.value = null
|
||
customerDialogVisible.value = true
|
||
loadCustomerList()
|
||
}
|
||
|
||
const loadCustomerList = async () => {
|
||
customerLoading.value = true
|
||
try {
|
||
const keyword = customerSearch.keyword || customerSearch.phoneNo || undefined
|
||
const res: any = await fetchCustomerList({
|
||
page: customerPage.value,
|
||
pageSize: customerPageSize.value,
|
||
keyword,
|
||
})
|
||
const data = res?.data ?? res
|
||
customerList.value = data?.list ?? data?.records ?? []
|
||
customerTotal.value = data?.total ?? 0
|
||
} catch {
|
||
customerList.value = []
|
||
} finally {
|
||
customerLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleCustomerSelect = (row: any) => {
|
||
selectedCustomer.value = row
|
||
}
|
||
|
||
const applyCustomerToPatient = () => {
|
||
const c = selectedCustomer.value
|
||
if (!c) return
|
||
patientForm.customerId = c.id || ''
|
||
patientForm.patientName = c.customerName || c.name || ''
|
||
patientForm.sex = c.sex || ''
|
||
patientForm.age = c.age ?? null
|
||
patientForm.idCardNo = c.idCardNo || ''
|
||
patientForm.phoneNo = c.phoneNo || ''
|
||
customerDialogVisible.value = false
|
||
ElMessage.success('已填充客户信息')
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// 输注方案
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
const planList = ref<any[]>([])
|
||
const planLoading = ref(false)
|
||
const scheduleMap = ref<Record<string, any[]>>({})
|
||
const scheduleLoadingMap = ref<Record<string, boolean>>({})
|
||
|
||
const loadPlans = async () => {
|
||
if (!applicationId.value) return
|
||
planLoading.value = true
|
||
try {
|
||
const res: any = await fetchPilotInfusionPlanList({ pilotApplicationId: applicationId.value })
|
||
planList.value = unwrapApiList(res)
|
||
// 为每个方案加载计划
|
||
for (const plan of planList.value) {
|
||
await loadSchedulesForPlan(plan.id)
|
||
}
|
||
} catch (e) {
|
||
console.error('加载输注方案失败:', e)
|
||
} finally {
|
||
planLoading.value = false
|
||
}
|
||
}
|
||
|
||
const loadSchedulesForPlan = async (planId: string) => {
|
||
scheduleLoadingMap.value[planId] = true
|
||
try {
|
||
const res: any = await fetchPilotInfusionScheduleList({ infusionPlanId: planId })
|
||
scheduleMap.value[planId] = unwrapApiList(res)
|
||
} catch {
|
||
scheduleMap.value[planId] = []
|
||
} finally {
|
||
scheduleLoadingMap.value[planId] = false
|
||
}
|
||
}
|
||
|
||
const getScheduleCount = (planId: string): number => {
|
||
return (scheduleMap.value[planId] || []).length
|
||
}
|
||
|
||
const getCompletedCount = (planId: string): number => {
|
||
return (scheduleMap.value[planId] || []).filter(
|
||
(s: any) => s.scheduleStatus === 'ywc',
|
||
).length
|
||
}
|
||
|
||
// ── 方案弹窗 ──────────────────────────────────────────────────────────
|
||
const planDialogVisible = ref(false)
|
||
const planDialogMode = ref<'add' | 'edit'>('add')
|
||
const planSaving = ref(false)
|
||
const planFormRef = ref<FormInstance>()
|
||
const currentEditPlanId = ref('')
|
||
|
||
const initPlanForm = () => ({
|
||
cellType: '' as string,
|
||
specText: '' as string,
|
||
totalCourseCount: 1 as number,
|
||
courseInterval: '' as string,
|
||
infusionRoute: '' as string,
|
||
infusionStartDate: '' as string,
|
||
})
|
||
|
||
const planForm = reactive(initPlanForm())
|
||
|
||
const planRules: FormRules = {
|
||
cellType: [{ required: true, message: '请输入细胞类型', trigger: 'blur' }],
|
||
totalCourseCount: [{ required: true, message: '请输入总疗程数', trigger: 'blur' }],
|
||
}
|
||
|
||
const openPlanDialog = (row?: any) => {
|
||
if (row) {
|
||
planDialogMode.value = 'edit'
|
||
currentEditPlanId.value = row.id
|
||
planForm.cellType = row.cellType || ''
|
||
planForm.specText = row.specText || ''
|
||
planForm.totalCourseCount = row.totalCourseCount ?? 1
|
||
planForm.courseInterval = row.courseInterval || ''
|
||
planForm.infusionRoute = row.infusionRoute || ''
|
||
planForm.infusionStartDate = row.infusionStartDate || ''
|
||
} else {
|
||
planDialogMode.value = 'add'
|
||
currentEditPlanId.value = ''
|
||
Object.assign(planForm, initPlanForm())
|
||
}
|
||
planDialogVisible.value = true
|
||
}
|
||
|
||
const handleSavePlan = async () => {
|
||
try {
|
||
await planFormRef.value?.validate()
|
||
} catch {
|
||
return
|
||
}
|
||
|
||
planSaving.value = true
|
||
try {
|
||
const payload = {
|
||
pilotApplicationId: applicationId.value,
|
||
cellType: planForm.cellType,
|
||
specText: planForm.specText || undefined,
|
||
totalCourseCount: planForm.totalCourseCount,
|
||
courseInterval: planForm.courseInterval || undefined,
|
||
infusionRoute: planForm.infusionRoute || undefined,
|
||
infusionStartDate: planForm.infusionStartDate || undefined,
|
||
}
|
||
|
||
let planId = ''
|
||
if (planDialogMode.value === 'add') {
|
||
const res: any = await createPilotInfusionPlan(payload)
|
||
const data = res?.data ?? res
|
||
planId = data?.id || data
|
||
ElMessage.success('方案创建成功')
|
||
} else {
|
||
await updatePilotInfusionPlan({
|
||
id: currentEditPlanId.value,
|
||
...payload,
|
||
})
|
||
planId = currentEditPlanId.value
|
||
ElMessage.success('方案更新成功')
|
||
}
|
||
|
||
planDialogVisible.value = false
|
||
await loadPlans()
|
||
|
||
// 新增方案后提示是否自动生成计划
|
||
if (planDialogMode.value === 'add' && planId && planForm.totalCourseCount > 0) {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`是否立即生成 ${planForm.totalCourseCount} 条输注计划?`,
|
||
'提示',
|
||
{
|
||
confirmButtonText: '立即生成',
|
||
cancelButtonText: '稍后再说',
|
||
type: 'info',
|
||
},
|
||
)
|
||
await autoGenerateSchedules({ infusionPlanId: planId })
|
||
ElMessage.success('计划已生成')
|
||
await loadPlans()
|
||
} catch {
|
||
// 用户取消
|
||
}
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '操作失败')
|
||
} finally {
|
||
planSaving.value = false
|
||
}
|
||
}
|
||
|
||
const handleDeletePlan = async (row: any) => {
|
||
try {
|
||
await deletePilotInfusionPlan({ id: row.id })
|
||
ElMessage.success('删除成功')
|
||
loadPlans()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
// ── 计划弹窗 ──────────────────────────────────────────────────────────
|
||
const scheduleDialogVisible = ref(false)
|
||
const scheduleDialogMode = ref<'add' | 'edit'>('add')
|
||
const scheduleSaving = ref(false)
|
||
const scheduleFormRef = ref<FormInstance>()
|
||
const currentEditScheduleId = ref('')
|
||
const currentSchedulePlanId = ref('')
|
||
|
||
interface ScheduleFile {
|
||
name: string
|
||
url: string
|
||
}
|
||
|
||
const scheduleFileList = ref<ScheduleFile[]>([])
|
||
|
||
const scheduleForm = reactive({
|
||
infusionTime: '' as string,
|
||
remark: '' as string,
|
||
attachments: '' as string,
|
||
})
|
||
|
||
const openScheduleDialog = (row: any | null, plan: any) => {
|
||
currentSchedulePlanId.value = plan.id
|
||
if (row) {
|
||
scheduleDialogMode.value = 'edit'
|
||
currentEditScheduleId.value = row.id
|
||
scheduleForm.infusionTime = row.infusionTime || ''
|
||
scheduleForm.remark = row.remark || ''
|
||
scheduleForm.attachments = row.attachments || ''
|
||
scheduleFileList.value = parseAttachmentUrls(scheduleForm.attachments)
|
||
} else {
|
||
scheduleDialogMode.value = 'add'
|
||
currentEditScheduleId.value = ''
|
||
scheduleForm.infusionTime = ''
|
||
scheduleForm.remark = ''
|
||
scheduleForm.attachments = ''
|
||
scheduleFileList.value = []
|
||
}
|
||
scheduleDialogVisible.value = true
|
||
}
|
||
|
||
const handleScheduleFileChange = async (uploadFile: any) => {
|
||
if (!uploadFile?.raw) return
|
||
try {
|
||
const formData = new FormData()
|
||
formData.append('file', uploadFile.raw)
|
||
const res: any = await fetchUploadAttachment(formData)
|
||
const fileUrl = res?.data?.fileUrl || res?.fileUrl || res?.data?.url || ''
|
||
if (fileUrl) {
|
||
scheduleFileList.value.push({ name: uploadFile.name, url: fileUrl })
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '上传失败')
|
||
}
|
||
}
|
||
|
||
const removeScheduleFile = (idx: number) => {
|
||
scheduleFileList.value.splice(idx, 1)
|
||
}
|
||
|
||
const handleSaveSchedule = async () => {
|
||
scheduleSaving.value = true
|
||
try {
|
||
const attachments = scheduleFileList.value.map((f) => f.url).join(',')
|
||
if (scheduleDialogMode.value === 'add') {
|
||
// 手动添加:生成下一条 orderNum
|
||
const existing = scheduleMap.value[currentSchedulePlanId.value] || []
|
||
const nextOrderNum = existing.length + 1
|
||
await autoGenerateSchedules({
|
||
infusionPlanId: currentSchedulePlanId.value,
|
||
manualSchedules: [{
|
||
orderNum: nextOrderNum,
|
||
infusionTime: scheduleForm.infusionTime || undefined,
|
||
remark: scheduleForm.remark || undefined,
|
||
attachments: attachments || undefined,
|
||
}],
|
||
})
|
||
ElMessage.success('添加成功')
|
||
} else {
|
||
await updatePilotInfusionSchedule({
|
||
id: currentEditScheduleId.value,
|
||
infusionTime: scheduleForm.infusionTime || undefined,
|
||
remark: scheduleForm.remark || undefined,
|
||
attachments: attachments || undefined,
|
||
})
|
||
ElMessage.success('更新成功')
|
||
}
|
||
scheduleDialogVisible.value = false
|
||
await loadSchedulesForPlan(currentSchedulePlanId.value)
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '操作失败')
|
||
} finally {
|
||
scheduleSaving.value = false
|
||
}
|
||
}
|
||
|
||
const handleDeleteSchedule = async (row: any) => {
|
||
try {
|
||
await deletePilotInfusionSchedule({ id: row.id })
|
||
ElMessage.success('删除成功')
|
||
// 刷新对应方案的计划
|
||
const planId = Object.entries(scheduleMap.value).find(([, schedules]) =>
|
||
schedules.some((s: any) => s.id === row.id),
|
||
)?.[0]
|
||
if (planId) await loadSchedulesForPlan(planId)
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
const handleMarkScheduleComplete = async (row: any, plan: any) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定将该计划标记为已完成吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await markScheduleComplete({ id: row.id })
|
||
ElMessage.success('已标记完成')
|
||
await loadSchedulesForPlan(plan.id)
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleCreateOutboundProduction = async (row: any, plan: any) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定为该计划创建出库生产工单吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await createOutboundProductionWorkOrder({ scheduleId: row.id })
|
||
ElMessage.success('创建出库生产工单成功')
|
||
await loadSchedulesForPlan(plan.id)
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleCreateOutboundQc = async (row: any) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定为该计划创建出库质检工单吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await createOutboundQcWorkOrder({ scheduleId: row.id })
|
||
ElMessage.success('创建出库质检工单成功')
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// 放行审核
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
const releaseReviewList = ref<any[]>([])
|
||
const releaseReviewLoading = ref(false)
|
||
|
||
const loadReleaseReviews = async () => {
|
||
if (!applicationId.value) return
|
||
releaseReviewLoading.value = true
|
||
try {
|
||
const res: any = await fetchReleaseReviewList({ scheduleId: applicationId.value })
|
||
releaseReviewList.value = unwrapApiList(res)
|
||
} catch {
|
||
releaseReviewList.value = []
|
||
} finally {
|
||
releaseReviewLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleReviewConclusion = async (row: any, conclusion: string) => {
|
||
try {
|
||
const msg = conclusion === 'tg' ? '通过' : '不通过'
|
||
await ElMessageBox.confirm(`确定将该审核标记为「${msg}」吗?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await updateReleaseReview({ id: row.id, reviewConclusion: conclusion })
|
||
ElMessage.success('审核完成')
|
||
await loadReleaseReviews()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleDeleteReleaseReview = async (row: any) => {
|
||
try {
|
||
await deleteReleaseReview({ id: row.id })
|
||
ElMessage.success('删除成功')
|
||
await loadReleaseReviews()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// 应用确认
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
const infusionConfirmList = ref<any[]>([])
|
||
const infusionConfirmLoading = ref(false)
|
||
|
||
const loadInfusionConfirms = async () => {
|
||
if (!applicationId.value) return
|
||
infusionConfirmLoading.value = true
|
||
try {
|
||
const res: any = await fetchInfusionConfirmList({ applicationId: applicationId.value })
|
||
infusionConfirmList.value = unwrapApiList(res)
|
||
} catch {
|
||
infusionConfirmList.value = []
|
||
} finally {
|
||
infusionConfirmLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleConfirmInfusion = async (row: any) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定标记为已确认吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await confirmInfusion({ id: row.id })
|
||
ElMessage.success('确认成功')
|
||
await loadInfusionConfirms()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleEditConfirmDesc = async (row: any) => {
|
||
try {
|
||
const { value } = await ElMessageBox.prompt('请输入确认说明', '编辑确认说明', {
|
||
inputValue: row.confirmDesc || '',
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
})
|
||
await updateInfusionConfirm({ id: row.id, confirmDesc: value })
|
||
ElMessage.success('更新成功')
|
||
await loadInfusionConfirms()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleDeleteInfusionConfirm = async (row: any) => {
|
||
try {
|
||
await deleteInfusionConfirm({ id: row.id })
|
||
ElMessage.success('删除成功')
|
||
await loadInfusionConfirms()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// 体检信息
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
const checkupData = ref<any>(null)
|
||
const checkupSaving = ref(false)
|
||
const checkupDialogVisible = ref(false)
|
||
const checkupFormRef = ref<FormInstance>()
|
||
|
||
interface CheckupFile {
|
||
name: string
|
||
url: string
|
||
}
|
||
|
||
const checkupReportFiles = ref<CheckupFile[]>([])
|
||
const checkupDialogFileList = ref<CheckupFile[]>([])
|
||
|
||
const initCheckupForm = () => ({
|
||
reserveTime: '' as string,
|
||
reserveFee: null as number | null,
|
||
actualCheckupTime: '' as string,
|
||
actualCheckupFee: null as number | null,
|
||
checkupRemark: '' as string,
|
||
reportAttachments: '' as string,
|
||
})
|
||
|
||
const checkupForm = reactive(initCheckupForm())
|
||
|
||
const loadCheckup = async () => {
|
||
if (!applicationId.value) return
|
||
try {
|
||
const res: any = await fetchPilotCheckupDetail({ pilotApplicationId: applicationId.value })
|
||
const data = res?.data ?? res
|
||
if (data && Object.keys(data).length > 0 && data.id) {
|
||
checkupData.value = data
|
||
checkupReportFiles.value = parseAttachmentUrls(data.reportAttachments || '')
|
||
} else {
|
||
checkupData.value = null
|
||
checkupReportFiles.value = []
|
||
}
|
||
} catch {
|
||
checkupData.value = null
|
||
checkupReportFiles.value = []
|
||
}
|
||
}
|
||
|
||
const openCheckupDialog = () => {
|
||
if (checkupData.value) {
|
||
checkupForm.reserveTime = checkupData.value.reserveTime || ''
|
||
checkupForm.reserveFee = checkupData.value.reserveFee ?? null
|
||
checkupForm.actualCheckupTime = checkupData.value.actualCheckupTime || ''
|
||
checkupForm.actualCheckupFee = checkupData.value.actualCheckupFee ?? null
|
||
checkupForm.checkupRemark = checkupData.value.checkupRemark || ''
|
||
checkupForm.reportAttachments = checkupData.value.reportAttachments || ''
|
||
checkupDialogFileList.value = parseAttachmentUrls(checkupForm.reportAttachments)
|
||
} else {
|
||
Object.assign(checkupForm, initCheckupForm())
|
||
checkupDialogFileList.value = []
|
||
}
|
||
checkupDialogVisible.value = true
|
||
}
|
||
|
||
const handleCheckupDialogFileChange = async (uploadFile: any) => {
|
||
if (!uploadFile?.raw) return
|
||
try {
|
||
const formData = new FormData()
|
||
formData.append('file', uploadFile.raw)
|
||
const res: any = await fetchUploadAttachment(formData)
|
||
const fileUrl = res?.data?.fileUrl || res?.fileUrl || res?.data?.url || ''
|
||
if (fileUrl) {
|
||
checkupDialogFileList.value.push({ name: uploadFile.name, url: fileUrl })
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '上传失败')
|
||
}
|
||
}
|
||
|
||
const removeCheckupDialogFile = (idx: number) => {
|
||
checkupDialogFileList.value.splice(idx, 1)
|
||
}
|
||
|
||
const handleSaveCheckup = async () => {
|
||
checkupSaving.value = true
|
||
try {
|
||
const attachments = checkupDialogFileList.value.map((f) => f.url).join(',')
|
||
await savePilotCheckup({
|
||
applicationId: applicationId.value,
|
||
reserveTime: checkupForm.reserveTime || undefined,
|
||
reserveFee: checkupForm.reserveFee ?? undefined,
|
||
actualCheckupTime: checkupForm.actualCheckupTime || undefined,
|
||
actualCheckupFee: checkupForm.actualCheckupFee ?? undefined,
|
||
checkupRemark: checkupForm.checkupRemark || undefined,
|
||
reportAttachments: attachments || undefined,
|
||
})
|
||
ElMessage.success('保存成功')
|
||
checkupDialogVisible.value = false
|
||
await loadCheckup()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '保存失败')
|
||
} finally {
|
||
checkupSaving.value = false
|
||
}
|
||
}
|
||
|
||
const handleMarkChecked = async () => {
|
||
try {
|
||
await ElMessageBox.confirm('确定标记为已体检吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await markPilotCheckupChecked({ id: checkupData.value.id })
|
||
ElMessage.success('已标记为已体检')
|
||
await loadCheckup()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
const handleSettleFee = async () => {
|
||
try {
|
||
await ElMessageBox.confirm('确定结算费用吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
})
|
||
await settlePilotCheckupFee({ id: checkupData.value.id })
|
||
ElMessage.success('费用已结算')
|
||
await loadCheckup()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '操作失败')
|
||
}
|
||
}
|
||
|
||
// ── 预约项目 ──────────────────────────────────────────────────────────
|
||
const reserveItems = ref<any[]>([])
|
||
const reserveItemsLoading = ref(false)
|
||
const reserveItemDialogVisible = ref(false)
|
||
const reserveItemSaving = ref(false)
|
||
const reserveItemForm = reactive({ itemName: '', itemFee: null as number | null })
|
||
|
||
const loadReserveItems = async () => {
|
||
if (!checkupData.value?.id) return
|
||
reserveItemsLoading.value = true
|
||
try {
|
||
const res: any = await fetchPilotCheckupReserveItems({ pilotCheckupId: checkupData.value.id })
|
||
reserveItems.value = unwrapApiList(res)
|
||
} catch {
|
||
reserveItems.value = []
|
||
} finally {
|
||
reserveItemsLoading.value = false
|
||
}
|
||
}
|
||
|
||
const openReserveItemDialog = () => {
|
||
reserveItemForm.itemName = ''
|
||
reserveItemForm.itemFee = null
|
||
reserveItemDialogVisible.value = true
|
||
}
|
||
|
||
const handleSaveReserveItem = async () => {
|
||
if (!reserveItemForm.itemName) {
|
||
ElMessage.warning('请输入项目名称')
|
||
return
|
||
}
|
||
reserveItemSaving.value = true
|
||
try {
|
||
await savePilotCheckupReserveItems({
|
||
pilotCheckupId: checkupData.value.id,
|
||
items: [{ itemName: reserveItemForm.itemName, itemFee: reserveItemForm.itemFee ?? undefined }],
|
||
})
|
||
ElMessage.success('添加成功')
|
||
reserveItemDialogVisible.value = false
|
||
await loadReserveItems()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '添加失败')
|
||
} finally {
|
||
reserveItemSaving.value = false
|
||
}
|
||
}
|
||
|
||
const handleDeleteReserveItem = async (row: any) => {
|
||
try {
|
||
await deletePilotCheckupReserveItem({ id: row.id })
|
||
ElMessage.success('删除成功')
|
||
await loadReserveItems()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
// ── 实际项目 ──────────────────────────────────────────────────────────
|
||
const actualItems = ref<any[]>([])
|
||
const actualItemsLoading = ref(false)
|
||
const actualItemDialogVisible = ref(false)
|
||
const actualItemSaving = ref(false)
|
||
const actualItemForm = reactive({ itemName: '', itemFee: null as number | null })
|
||
|
||
const loadActualItems = async () => {
|
||
if (!checkupData.value?.id) return
|
||
actualItemsLoading.value = true
|
||
try {
|
||
const res: any = await fetchPilotCheckupActualItems({ pilotCheckupId: checkupData.value.id })
|
||
actualItems.value = unwrapApiList(res)
|
||
} catch {
|
||
actualItems.value = []
|
||
} finally {
|
||
actualItemsLoading.value = false
|
||
}
|
||
}
|
||
|
||
const openActualItemDialog = () => {
|
||
actualItemForm.itemName = ''
|
||
actualItemForm.itemFee = null
|
||
actualItemDialogVisible.value = true
|
||
}
|
||
|
||
const handleSaveActualItem = async () => {
|
||
if (!actualItemForm.itemName) {
|
||
ElMessage.warning('请输入项目名称')
|
||
return
|
||
}
|
||
actualItemSaving.value = true
|
||
try {
|
||
await savePilotCheckupActualItems({
|
||
pilotCheckupId: checkupData.value.id,
|
||
items: [{ itemName: actualItemForm.itemName, itemFee: actualItemForm.itemFee ?? undefined }],
|
||
})
|
||
ElMessage.success('添加成功')
|
||
actualItemDialogVisible.value = false
|
||
await loadActualItems()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '添加失败')
|
||
} finally {
|
||
actualItemSaving.value = false
|
||
}
|
||
}
|
||
|
||
const handleDeleteActualItem = async (row: any) => {
|
||
try {
|
||
await deletePilotCheckupActualItem({ id: row.id })
|
||
ElMessage.success('删除成功')
|
||
await loadActualItems()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
// 交付信息
|
||
// ═══════════════════════════════════════════════════════════════════════
|
||
const deliveryList = ref<any[]>([])
|
||
const deliveryLoading = ref(false)
|
||
const deliveryDetailDialogVisible = ref(false)
|
||
const deliveryDetailTab = ref('materials')
|
||
const currentDeliveryRow = ref<any>(null)
|
||
|
||
// 物料
|
||
const deliveryMaterials = ref<any[]>([])
|
||
const deliveryMaterialsLoading = ref(false)
|
||
const addMaterialDialogVisible = ref(false)
|
||
const addMaterialSaving = ref(false)
|
||
const addMaterialForm = reactive({
|
||
materialName: '',
|
||
quantity: 1 as number,
|
||
unitName: '',
|
||
remark: '',
|
||
})
|
||
|
||
// 交付方式
|
||
const deliveryMethodSaving = ref(false)
|
||
const deliveryMethodFileList = ref<CheckupFile[]>([])
|
||
const deliveryMethodForm = reactive({
|
||
deliveryMode: '' as string,
|
||
attachments: '' as string,
|
||
shipTime: '' as string,
|
||
handoverTemperature: '' as string,
|
||
pickupPerson: '' as string,
|
||
pickupPhone: '' as string,
|
||
senderName: '' as string,
|
||
expressCompany: '' as string,
|
||
expressNo: '' as string,
|
||
expressFee: null as number | null,
|
||
estimatedArrivalTime: '' as string,
|
||
})
|
||
|
||
const loadDeliveryList = async () => {
|
||
if (!applicationId.value) return
|
||
deliveryLoading.value = true
|
||
try {
|
||
const res: any = await fetchPilotDeliveryListByApplication({ pilotApplicationId: applicationId.value })
|
||
deliveryList.value = unwrapApiList(res)
|
||
} catch {
|
||
deliveryList.value = []
|
||
} finally {
|
||
deliveryLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleCreateDelivery = async (row: any) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
'确认创建交付?创建后将自动扣减渠道费用/份数。',
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
},
|
||
)
|
||
await createPilotDelivery({ infusionScheduleId: row.scheduleId })
|
||
ElMessage.success('交付创建成功')
|
||
await loadDeliveryList()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel') ElMessage.error(e?.message || '创建失败')
|
||
}
|
||
}
|
||
|
||
const handleDeleteDelivery = async (row: any) => {
|
||
try {
|
||
await deletePilotDelivery({ id: row.deliveryId })
|
||
ElMessage.success('删除成功')
|
||
await loadDeliveryList()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
const openDeliveryDetailDialog = async (row: any) => {
|
||
currentDeliveryRow.value = row
|
||
deliveryDetailTab.value = 'materials'
|
||
deliveryDetailDialogVisible.value = true
|
||
await loadDeliveryMaterials()
|
||
await loadDeliveryMethod()
|
||
}
|
||
|
||
const loadDeliveryMaterials = async () => {
|
||
if (!currentDeliveryRow.value?.deliveryId) return
|
||
deliveryMaterialsLoading.value = true
|
||
try {
|
||
const res: any = await fetchPilotDeliveryMaterials({ pilotDeliveryId: currentDeliveryRow.value.deliveryId })
|
||
deliveryMaterials.value = unwrapApiList(res)
|
||
} catch {
|
||
deliveryMaterials.value = []
|
||
} finally {
|
||
deliveryMaterialsLoading.value = false
|
||
}
|
||
}
|
||
|
||
const openAddMaterialDialog = () => {
|
||
addMaterialForm.materialName = ''
|
||
addMaterialForm.quantity = 1
|
||
addMaterialForm.unitName = ''
|
||
addMaterialForm.remark = ''
|
||
addMaterialDialogVisible.value = true
|
||
}
|
||
|
||
const handleSaveMaterial = async () => {
|
||
if (!addMaterialForm.materialName) {
|
||
ElMessage.warning('请输入物料名称')
|
||
return
|
||
}
|
||
addMaterialSaving.value = true
|
||
try {
|
||
await savePilotDeliveryMaterials({
|
||
pilotDeliveryId: currentDeliveryRow.value.deliveryId,
|
||
items: [{
|
||
materialName: addMaterialForm.materialName,
|
||
quantity: addMaterialForm.quantity,
|
||
unitName: addMaterialForm.unitName || undefined,
|
||
remark: addMaterialForm.remark || undefined,
|
||
}],
|
||
})
|
||
ElMessage.success('添加成功')
|
||
addMaterialDialogVisible.value = false
|
||
await loadDeliveryMaterials()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '添加失败')
|
||
} finally {
|
||
addMaterialSaving.value = false
|
||
}
|
||
}
|
||
|
||
const handleDeleteMaterial = async (row: any) => {
|
||
try {
|
||
await savePilotDeliveryMaterials({
|
||
pilotDeliveryId: currentDeliveryRow.value.deliveryId,
|
||
deleteIds: [row.id],
|
||
})
|
||
ElMessage.success('删除成功')
|
||
await loadDeliveryMaterials()
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '删除失败')
|
||
}
|
||
}
|
||
|
||
// ── 交付方式 ──────────────────────────────────────────────────────────
|
||
const loadDeliveryMethod = async () => {
|
||
if (!currentDeliveryRow.value?.deliveryId) return
|
||
try {
|
||
const res: any = await fetchPilotDeliveryMethod({ pilotDeliveryId: currentDeliveryRow.value.deliveryId })
|
||
const data = res?.data ?? res
|
||
if (data && Object.keys(data).length > 0) {
|
||
deliveryMethodForm.deliveryMode = data.deliveryMode || ''
|
||
deliveryMethodForm.attachments = data.attachments || ''
|
||
deliveryMethodForm.shipTime = data.shipTime || ''
|
||
deliveryMethodForm.handoverTemperature = data.handoverTemperature || ''
|
||
deliveryMethodForm.pickupPerson = data.pickupPerson || ''
|
||
deliveryMethodForm.pickupPhone = data.pickupPhone || ''
|
||
deliveryMethodForm.senderName = data.senderName || ''
|
||
deliveryMethodForm.expressCompany = data.expressCompany || ''
|
||
deliveryMethodForm.expressNo = data.expressNo || ''
|
||
deliveryMethodForm.expressFee = data.expressFee ?? null
|
||
deliveryMethodForm.estimatedArrivalTime = data.estimatedArrivalTime || ''
|
||
deliveryMethodFileList.value = parseAttachmentUrls(deliveryMethodForm.attachments)
|
||
}
|
||
} catch {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
const handleDeliveryMethodFileChange = async (uploadFile: any) => {
|
||
if (!uploadFile?.raw) return
|
||
try {
|
||
const formData = new FormData()
|
||
formData.append('file', uploadFile.raw)
|
||
const res: any = await fetchUploadAttachment(formData)
|
||
const fileUrl = res?.data?.fileUrl || res?.fileUrl || res?.data?.url || ''
|
||
if (fileUrl) {
|
||
deliveryMethodFileList.value.push({ name: uploadFile.name, url: fileUrl })
|
||
}
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '上传失败')
|
||
}
|
||
}
|
||
|
||
const removeDeliveryMethodFile = (idx: number) => {
|
||
deliveryMethodFileList.value.splice(idx, 1)
|
||
}
|
||
|
||
const handleSaveDeliveryMethod = async () => {
|
||
deliveryMethodSaving.value = true
|
||
try {
|
||
const attachments = deliveryMethodFileList.value.map((f) => f.url).join(',')
|
||
await savePilotDeliveryMethod({
|
||
pilotDeliveryId: currentDeliveryRow.value.deliveryId,
|
||
deliveryMode: deliveryMethodForm.deliveryMode || undefined,
|
||
attachments: attachments || undefined,
|
||
shipTime: deliveryMethodForm.shipTime || undefined,
|
||
handoverTemperature: deliveryMethodForm.handoverTemperature || undefined,
|
||
pickupPerson: deliveryMethodForm.pickupPerson || undefined,
|
||
pickupPhone: deliveryMethodForm.pickupPhone || undefined,
|
||
senderName: deliveryMethodForm.senderName || undefined,
|
||
expressCompany: deliveryMethodForm.expressCompany || undefined,
|
||
expressNo: deliveryMethodForm.expressNo || undefined,
|
||
expressFee: deliveryMethodForm.expressFee ?? undefined,
|
||
estimatedArrivalTime: deliveryMethodForm.estimatedArrivalTime || undefined,
|
||
})
|
||
ElMessage.success('保存成功')
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.message || '保存失败')
|
||
} finally {
|
||
deliveryMethodSaving.value = false
|
||
}
|
||
}
|
||
|
||
// ── 初始化 ────────────────────────────────────────────────────────────
|
||
onMounted(async () => {
|
||
await loadApplicationDetail()
|
||
await loadPatient()
|
||
await loadPlans()
|
||
await loadCheckup()
|
||
if (checkupData.value?.id) {
|
||
await loadReserveItems()
|
||
await loadActualItems()
|
||
}
|
||
await loadDeliveryList()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.detail-head-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
|
||
.detail-head-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.detail-head-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.detail-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.detail-code {
|
||
font-size: 14px;
|
||
font-weight: 400;
|
||
color: var(--el-text-color-secondary);
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.detail-tabs {
|
||
:deep(.el-tabs__header) {
|
||
margin-bottom: 16px;
|
||
}
|
||
}
|
||
|
||
.detail-section {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.detail-section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.detail-section-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
margin: 0 0 16px;
|
||
padding-left: 10px;
|
||
position: relative;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 3px;
|
||
height: 16px;
|
||
border-radius: 2px;
|
||
background-color: var(--el-color-primary);
|
||
}
|
||
}
|
||
|
||
.detail-section-header .detail-section-title {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.detail-section-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.form-item-hint {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-placeholder);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.attachment-link {
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: border-color 0.2s;
|
||
|
||
&:hover {
|
||
border-color: var(--el-color-primary);
|
||
}
|
||
}
|
||
|
||
.detail-section-footer {
|
||
margin-top: 16px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.expand-content {
|
||
padding: 12px 48px 12px 24px;
|
||
}
|
||
|
||
.expand-footer {
|
||
margin-top: 12px;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
.customer-dialog-content {
|
||
.filter-form {
|
||
margin-bottom: 12px;
|
||
}
|
||
}
|
||
|
||
.attachment-upload-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.attachment-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 6px 10px;
|
||
border: 1px solid var(--el-border-color);
|
||
border-radius: 6px;
|
||
background: var(--el-fill-color-lighter);
|
||
max-width: 200px;
|
||
}
|
||
|
||
.attachment-icon {
|
||
color: var(--el-color-primary);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.attachment-name {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-size: 13px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.attachment-trigger {
|
||
display: inline-flex;
|
||
}
|
||
|
||
.attachment-trigger-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 80px;
|
||
height: 60px;
|
||
border: 1px dashed var(--el-border-color);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
color: var(--el-text-color-secondary);
|
||
font-size: 12px;
|
||
gap: 4px;
|
||
transition: border-color 0.2s;
|
||
|
||
&:hover {
|
||
border-color: var(--el-color-primary);
|
||
color: var(--el-color-primary);
|
||
}
|
||
}
|
||
</style>
|