Files
his/openhis-ui-vue3/src/views/diseaseReportManagement/ReportManagement/index.vue
Ranyunqiao 88d9e19cc5 401
门诊完诊审计日志错误:div_log 表中 pool_id 与 slot_id 存值与设计规范不符
400
门诊医生站点击【完诊】后,triage_queue_item 表 status 字段未按规范更新为 30
393
疾病报告管理-报告卡管理:状态为“审核失败”的报卡操作列缺失“审核”按钮
369
【住院管理】进入护理记录模块报错
361
三测单(体温单)住院第一日显示 1970-01-01,未正确获取入院日期
2026-04-21 11:38:05 +08:00

1461 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="report-management-container">
<!-- 1. 顶部导航栏 -->
<div class="top-navbar">
<div class="navbar-content">
<div class="system-logo">
<span class="cdc-text">CDC</span>
<span class="logo-text">报卡管理</span>
</div>
</div>
</div>
<!-- 2. 报卡管理概览 - 统计卡片 -->
<div class="overview-cards">
<el-row :gutter="16">
<el-col :span="6">
<el-card class="stat-card" shadow="hover" @click="handleStatClick('pending-today')">
<div class="stat-card-border" style="border-left-color: #409eff;"></div>
<div class="stat-content">
<div class="stat-icon" style="background: rgba(64, 158, 255, 0.1)">
<el-icon :size="28" color="#409eff"><Clock /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.pendingToday }}</div>
<div class="stat-label">今日待审核</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" shadow="hover" @click="handleStatClick('failed-month')">
<div class="stat-card-border" style="border-left-color: #f56c6c;"></div>
<div class="stat-content">
<div class="stat-icon" style="background: rgba(245, 108, 108, 0.1)">
<el-icon :size="28" color="#f56c6c"><CircleClose /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.failedMonth }}</div>
<div class="stat-label">本月审核失败</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" shadow="hover" @click="handleStatClick('success-month')">
<div class="stat-card-border" style="border-left-color: #67c23a;"></div>
<div class="stat-content">
<div class="stat-icon" style="background: rgba(103, 194, 58, 0.1)">
<el-icon :size="28" color="#67c23a"><CircleCheck /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.successMonth }}</div>
<div class="stat-label">本月审核成功</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card" shadow="hover" @click="handleStatClick('reported-month')">
<div class="stat-card-border" style="border-left-color: #e6a23c;"></div>
<div class="stat-content">
<div class="stat-icon" style="background: rgba(230, 162, 60, 0.1)">
<el-icon :size="28" color="#e6a23c"><Document /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ stats.reportedMonth }}</div>
<div class="stat-label">本月已上报</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 3. 筛选控制区 -->
<div class="filter-section">
<el-card>
<div class="filter-container">
<el-form :model="filterForm" ref="filterFormRef" label-width="80px" :inline="true" style="flex: 1; overflow: hidden;">
<el-row :gutter="16">
<!-- 第一行 -->
<el-col :span="24">
<el-row :gutter="16">
<el-col :span="3">
<el-form-item label="登记来源">
<el-select v-model="filterForm.source" placeholder="全部" clearable>
<el-option label="门诊" value="1" />
<el-option label="住院" value="2" />
<el-option label="急诊" value="3" />
<el-option label="体检" value="4" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="上报时间">
<el-date-picker
v-model="filterForm.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="患者姓名">
<el-input v-model="filterForm.patientName" placeholder="请输入患者姓名" clearable />
</el-form-item>
</el-col>
</el-row>
</el-col>
<!-- 第二行 -->
<el-col :span="24">
<el-row :gutter="16">
<el-col :span="3">
<el-form-item label="审核状态">
<el-select v-model="filterForm.status" placeholder="全部" clearable>
<el-option
v-for="item in auditStatusList.filter(s => ['1', '2', '3', '5'].includes(s.value))"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="上报科室">
<el-tree-select
v-model="filterForm.deptId"
:data="deptTreeData"
:props="{ children: 'children', label: 'label', value: 'value' }"
placeholder="全部科室"
clearable
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="18" style="display: flex; align-items: center; justify-content: flex-end;">
<el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon> 查询
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon> 重置
</el-button>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
</div>
<!-- 4. 报卡列表区 -->
<div class="table-section">
<el-card>
<!-- 表格头部操作按钮 -->
<div class="table-toolbar">
<div class="toolbar-left">
<el-button type="primary" @click="handleBatchAudit" :disabled="selectedRows.length === 0">
<el-icon><DocumentChecked /></el-icon> 批量审核
</el-button>
<el-button type="warning" @click="handleBatchReturn" :disabled="selectedRows.length === 0">
<el-icon><RefreshLeft /></el-icon> 批量退回修改
</el-button>
</div>
<div class="toolbar-right">
<el-button @click="handleExport">
<el-icon><Download /></el-icon> 导出当前
</el-button>
</div>
</div>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="tableData"
@selection-change="handleSelectionChange"
row-key="cardNo"
border
stripe
style="width: 100%"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="cardName" label="报卡名称" min-width="120" />
<el-table-column prop="diseaseName" label="病种名称" min-width="120" />
<el-table-column prop="cardNo" label="报卡编号" min-width="160" />
<el-table-column prop="patientName" label="患者姓名" min-width="100" />
<el-table-column prop="sex" label="性别" width="60">
<template #default="{ row }">
<span>{{ row.sex === '1' ? '男' : row.sex === '2' ? '女' : '未知' }}</span>
</template>
</el-table-column>
<el-table-column prop="age" label="年龄" width="70" />
<el-table-column prop="deptName" label="上报科室" min-width="100" />
<el-table-column prop="registrationSource" label="登记来源" width="80">
<template #default="{ row }">
<span>{{ getSourceName(row.registrationSource) }}</span>
</template>
</el-table-column>
<el-table-column prop="reportDate" label="上报时间" width="160">
<template #default="{ row }">
<span>{{ formatDateTime(row.reportDate) }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusName(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.status === 1 || row.status === 5"
type="primary"
link
@click="handleAudit(row)"
>
审核
</el-button>
<el-button
type="info"
link
@click="handleView(row)"
>
查看
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:page-sizes="[5, 10, 20, 50]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
</div>
<!-- 5. 抽屉详情区 -->
<el-drawer
v-model="drawerVisible"
:title="drawerMode === 'audit' ? '审核报卡' : '查看报卡'"
direction="rtl"
size="800px"
:before-close="handleDrawerClose"
>
<div class="drawer-content" v-loading="drawerLoading">
<!-- 报卡表单与传染病报告卡登记界面保持一致 -->
<div class="card-detail-section">
<h3 class="section-title">中华人民共和国传染病报告卡</h3>
<el-form :model="currentCard" label-width="100px" class="card-form">
<!-- 卡片编号 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="卡片编号">
<el-input v-model="currentCard.cardNo" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 患者基本信息 -->
<div class="form-divider">患者基本信息</div>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="患者姓名">
<el-input v-model="currentCard.patName" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="家长姓名">
<el-input v-model="currentCard.parentName" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="身份证号">
<el-input v-model="currentCard.idNo" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="性别">
<el-select v-model="currentCard.sex" :disabled="drawerMode !== 'edit'" style="width: 100%">
<el-option label="男" value="1" />
<el-option label="女" value="2" />
<el-option label="未知" value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="出生日期">
<el-input v-model="currentCard.birthday" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="实足年龄">
<el-input :value="currentCard.age + getAgeUnit(currentCard.ageUnit)" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="联系电话">
<el-input v-model="currentCard.phone" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急电话">
<el-input v-model="currentCard.contactPhone" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="现住地址">
<el-input :value="getFullAddress(currentCard)" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="病人属于">
<el-select v-model="currentCard.patientBelong" :disabled="drawerMode !== 'edit'" style="width: 100%">
<el-option label="本县区" :value="1" />
<el-option label="本市其他县区" :value="2" />
<el-option label="本省其他地市" :value="3" />
<el-option label="外省" :value="4" />
<el-option label="港澳台" :value="5" />
<el-option label="外籍" :value="6" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职业">
<el-select v-model="currentCard.occupation" :disabled="drawerMode !== 'edit'" placeholder="请选择" style="width: 100%">
<el-option v-for="item in occupationList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 临床信息 -->
<div class="form-divider">临床信息</div>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="病例分类">
<el-select v-model="currentCard.caseClass" :disabled="drawerMode !== 'edit'" style="width: 100%">
<el-option label="疑似病例" value="1" />
<el-option label="临床诊断病例" value="2" />
<el-option label="确诊病例" value="3" />
<el-option label="病原携带者" value="4" />
<el-option label="阳性检测结果" value="5" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="发病日期">
<el-date-picker
v-model="currentCard.onsetDate"
type="date"
placeholder="选择日期"
:disabled="drawerMode !== 'edit'"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="诊断日期">
<el-date-picker
v-model="currentCard.diagDate"
type="date"
placeholder="选择日期"
:disabled="drawerMode !== 'edit'"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="疾病名称">
<el-input :value="getDiseaseName(currentCard.diseaseCode)" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分型">
<el-input v-model="currentCard.diseaseType" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
</el-row>
<!-- 上报信息 -->
<div class="form-divider">上报信息</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="报告单位">
<el-input v-model="currentCard.reportOrg" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报告医生">
<el-input v-model="currentCard.reportDoc" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="填卡日期">
<el-date-picker
v-model="currentCard.reportDate"
type="date"
placeholder="选择日期"
:disabled="drawerMode !== 'edit'"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="登记来源">
<el-input :value="getSourceName(currentCard.registrationSource)" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="currentCard.remark" type="textarea" :rows="2" :disabled="drawerMode !== 'edit'" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 审核记录 -->
<div class="audit-records-section" v-if="auditRecords.length > 0">
<h3 class="section-title">审核记录</h3>
<el-timeline>
<el-timeline-item
v-for="record in auditRecords"
:key="record.auditId"
:timestamp="record.auditTime"
placement="top"
:type="getAuditType(record.auditStatusTo)"
>
<el-card>
<div class="record-content">
<div class="record-header">
<span class="auditor">{{ record.auditorName }}</span>
<el-tag size="small" :type="getAuditType(record.auditStatusTo)">
{{ getAuditTypeName(record.auditType) }}
</el-tag>
</div>
<div class="record-detail">
<span v-if="record.auditOpinion">审核意见{{ record.auditOpinion }}</span>
<span v-if="record.reasonForReturn">退回原因{{ record.reasonForReturn }}</span>
</div>
<div class="record-status">
{{ getStatusName(record.auditStatusFrom) }} {{ getStatusName(record.auditStatusTo) }}
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
<!-- 审核操作区域 -->
<div class="audit-action-section" v-if="drawerMode === 'audit'">
<h3 class="section-title">审核操作</h3>
<el-form :model="auditForm" label-width="100px">
<el-form-item label="审核意见" required>
<el-input
v-model="auditForm.auditOpinion"
type="textarea"
:rows="3"
placeholder="请填写审核意见"
/>
</el-form-item>
<el-form-item label="退回原因">
<el-input
v-model="auditForm.returnReason"
type="textarea"
:rows="3"
placeholder="如需退回,请填写退回原因"
maxlength="50"
show-word-limit
/>
</el-form-item>
</el-form>
</div>
</div>
<template #footer>
<div class="drawer-footer">
<el-button @click="handleDrawerClose">关闭</el-button>
<template v-if="drawerMode === 'audit'">
<el-button type="warning" @click="handleReturnCard" :disabled="!auditForm.returnReason">
退回修改
</el-button>
<el-button type="success" @click="handlePassCard" :disabled="!auditForm.auditOpinion">
审核通过
</el-button>
</template>
</div>
</template>
</el-drawer>
<!-- 批量审核弹窗 -->
<el-dialog
v-model="batchAuditDialogVisible"
title="批量审核"
width="500px"
:before-close="handleBatchAuditClose"
>
<el-form :model="batchAuditForm" label-width="80px">
<el-form-item label="审核意见" required>
<el-input
v-model="batchAuditForm.auditOpinion"
type="textarea"
:rows="3"
placeholder="请填写审核意见"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleBatchAuditClose">取消</el-button>
<el-button type="success" @click="confirmBatchAudit" :loading="batchLoading">确定</el-button>
</template>
</el-dialog>
<!-- 批量退回弹窗 -->
<el-dialog
v-model="batchReturnDialogVisible"
title="批量退回修改"
width="500px"
:before-close="handleBatchReturnClose"
>
<span style="color: #f56c6c;">即将退回 {{ selectedRows.length }} 张报卡请谨慎操作</span>
<el-form :model="batchReturnForm" label-width="80px" style="margin-top: 16px;">
<el-form-item label="退回原因" required>
<el-input
v-model="batchReturnForm.returnReason"
type="textarea"
:rows="3"
placeholder="请填写退回原因"
maxlength="50"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleBatchReturnClose">取消</el-button>
<el-button type="warning" @click="confirmBatchReturn" :loading="batchLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Clock, CircleClose, CircleCheck, Document, Search, Refresh, DocumentChecked, RefreshLeft, Download } from '@element-plus/icons-vue';
import { useDict } from '@/utils/dict';
import useUserStore from '@/store/modules/user';
import {
listInfectiousCards,
getInfectiousCard,
auditInfectiousCard,
returnInfectiousCard,
batchAuditCards,
batchReturnCards,
getDeptTree,
exportInfectiousCards
} from './api';
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
// 获取职业字典
const { prfs: occupationList } = useDict('prfs');
// 获取审核状态字典
const { card_audit_status: auditStatusList } = useDict('card_audit_status');
// 获取登记来源字典
const { registration: registrationList } = useDict('registration');
// 统计数据
const stats = ref({
pendingToday: 0,
failedMonth: 0,
successMonth: 0,
reportedMonth: 0,
});
// 筛选表单
const filterForm = reactive({
source: '',
dateRange: [],
patientName: '',
status: '',
deptId: '',
});
// 分页配置
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
// 表格数据
const tableData = ref([]);
const loading = ref(false);
const selectedRows = ref([]);
// 抽屉相关
const drawerVisible = ref(false);
const drawerMode = ref('view'); // view | audit
const drawerLoading = ref(false);
const currentCard = ref({});
const auditRecords = ref([]);
const auditForm = ref({
auditOpinion: '',
returnReason: '',
});
// 批量操作
const batchAuditDialogVisible = ref(false);
const batchReturnDialogVisible = ref(false);
const batchAuditForm = reactive({
auditOpinion: '',
});
const batchReturnForm = reactive({
returnReason: '',
});
const batchLoading = ref(false);
// 科室树数据
const deptTreeData = ref([]);
// 初始化加载
onMounted(() => {
loadDeptTree();
loadStats();
loadTableData();
});
// 加载科室树
async function loadDeptTree() {
try {
const res = await getDeptTree();
if (res.code === 200) {
deptTreeData.value = res.data || [];
}
} catch (err) {
console.error('加载科室树失败:', err);
}
}
// 加载统计数据
async function loadStats() {
try {
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1).toISOString().split('T')[0];
const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0).toISOString().split('T')[0];
// 今日待审核
const pendingRes = await listInfectiousCards({
status: '1',
startDate: todayStr,
endDate: todayStr,
pageNum: 1,
pageSize: 1
});
if (pendingRes.code === 200) {
stats.value.pendingToday = pendingRes.data?.total || pendingRes.data?.totalNum || 0;
}
// 本月审核失败
const failedRes = await listInfectiousCards({
status: '5',
startDate: monthStart,
endDate: monthEnd,
pageNum: 1,
pageSize: 1
});
if (failedRes.code === 200) {
stats.value.failedMonth = failedRes.data?.total || failedRes.data?.totalNum || 0;
}
// 本月审核成功
const successRes = await listInfectiousCards({
status: '2',
startDate: monthStart,
endDate: monthEnd,
pageNum: 1,
pageSize: 1
});
if (successRes.code === 200) {
stats.value.successMonth = successRes.data?.total || successRes.data?.totalNum || 0;
}
// 本月已上报
const reportedRes = await listInfectiousCards({
status: '3',
startDate: monthStart,
endDate: monthEnd,
pageNum: 1,
pageSize: 1
});
if (reportedRes.code === 200) {
stats.value.reportedMonth = reportedRes.data?.total || reportedRes.data?.totalNum || 0;
}
} catch (err) {
console.error('加载统计数据失败:', err);
}
}
// 加载表格数据
async function loadTableData() {
loading.value = true;
try {
const params = {
pageNo: pagination.currentPage,
pageSize: pagination.pageSize,
registrationSource: filterForm.source,
patientName: filterForm.patientName,
status: filterForm.status,
deptId: filterForm.deptId,
};
if (filterForm.dateRange && filterForm.dateRange.length === 2) {
params.startDate = filterForm.dateRange[0];
params.endDate = filterForm.dateRange[1];
}
const res = await listInfectiousCards(params);
if (res.code === 200) {
// 适配不同的响应格式
const dataList = res.data?.rows || res.data?.records || res.data?.list || [];
const total = res.data?.total || res.data?.totalNum || 0;
tableData.value = dataList.map(item => ({
...item,
// 确保关键字段存在
cardName: item.cardName || '传染病报告卡',
diseaseName: item.diseaseName || getDiseaseName(item.diseaseCode),
patientName: item.patientName || item.patName,
reportDate: item.reportDate || item.createdAt,
deptName: item.deptName || item.createDeptName,
}));
pagination.total = total;
} else {
ElMessage.error(res.msg || '加载数据失败');
}
} catch (err) {
ElMessage.error('加载数据失败');
console.error(err);
} finally {
loading.value = false;
}
}
// 搜索
function handleSearch() {
pagination.currentPage = 1;
loadTableData();
}
// 重置
function handleReset() {
filterForm.source = '';
filterForm.dateRange = [];
filterForm.patientName = '';
filterForm.status = '';
filterForm.deptId = '';
handleSearch();
}
// 统计卡片点击
function handleStatClick(type) {
filterForm.status = '';
filterForm.dateRange = [];
const today = new Date().toISOString().split('T')[0];
const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0];
const monthEnd = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).toISOString().split('T')[0];
switch (type) {
case 'pending-today':
filterForm.status = '1';
filterForm.dateRange = [today, today];
break;
case 'failed-month':
filterForm.status = '5';
filterForm.dateRange = [monthStart, monthEnd];
break;
case 'success-month':
filterForm.status = '2';
filterForm.dateRange = [monthStart, monthEnd];
break;
case 'reported-month':
filterForm.status = '3';
filterForm.dateRange = [monthStart, monthEnd];
break;
}
handleSearch();
}
// 选择变化
function handleSelectionChange(rows) {
selectedRows.value = rows;
}
// 审核
function handleAudit(row) {
drawerMode.value = 'audit';
drawerVisible.value = true;
loadCardDetail(row.cardNo);
}
// 查看
function handleView(row) {
drawerMode.value = 'view';
drawerVisible.value = true;
loadCardDetail(row.cardNo);
}
// 加载卡片详情
async function loadCardDetail(cardNo) {
drawerLoading.value = true;
try {
// 使用详情 API 获取卡片详细信息
const res = await getInfectiousCard(cardNo);
if (res.code === 200 && res.data) {
// 字段映射,确保前端绑定的字段名与后端返回一致
currentCard.value = {
...res.data,
patientName: res.data.patientName || res.data.patName,
patName: res.data.patName || res.data.patientName,
reportDate: res.data.reportDate || res.data.createdAt,
deptName: res.data.deptName || res.data.createDeptName,
diseaseName: res.data.diseaseName || getDiseaseName(res.data.diseaseCode),
};
// 如果没有单独的审核记录 API尝试从详情数据中获取
auditRecords.value = res.data.auditRecords || res.auditRecords || [];
} else {
ElMessage.error('获取卡片详情失败');
}
} catch (err) {
ElMessage.error('加载详情失败');
console.error(err);
} finally {
drawerLoading.value = false;
}
}
// 关闭抽屉
function handleDrawerClose() {
drawerVisible.value = false;
auditForm.value = {
auditOpinion: '',
returnReason: '',
};
}
// 审核通过
async function handlePassCard() {
if (!auditForm.value.auditOpinion) {
ElMessage.warning('请填写审核意见');
return;
}
try {
const res = await auditInfectiousCard({
cardNo: currentCard.value.cardNo,
auditOpinion: auditForm.value.auditOpinion,
status: '2'
});
if (res.code === 200) {
ElMessage.success('审核通过');
handleDrawerClose();
loadTableData();
loadStats();
} else {
ElMessage.error(res.msg || '审核失败');
}
} catch (err) {
ElMessage.error('审核失败');
console.error(err);
}
}
// 退回修改
async function handleReturnCard() {
if (!auditForm.value.returnReason) {
ElMessage.warning('请填写退回原因');
return;
}
// 验证退回原因长度
if (auditForm.value.returnReason.length > 50) {
ElMessage.warning('退回原因不能超过50个字符');
return;
}
try {
const res = await returnInfectiousCard({
cardNo: currentCard.value.cardNo,
returnReason: auditForm.value.returnReason,
status: '5'
});
if (res.code === 200) {
ElMessage.success('已退回修改');
handleDrawerClose();
loadTableData();
loadStats();
} else {
ElMessage.error(res.msg || '退回失败');
}
} catch (err) {
ElMessage.error('退回失败');
console.error(err);
}
}
// 批量审核
function handleBatchAudit() {
// 检查是否包含非可审核状态的报卡(只有待审核和审核失败可以批量审核)
const nonPendingCards = selectedRows.value.filter(row => row.status !== 1 && row.status !== 5);
if (nonPendingCards.length > 0) {
ElMessage.warning('只能选择待审核或审核失败状态的报卡');
return;
}
batchAuditDialogVisible.value = true;
}
function handleBatchAuditClose() {
batchAuditDialogVisible.value = false;
batchAuditForm.auditOpinion = '';
}
async function confirmBatchAudit() {
if (!batchAuditForm.auditOpinion) {
ElMessage.warning('请填写审核意见');
return;
}
batchLoading.value = true;
try {
const cardNos = selectedRows.value.map(row => row.cardNo);
const res = await batchAuditCards({
cardNos,
auditOpinion: batchAuditForm.auditOpinion,
status: '2'
});
if (res.code === 200) {
ElMessage.success('批量审核成功');
handleBatchAuditClose();
loadTableData();
loadStats();
} else {
ElMessage.error(res.msg || '批量审核失败');
}
} catch (err) {
ElMessage.error('批量审核失败');
console.error(err);
} finally {
batchLoading.value = false;
}
}
// 批量退回
function handleBatchReturn() {
// 检查是否包含非可审核状态的报卡(只有待审核和审核失败可以批量退回)
const nonPendingCards = selectedRows.value.filter(row => row.status !== 1 && row.status !== 5);
if (nonPendingCards.length > 0) {
ElMessage.warning('只能选择待审核或审核失败状态的报卡');
return;
}
batchReturnDialogVisible.value = true;
}
function handleBatchReturnClose() {
batchReturnDialogVisible.value = false;
batchReturnForm.returnReason = '';
}
async function confirmBatchReturn() {
if (!batchReturnForm.returnReason) {
ElMessage.warning('请填写退回原因');
return;
}
// 验证退回原因长度
if (batchReturnForm.returnReason.length > 50) {
ElMessage.warning('退回原因不能超过50个字符');
return;
}
batchLoading.value = true;
try {
const cardNos = selectedRows.value.map(row => row.cardNo);
const res = await batchReturnCards({
cardNos,
returnReason: batchReturnForm.returnReason,
status: '5'
});
if (res.code === 200) {
ElMessage.success('批量退回成功');
handleBatchReturnClose();
loadTableData();
loadStats();
} else {
ElMessage.error(res.msg || '批量退回失败');
}
} catch (err) {
ElMessage.error('批量退回失败');
console.error(err);
} finally {
batchLoading.value = false;
}
}
// 导出
async function handleExport() {
try {
// 构建查询参数
const params = {
cardNo: filterForm.cardNo,
patientName: filterForm.patientName,
status: filterForm.status,
registrationSource: filterForm.source,
deptId: filterForm.deptId,
};
if (filterForm.dateRange && filterForm.dateRange.length === 2) {
params.startDate = filterForm.dateRange[0];
params.endDate = filterForm.dateRange[1];
}
// 使用导出 API 发起请求,携带认证信息
const res = await exportInfectiousCards(params);
// 创建 Blob 对象
const blob = new Blob([res], { type: 'text/csv;charset=utf-8' });
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '传染病报告卡_' + new Date().getTime() + '.csv';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
// 释放资源
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
ElMessage.success('导出成功');
} catch (err) {
ElMessage.error('导出失败');
console.error(err);
}
}
// 分页处理
function handleSizeChange(size) {
pagination.pageSize = size;
pagination.currentPage = 1;
loadTableData();
}
function handleCurrentChange(page) {
pagination.currentPage = page;
loadTableData();
}
// 工具函数
/**
* 获取状态名称
* @param {string|number} status - 状态值
* @returns {string} 状态名称
*/
function getStatusName(status) {
if (status === null || status === undefined) return '未知';
const item = auditStatusList.value.find(item => item.value === String(status) || item.value === status);
return item ? item.label : '未知';
}
/**
* 获取状态类型(用于 el-tag
* @param {string|number} status - 状态值
* @returns {string} 类型
*/
function getStatusType(status) {
const statusStr = String(status);
if (statusStr === '0') return 'info';
if (statusStr === '1') return 'warning';
if (statusStr === '2') return 'success';
if (statusStr === '3') return '';
if (statusStr === '4' || statusStr === '5') return 'danger';
return 'info';
}
/**
* 获取来源名称
* @param {string|number} source - 来源值
* @returns {string} 来源名称
*/
function getSourceName(source) {
if (source === null || source === undefined) return '-';
const item = registrationList.value.find(item => item.value === String(source) || item.value === source);
return item ? item.label : '-';
}
function getAgeUnit(unit) {
const unitMap = { '1': '岁', '2': '月', '3': '天' };
return unitMap[unit] || '岁';
}
function getDiseaseName(code) {
const diseaseMap = {
'0101': '鼠疫',
'0102': '霍乱',
'0201': '传染性非典型肺炎',
'0202': '艾滋病',
'0203': '病毒性肝炎',
'0211': '炭疽',
'0213': '肺结核',
'0222': '梅毒',
'0224': '血吸虫病',
'0225': '疟疾',
'0301': '流行性感冒',
'0302': '流行性腮腺炎',
'0303': '风疹',
'0310': '其它感染性腹泻病',
'0311': '手足口病',
};
return diseaseMap[code] || code;
}
function formatDateTime(date) {
if (!date) return '-';
return date.replace('T', ' ').substring(0, 16);
}
function getFullAddress(data) {
const parts = [
data.addressProv,
data.addressCity,
data.addressCounty,
data.addressTown,
data.addressVillage,
data.addressHouse,
].filter(Boolean);
return parts.join('') || '-';
}
function getAuditType(status) {
if (status === '2') return 'success';
if (status === '5') return 'danger';
return 'primary';
}
function getAuditTypeName(type) {
const typeMap = {
'1': '批量审核',
'2': '审核通过',
'3': '批量退回',
'4': '退回修改',
'5': '其他',
};
return typeMap[type] || '未知';
}
</script>
<style scoped>
.report-management-container {
height: 100%;
display: flex;
flex-direction: column;
background: #f0f2f5;
}
/* 顶部导航栏 */
.top-navbar {
height: 60px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
}
.navbar-content {
width: 100%;
padding: 0 20px;
}
.system-logo {
display: flex;
align-items: center;
gap: 8px;
}
.cdc-text {
font-size: 20px;
font-weight: bold;
color: #4a6fa5;
padding: 4px 8px;
border: 2px solid #4a6fa5;
border-radius: 4px;
}
.logo-text {
font-size: 18px;
font-weight: bold;
color: #4a6fa5;
}
/* 统计卡片 */
.overview-cards {
padding: 16px;
}
.stat-card {
position: relative;
cursor: pointer;
transition: all 0.3s;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.stat-card-border {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
}
.stat-content {
display: flex;
align-items: center;
gap: 16px;
}
.stat-icon {
width: 56px;
height: 56px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.stat-info {
flex: 1;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #303133;
}
.stat-label {
font-size: 14px;
color: #909399;
margin-top: 4px;
}
/* 筛选区 */
.filter-section {
padding: 0 16px 16px;
}
.filter-section .filter-container {
display: block;
}
.filter-section .filter-container .el-form {
width: 100%;
}
.filter-section .el-form-item {
display: block;
width: 100%;
}
.filter-section .el-form-item__content {
width: 100%;
}
/* 表格区 */
.table-section {
flex: 1;
padding: 0 16px 16px;
overflow: hidden;
}
.table-section .el-card {
height: 100%;
display: flex;
flex-direction: column;
}
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.toolbar-left,
.toolbar-right {
display: flex;
gap: 8px;
}
.pagination-container {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
/* 抽屉样式 */
.drawer-content {
padding: 0 20px;
max-height: calc(100vh - 200px);
overflow-y: auto;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #303133;
margin: 0 0 16px 0;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
}
.card-detail-section {
margin-bottom: 24px;
}
.card-form {
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
.form-divider {
font-size: 14px;
font-weight: bold;
color: #606266;
margin: 16px 0 12px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
}
.audit-records-section {
margin-bottom: 24px;
}
.record-content {
padding: 8px 0;
}
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.auditor {
font-weight: bold;
color: #303133;
}
.record-detail {
font-size: 13px;
color: #606266;
margin-bottom: 8px;
}
.record-status {
font-size: 12px;
color: #909399;
}
.audit-action-section {
margin-bottom: 24px;
}
.audit-action-section .el-form {
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* 响应式调整 */
@media (max-width: 1400px) {
.stat-value {
font-size: 24px;
}
}
@media (max-width: 1200px) {
.filter-section .el-form {
display: block;
}
.filter-section .el-form-item {
display: block;
margin-bottom: 16px;
}
.filter-section .el-form-item__label {
display: block;
margin-bottom: 8px;
}
.filter-section .el-form-item__content {
width: 100%;
}
}
</style>