<template>
|
<el-dialog
|
title="案例各阶段附件"
|
:visible.sync="visible"
|
width="1200px"
|
top="4vh"
|
append-to-body
|
:close-on-click-modal="false"
|
@closed="handleClosed"
|
>
|
<!-- 阶段 Tabs -->
|
<el-tabs v-model="activeStage" type="card" @tab-click="handleStageChange">
|
<el-tab-pane
|
v-for="stage in stageTypes"
|
:key="stage.value"
|
:label="stage.label"
|
:name="stage.value"
|
>
|
<!-- 阶段内的附件类型 Tabs -->
|
<div class="stage-content">
|
<el-tabs v-model="activeType" type="border-card">
|
<el-tab-pane
|
v-for="type in getTypesByStage(stage.value)"
|
:key="type.value"
|
:label="type.label"
|
:name="type.value"
|
>
|
<!-- 附件列表 -->
|
<div class="attachment-list">
|
<el-table
|
:data="getAttachmentsByStageAndType(stage.value, type.value)"
|
size="small"
|
v-loading="loading"
|
style="width: 100%;"
|
@row-click="handleRowClick"
|
:row-class-name="tableRowClassName"
|
>
|
<el-table-column label="文件名" min-width="220">
|
<template slot-scope="scope">
|
<i
|
class="el-icon-document"
|
style="color: #409EFF; margin-right: 8px;"
|
></i>
|
<span class="file-name">{{ scope.row.fileName }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="文件类型" width="100" align="center">
|
<template slot-scope="scope">
|
<el-tag size="small">{{
|
getFileType(scope.row.fileName)
|
}}</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="文件大小" width="100" align="center">
|
<template slot-scope="scope">
|
<span>{{ formatFileSize(scope.row.fileSize) }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="上传时间" width="160" align="center">
|
<template slot-scope="scope">
|
<span>{{ formatDateTime(scope.row.uploadTime) }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="上传人" width="120" align="center">
|
<template slot-scope="scope">
|
<span>{{ scope.row.uploader || "-" }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="操作" width="180" align="center">
|
<template slot-scope="scope">
|
<el-button
|
size="mini"
|
type="primary"
|
@click="handlePreview(scope.row)"
|
>
|
预览
|
</el-button>
|
<el-button
|
size="mini"
|
type="danger"
|
@click="handleDownload(scope.row)"
|
>
|
下载
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 空状态 -->
|
<div
|
v-if="
|
getAttachmentsByStageAndType(stage.value, type.value)
|
.length === 0
|
"
|
class="empty-attachment"
|
>
|
<el-empty
|
:description="`暂无${type.label}附件`"
|
:image-size="80"
|
></el-empty>
|
</div>
|
</div>
|
</el-tab-pane>
|
</el-tabs>
|
</div>
|
</el-tab-pane>
|
</el-tabs>
|
|
<span slot="footer" class="dialog-footer">
|
<el-button @click="visible = false">关闭</el-button>
|
<!-- <el-button type="primary" @click="handleConfirm">
|
确认选择 ({{ selectedCount }})
|
</el-button> -->
|
</span>
|
|
<!-- 文件预览弹窗 -->
|
<FilePreviewDialog
|
:visible="previewVisible"
|
:file="currentPreviewFile"
|
@close="previewVisible = false"
|
@download="handleDownload"
|
/>
|
</el-dialog>
|
</template>
|
|
<script>
|
import { getfileList } from "@/api/project/donatebaseinfo";
|
import FilePreviewDialog from "@/components/FilePreviewDialog";
|
import { parseTime } from "@/utils/ruoyi";
|
|
export default {
|
name: "StageAttachmentsDialog",
|
components: {
|
FilePreviewDialog
|
},
|
props: {
|
caseId: {
|
type: [String, Number],
|
required: true
|
},
|
// 已选中的附件ID集合(用于回显)
|
selectedIds: {
|
type: Array,
|
default: () => []
|
}
|
},
|
data() {
|
return {
|
visible: false,
|
loading: false,
|
|
// 阶段定义
|
stageTypes: [
|
{ value: "death", label: "死亡判定" },
|
{ value: "confirm", label: "捐献确认" },
|
{ value: "maintain", label: "供者维护" },
|
{ value: "witness", label: "器官获取见证" },
|
{ value: "ethics", label: "伦理审查" }
|
],
|
|
// 各阶段附件类型定义
|
typeDefinitions: {
|
death: [
|
{ value: "brain", label: "脑死亡判定" },
|
{ value: "heart", label: "心死亡判定" }
|
],
|
confirm: [
|
{ value: "1", label: "人体器官潜在捐献者登记表" },
|
{ value: "2", label: "人体器官捐献亲属确认登记表" },
|
{ value: "3", label: "捐献者及直系亲属身份证、户口簿相关证明" },
|
{ value: "4", label: "公民身故后人体器官(角膜)遗体捐献告知书" },
|
{ value: "5", label: "脑死亡判定知情同意书" },
|
{ value: "6", label: "心死亡判定知情同意书" }
|
],
|
maintain: [
|
{ value: "urine", label: "尿常规" },
|
{ value: "blood", label: "血常规" },
|
{ value: "liver", label: "肝肾功能" },
|
{ value: "culture", label: "培养结果" },
|
{ value: "nurse", label: "护理记录" }
|
],
|
witness: [{ value: "deathCert", label: "死亡证明" }],
|
ethics: [{ value: "review", label: "伦理审查材料" }]
|
},
|
|
// 当前激活的Tab
|
activeStage: "death",
|
activeType: "",
|
|
// 原始接口数据
|
rawData: null,
|
|
// 处理后的附件数据 stage -> type -> list
|
attachmentData: {},
|
|
// 选中的附件集合
|
selectedAttachments: new Map(),
|
|
// 预览相关
|
previewVisible: false,
|
currentPreviewFile: null
|
};
|
},
|
computed: {
|
// 当前阶段下的附件类型
|
currentStageTypes() {
|
return this.typeDefinitions[this.activeStage] || [];
|
},
|
|
// 已选数量
|
selectedCount() {
|
return this.selectedAttachments.size;
|
},
|
selectedCount() {
|
return this.selectedAttachments?.size || 0;
|
}
|
},
|
watch: {
|
selectedIds: {
|
handler(newVal) {
|
this.initSelectedState(newVal);
|
},
|
immediate: true
|
}
|
},
|
methods: {
|
open() {
|
this.visible = true;
|
this.loadData();
|
},
|
|
// 初始化选中状态
|
initSelectedState(ids) {
|
this.selectedAttachments.clear();
|
ids.forEach(id => {
|
this.selectedAttachments.set(id, true);
|
});
|
},
|
|
// 加载数据
|
async loadData() {
|
this.loading = true;
|
try {
|
const res = await getfileList({ infoid: this.caseId });
|
if (res.code === 200) {
|
this.rawData = res.data || {};
|
this.processData();
|
}
|
} catch (e) {
|
console.error(e);
|
} finally {
|
this.loading = false;
|
}
|
},
|
|
// 处理接口数据
|
processData() {
|
const d = this.rawData;
|
const result = {};
|
|
// 初始化结构
|
this.stageTypes.forEach(stage => {
|
result[stage.value] = {};
|
(this.typeDefinitions[stage.value] || []).forEach(type => {
|
result[stage.value][type.value] = [];
|
});
|
});
|
|
// ===== 1. 死亡判定 =====
|
(d.deathinfo || []).forEach(item => {
|
this.pushAttachment(
|
result.death,
|
"brain",
|
this.parseAttachments(item.deathjudgeannex)
|
);
|
this.pushAttachment(
|
result.death,
|
"heart",
|
this.parseAttachments(item.heartdeathjudgeannex)
|
);
|
});
|
|
// ===== 2. 捐献确认 =====
|
(d.relativesconfirmation || []).forEach(item => {
|
this.pushAttachment(
|
result.confirm,
|
"1",
|
this.parseAttachments(item.assessannex)
|
);
|
});
|
|
// ===== 3. 供者维护 =====
|
(d.donatemaintenance || []).forEach(item => {
|
try {
|
const itemDesc = JSON.parse(item.itemDesc || "{}");
|
this.pushAttachment(
|
result.maintain,
|
"urine",
|
itemDesc.urineRoutine?.attachments || []
|
);
|
this.pushAttachment(
|
result.maintain,
|
"blood",
|
itemDesc.bloodRoutine?.attachments || []
|
);
|
this.pushAttachment(
|
result.maintain,
|
"liver",
|
itemDesc.liverKidney?.attachments || []
|
);
|
this.pushAttachment(
|
result.maintain,
|
"culture",
|
itemDesc.cultureResults?.flatMap(c => c.attachments || []) || []
|
);
|
this.pushAttachment(
|
result.maintain,
|
"nurse",
|
itemDesc.nursingRecords?.flatMap(n => n.attachments || []) || []
|
);
|
} catch {}
|
});
|
|
// ===== 4. 器官获取见证 =====
|
(d.donationwitness || []).forEach(item => {
|
this.pushAttachment(
|
result.witness,
|
"deathCert",
|
this.parseAttachments(item.deathjudgeannex)
|
);
|
});
|
|
// ===== 5. 伦理审查 =====
|
(d.donateflowcharts || []).forEach(item => {
|
this.pushAttachment(
|
result.ethics,
|
"review",
|
this.parseAttachments(item.filePatch)
|
);
|
});
|
|
this.attachmentData = result;
|
|
this.$nextTick(() => {
|
if (this.currentStageTypes.length > 0) {
|
this.activeType = this.currentStageTypes[0].value;
|
}
|
});
|
},
|
|
// 辅助:解析附件字段
|
parseAttachments(val) {
|
if (!val) return [];
|
if (Array.isArray(val)) return val;
|
try {
|
const arr = JSON.parse(val);
|
return Array.isArray(arr) ? arr : [];
|
} catch {
|
return val
|
.split(";")
|
.filter(Boolean)
|
.map(p => ({ fileName: p.split("/").pop(), fileUrl: p }));
|
}
|
},
|
|
// 辅助:推送附件并补充字段
|
pushAttachment(result, type, files) {
|
files.forEach(file => {
|
const attachment = {
|
id: file.id || `${Date.now()}_${Math.random()}`,
|
fileName: file.fileName,
|
fileUrl: file.path || file.fileUrl,
|
fileSize: file.fileSize,
|
uploadTime: file.uploadTime,
|
uploader: file.uploader,
|
type: type,
|
stage: this.activeStage
|
};
|
result[type].push(attachment);
|
|
// // 回显选中状态
|
// if (this.selectedAttachments.has(attachment.id)) {
|
// attachment._selected = true;
|
// }
|
});
|
},
|
|
// 获取某阶段下的附件类型
|
getTypesByStage(stage) {
|
return this.typeDefinitions[stage] || [];
|
},
|
|
// 获取附件列表
|
getAttachmentsByStageAndType(stage, type) {
|
return this.attachmentData[stage]?.[type] || [];
|
},
|
|
// 阶段切换
|
handleStageChange() {
|
this.$nextTick(() => {
|
if (this.currentStageTypes.length > 0) {
|
this.activeType = this.currentStageTypes[0].value;
|
}
|
});
|
},
|
|
// 点击行选择/取消
|
handleRowClick(row) {
|
if (this.selectedAttachments.has(row.id)) {
|
this.selectedAttachments.delete(row.id);
|
} else {
|
this.selectedAttachments.set(row.id, row);
|
}
|
},
|
tableRowClassName({ row }) {
|
return this.selectedAttachments.has(row.id)
|
? "selected-row"
|
: "";
|
},
|
// 预览
|
handlePreview(row) {
|
this.currentPreviewFile = {
|
fileName: row.fileName,
|
fileUrl: row.fileUrl,
|
fileType: this.getFileType(row.fileName)
|
};
|
this.previewVisible = true;
|
},
|
|
// 下载
|
handleDownload(file) {
|
const link = document.createElement("a");
|
link.href = file.fileUrl || file.path;
|
link.download = file.fileName;
|
link.click();
|
},
|
|
// 获取文件类型
|
getFileType(fileName) {
|
const ext = fileName
|
.split(".")
|
.pop()
|
.toLowerCase();
|
if (["jpg", "jpeg", "png"].includes(ext)) return "image";
|
if (ext === "pdf") return "pdf";
|
if (["doc", "docx"].includes(ext)) return "office";
|
return "other";
|
},
|
|
// 文件大小格式化
|
formatFileSize(size) {
|
if (!size) return "0 B";
|
const k = 1024;
|
const sizes = ["B", "KB", "MB", "GB"];
|
const i = Math.floor(Math.log(size) / Math.log(k));
|
return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
},
|
|
// 日期时间格式化
|
formatDateTime(dateTime) {
|
return dateTime ? parseTime(dateTime) : "-";
|
},
|
|
// 确认选择
|
handleConfirm() {
|
const result = [];
|
this.selectedAttachments.forEach(attachment => {
|
result.push({
|
id: attachment.id,
|
fileName: attachment.fileName,
|
fileUrl: attachment.fileUrl,
|
fileSize: attachment.fileSize,
|
uploadTime: attachment.uploadTime,
|
uploader: attachment.uploader,
|
stage: attachment.stage,
|
stageName:
|
this.stageTypes.find(s => s.value === attachment.stage)?.label ||
|
"",
|
type: attachment.type,
|
typeName:
|
this.typeDefinitions[attachment.stage]?.find(
|
t => t.value === attachment.type
|
)?.label || ""
|
});
|
});
|
|
this.$emit("confirm", result);
|
this.visible = false;
|
},
|
|
// 弹框关闭
|
handleClosed() {
|
this.attachmentData = {};
|
this.selectedAttachments.clear();
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.stage-content {
|
padding: 15px 0;
|
}
|
|
.attachment-list {
|
margin-top: 15px;
|
}
|
|
.empty-attachment {
|
text-align: center;
|
padding: 40px 0;
|
color: #909399;
|
border: 1px dashed #dcdfe6;
|
border-radius: 4px;
|
margin-top: 15px;
|
}
|
|
.file-name {
|
font-size: 13px;
|
color: #606266;
|
}
|
|
/* 选中行高亮 */
|
::v-deep .el-table__row:hover {
|
cursor: pointer;
|
}
|
::v-deep .el-table__row.selected-row {
|
background-color: #ecf5ff !important;
|
}
|
</style>
|