From d189796882d88080aab7fb720dcf794819b0a609 Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期六, 20 十二月 2025 11:17:13 +0800
Subject: [PATCH] 上报、转运相关
---
src/views/business/appear/index.vue | 382 ++++++++++
src/views/business/transfer/transportDetail.vue | 672 +++++++++++++++++
src/views/business/transfer/index.vue | 550 ++++++++++++++
src/api/system/business/transport.js | 62 +
src/api/system/business/index.js | 2
src/views/business/appear/caseDetail.vue | 582 +++++++++++++++
6 files changed, 2,250 insertions(+), 0 deletions(-)
diff --git a/src/api/system/business/index.js b/src/api/system/business/index.js
new file mode 100644
index 0000000..d6265de
--- /dev/null
+++ b/src/api/system/business/index.js
@@ -0,0 +1,2 @@
+export * from "./transport";
+
diff --git a/src/api/system/business/transport.js b/src/api/system/business/transport.js
new file mode 100644
index 0000000..fbdec57
--- /dev/null
+++ b/src/api/system/business/transport.js
@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+// 鏌ヨ杞繍鍗曞垪琛�
+export function listTransport(query) {
+ return request({
+ url: '/system/transport/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鏌ヨ杞繍鍗曡缁�
+export function getTransport(id) {
+ return request({
+ url: '/system/transport/' + id,
+ method: 'get'
+ })
+}
+
+// 鏂板杞繍鍗�
+export function addTransport(data) {
+ return request({
+ url: '/system/transport',
+ method: 'post',
+ data: data
+ })
+}
+
+// 淇敼杞繍鍗�
+export function updateTransport(data) {
+ return request({
+ url: '/system/transport',
+ method: 'put',
+ data: data
+ })
+}
+
+// 鍒犻櫎杞繍鍗�
+export function delTransport(id) {
+ return request({
+ url: '/system/transport/' + id,
+ method: 'delete'
+ })
+}
+
+// 鏇存柊杞繍鐘舵��
+export function updateTransportStatus(data) {
+ return request({
+ url: '/system/transport/status',
+ method: 'put',
+ data: data
+ })
+}
+
+// 瀵煎嚭杞繍鍗�
+export function exportTransport(query) {
+ return request({
+ url: '/system/transport/export',
+ method: 'get',
+ params: query
+ })
+}
diff --git a/src/views/business/appear/caseDetail.vue b/src/views/business/appear/caseDetail.vue
new file mode 100644
index 0000000..05826f7
--- /dev/null
+++ b/src/views/business/appear/caseDetail.vue
@@ -0,0 +1,582 @@
+<template>
+ <div class="case-detail">
+ <el-tabs v-model="activeTab">
+ <el-tab-pane label="鍩烘湰淇℃伅" name="basic">
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鎹愮尞缂栧彿">{{ caseData.donorNo }}</el-descriptions-item>
+ <el-descriptions-item label="鎹愮尞鑰呭鍚�">{{ caseData.donorName }}</el-descriptions-item>
+ <el-descriptions-item label="鎬у埆">
+ <dict-tag :options="genderOptions" :value="caseData.gender"/>
+ </el-descriptions-item>
+ <el-descriptions-item label="骞撮緞">{{ caseData.age }}宀�</el-descriptions-item>
+ <el-descriptions-item label="琛�鍨�">
+ <dict-tag :options="bloodTypeOptions" :value="caseData.bloodType"/>
+ </el-descriptions-item>
+ <el-descriptions-item label="璇佷欢鍙风爜">{{ caseData.idCardNo }}</el-descriptions-item>
+ <el-descriptions-item label="姘戞棌">{{ caseData.nation }}</el-descriptions-item>
+ <el-descriptions-item label="鑱旂郴鐢佃瘽">{{ caseData.phone }}</el-descriptions-item>
+ <el-descriptions-item label="浣忓潃" :span="2">{{ caseData.address }}</el-descriptions-item>
+ </el-descriptions>
+ </el-tab-pane>
+
+ <el-tab-pane label="鍖荤枟淇℃伅" name="medical">
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="鐤剧梾璇婃柇">{{ caseData.diagnosis }}</el-descriptions-item>
+ <el-descriptions-item label="浣忛櫌鍙�">{{ caseData.inpatientNo }}</el-descriptions-item>
+ <el-descriptions-item label="鎵�鍦ㄧ瀹�">{{ caseData.departmentName }}</el-descriptions-item>
+ <el-descriptions-item label="涓绘不鍖荤敓">{{ caseData.doctorName }}</el-descriptions-item>
+ <el-descriptions-item label="浼犳煋鐥呮儏鍐�">{{ caseData.infectiousDisease || '鏃�' }}</el-descriptions-item>
+ <el-descriptions-item label="鍖荤枟璁板綍">{{ caseData.medicalRecord }}</el-descriptions-item>
+ </el-descriptions>
+ </el-tab-pane>
+
+ <el-tab-pane label="鍖婚櫌淇℃伅" name="hospital">
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鍖婚櫌鍚嶇О">{{ caseData.hospitalName }}</el-descriptions-item>
+ <el-descriptions-item label="鍖婚櫌绾у埆">{{ caseData.hospitalLevel }}</el-descriptions-item>
+ <el-descriptions-item label="鑱旂郴浜�">{{ caseData.contactPerson }}</el-descriptions-item>
+ <el-descriptions-item label="鑱旂郴鐢佃瘽">{{ caseData.contactPhone }}</el-descriptions-item>
+ <el-descriptions-item label="鍖婚櫌鍦板潃" :span="2">{{ caseData.hospitalAddress }}</el-descriptions-item>
+ </el-descriptions>
+ </el-tab-pane>
+
+ <!-- 鏂板闄勪欢淇℃伅鏍囩椤� -->
+ <el-tab-pane label="闄勪欢淇℃伅" name="attachments">
+ <el-card class="attachment-card">
+ <div slot="header" class="clearfix">
+ <span>闄勪欢鍒楄〃</span>
+ <el-button
+ style="float: right; padding: 3px 0"
+ type="text"
+ @click="handleUpload"
+ >
+ 涓婁紶闄勪欢
+ </el-button>
+ </div>
+
+ <el-table :data="attachmentList" style="width: 100%">
+ <el-table-column label="鏂囦欢鍚�" width="300">
+ <template slot-scope="scope">
+ <i class="el-icon-document" style="margin-right: 8px;"></i>
+ <span>{{ scope.row.fileName }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂囦欢绫诲瀷" width="120">
+ <template slot-scope="scope">
+ <el-tag size="small">{{ scope.row.fileType }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶у皬" width="100">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婁紶鏃堕棿" width="180">
+ <template slot-scope="scope">
+ <span>{{ scope.row.uploadTime }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔">
+ <template slot-scope="scope">
+ <el-button size="mini" @click="handlePreview(scope.row)"
+ >棰勮</el-button
+ >
+ <el-button
+ size="mini"
+ type="success"
+ @click="handleDownload(scope.row)"
+ >涓嬭浇</el-button
+ >
+ <el-button
+ size="mini"
+ type="danger"
+ @click="handleDelete(scope.row)"
+ >鍒犻櫎</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-tab-pane>
+
+ <el-tab-pane label="瀹℃壒淇℃伅" name="approval" v-if="caseData.status !== '0'">
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="瀹℃壒缁撴灉">
+ <el-tag :type="caseData.status | statusFilter">
+ {{ caseData.status | statusTextFilter }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="瀹℃壒鏃堕棿">{{ caseData.approveTime }}</el-descriptions-item>
+ <el-descriptions-item label="瀹℃壒浜�">{{ caseData.approverName }}</el-descriptions-item>
+ <el-descriptions-item label="瀹℃壒鎰忚">{{ caseData.approveOpinion }}</el-descriptions-item>
+ </el-descriptions>
+ </el-tab-pane>
+ </el-tabs>
+
+ <!-- PDF棰勮寮圭獥 -->
+ <el-dialog
+ :title="previewTitle"
+ :append-to-body="true"
+ :visible.sync="pdfPreviewVisible"
+ width="90%"
+ top="5vh"
+ :close-on-click-modal="true"
+ class="pdf-preview-dialog"
+ @close="handlePdfDialogClose"
+ >
+ <div class="pdf-preview-container" v-loading="pdfLoading">
+ <!-- PDF鎺у埗宸ュ叿鏍� -->
+ <div class="pdf-toolbar">
+ <el-button-group>
+ <el-button
+ size="mini"
+ @click="changePage(currentPage - 1)"
+ :disabled="currentPage <= 1"
+ icon="el-icon-arrow-left"
+ >
+ 涓婁竴椤�
+ </el-button>
+ <el-button size="mini" disabled>
+ 绗� {{ currentPage }} 椤� / 鍏� {{ pageCount }} 椤�
+ </el-button>
+ <el-button
+ size="mini"
+ @click="changePage(currentPage + 1)"
+ :disabled="currentPage >= pageCount"
+ icon="el-icon-arrow-right"
+ >
+ 涓嬩竴椤�
+ </el-button>
+ </el-button-group>
+
+ <el-button-group class="zoom-controls">
+ <el-button size="mini" @click="zoomOut" :disabled="scale <= 50">
+ <i class="el-icon-zoom-out"></i> 缂╁皬
+ </el-button>
+ <el-button size="mini" disabled> {{ scale }}% </el-button>
+ <el-button size="mini" @click="zoomIn" :disabled="scale >= 200">
+ <i class="el-icon-zoom-in"></i> 鏀惧ぇ
+ </el-button>
+ <el-button size="mini" @click="resetZoom">
+ <i class="el-icon-refresh-left"></i> 閲嶇疆
+ </el-button>
+ </el-button-group>
+
+ <el-button
+ size="mini"
+ type="success"
+ @click="downloadPdf(currentFile)"
+ icon="el-icon-download"
+ >
+ 涓嬭浇
+ </el-button>
+ </div>
+
+ <!-- PDF娓叉煋鍖哄煙 -->
+ <div class="pdf-viewport">
+ <pdf
+ ref="pdf"
+ :src="pdfUrl"
+ :page="currentPage"
+ :rotate="pageRotate"
+ @num-pages="pageCount = $event"
+ @page-loaded="currentPage = $event"
+ @loaded="loadPdfHandler"
+ @error="pdfErrorHandler"
+ :style="{
+ width: scale + '%',
+ transform: 'scale(' + scale / 100 + ')',
+ transformOrigin: '0 0'
+ }"
+ ></pdf>
+ </div>
+ </div>
+ </el-dialog>
+
+ <!-- 鍥剧墖棰勮寮圭獥 -->
+ <el-dialog
+ :append-to-body="true"
+ :title="previewTitle"
+ :visible.sync="imagePreviewVisible"
+ width="60%"
+ top="10vh"
+ :close-on-click-modal="true"
+ >
+ <div class="image-preview-container">
+ <img :src="previewUrl" alt="棰勮鍥剧墖" class="preview-image" />
+ </div>
+ </el-dialog>
+
+ <!-- 涓嶆敮鎸侀瑙堢殑鏂囦欢绫诲瀷 -->
+ <el-dialog
+ :title="previewTitle"
+ :visible.sync="unsupportedPreviewVisible"
+ width="400px"
+ :close-on-click-modal="true"
+ >
+ <div class="unsupported-preview">
+ <el-alert
+ title="璇ユ枃浠舵牸寮忎笉鏀寔鍦ㄧ嚎棰勮锛岃涓嬭浇鍚庢煡鐪�"
+ type="warning"
+ show-icon
+ :closable="false"
+ />
+ <div style="text-align: center; margin-top: 20px;">
+ <el-button type="primary" @click="handleDownload(currentFile)">
+ <i class="el-icon-download"></i> 涓嬭浇鏂囦欢
+ </el-button>
+ </div>
+ </div>
+ </el-dialog>
+
+ <div class="detail-footer">
+ <el-button @click="handleClose">鍏抽棴</el-button>
+ </div>
+ </div>
+</template>
+
+<script>
+import pdf from "vue-pdf";
+
+export default {
+ name: "CaseDetail",
+ components: {
+ pdf
+ },
+ props: {
+ caseData: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ filters: {
+ statusFilter(status) {
+ const statusMap = {
+ '0': 'warning',
+ '1': 'success',
+ '2': 'danger'
+ };
+ return statusMap[status];
+ },
+ statusTextFilter(status) {
+ const statusMap = {
+ '0': '寰呭鎵�',
+ '1': '宸查�氳繃',
+ '2': '宸查┏鍥�'
+ };
+ return statusMap[status];
+ }
+ },
+ data() {
+ return {
+ activeTab: 'basic',
+ genderOptions: [
+ { value: "0", label: "鐢�" },
+ { value: "1", label: "濂�" }
+ ],
+ bloodTypeOptions: [
+ { value: "A", label: "A鍨�" },
+ { value: "B", label: "B鍨�" },
+ { value: "O", label: "O鍨�" },
+ { value: "AB", label: "AB鍨�" }
+ ],
+
+ // 闄勪欢鐩稿叧鏁版嵁
+ attachmentList: [
+ {
+ id: 1,
+ fileName: "鎹愮尞鑰呰韩浠借瘉.jpg",
+ fileType: "jpg",
+ fileSize: 1024000,
+ uploadTime: "2024-12-19 10:30:00",
+ fileUrl: "https://img95.699pic.com/photo/40142/8262.jpg_wh860.jpg"
+ },
+ {
+ id: 2,
+ fileName: "鍖荤枟璇婃柇璇佹槑.pdf",
+ fileType: "pdf",
+ fileSize: 2048000,
+ uploadTime: "2024-12-19 11:20:00",
+ fileUrl:
+ "http://192.168.100.10:8080/profile/upload/2025/12/19/(鍚撮緳8.7)姣忔棩宸ヤ綔鎬荤粨1766131266142.pdf"
+ },
+ {
+ id: 3,
+ fileName: "妫�楠屾姤鍛婂崟.jpg",
+ fileType: "docx",
+ fileSize: 512000,
+ uploadTime: "2024-12-19 14:15:00",
+ fileUrl: "https://img95.699pic.com/photo/40019/3490.jpg_wh860.jpg"
+ }
+ ],
+
+ // PDF棰勮鐩稿叧鏁版嵁
+ pdfPreviewVisible: false,
+ pdfLoading: false,
+ pdfUrl: "",
+ currentPage: 1,
+ pageCount: 0,
+ scale: 100,
+ pageRotate: 0,
+
+ // 鍥剧墖棰勮鐩稿叧
+ imagePreviewVisible: false,
+
+ // 涓嶆敮鎸侀瑙堢浉鍏�
+ unsupportedPreviewVisible: false,
+
+ // 閫氱敤棰勮鏁版嵁
+ previewTitle: "",
+ previewUrl: "",
+ currentFile: null
+ };
+ },
+ methods: {
+ handleClose() {
+ this.$emit('close');
+ },
+
+ // 鑾峰彇鏂囦欢绫诲瀷
+ getFileType(fileName) {
+ const extension = fileName.split('.').pop().toLowerCase();
+ const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
+ const pdfTypes = ["pdf"];
+
+ if (imageTypes.includes(extension)) return "image";
+ if (pdfTypes.includes(extension)) return "pdf";
+ return "other";
+ },
+
+ // 鏂囦欢棰勮涓诲叆鍙�
+ handlePreview(file) {
+ this.currentFile = file;
+ this.previewTitle = `棰勮 - ${file.fileName}`;
+ this.previewUrl = file.fileUrl;
+ const fileType = this.getFileType(file.fileName);
+
+ switch (fileType) {
+ case "pdf":
+ this.previewPdf(file);
+ break;
+ case "image":
+ this.previewImage(file);
+ break;
+ default:
+ this.previewUnsupported(file);
+ break;
+ }
+ },
+
+ // PDF棰勮鏂规硶
+ previewPdf(file) {
+ this.pdfPreviewVisible = true;
+ this.pdfLoading = true;
+ this.currentPage = 1;
+ this.scale = 100;
+ this.pageRotate = 0;
+ this.pdfUrl = file.fileUrl;
+ },
+
+ // PDF鍔犺浇瀹屾垚鍥炶皟
+ loadPdfHandler() {
+ this.pdfLoading = false;
+ this.currentPage = 1;
+ },
+
+ // PDF鍔犺浇閿欒澶勭悊
+ pdfErrorHandler(error) {
+ console.error("PDF鍔犺浇澶辫触:", error);
+ this.pdfLoading = false;
+ this.$message.error("PDF鏂囦欢鍔犺浇澶辫触锛岃灏濊瘯涓嬭浇鍚庢煡鐪�");
+ this.pdfPreviewVisible = false;
+ },
+
+ // 缈婚〉鍔熻兘
+ changePage(newPage) {
+ if (newPage < 1 || newPage > this.pageCount) return;
+ this.currentPage = newPage;
+ },
+
+ // 缂╂斁鍔熻兘
+ zoomIn() {
+ if (this.scale >= 200) {
+ this.$message.info("宸叉斁澶у埌鏈�澶ф瘮渚�");
+ return;
+ }
+ this.scale += 10;
+ },
+
+ zoomOut() {
+ if (this.scale <= 50) {
+ this.$message.info("宸茬缉灏忓埌鏈�灏忔瘮渚�");
+ return;
+ }
+ this.scale -= 10;
+ },
+
+ resetZoom() {
+ this.scale = 100;
+ },
+
+ // 鍥剧墖棰勮鏂规硶
+ previewImage(file) {
+ this.imagePreviewVisible = true;
+ },
+
+ // 涓嶆敮鎸侀瑙堢殑鏂囦欢绫诲瀷
+ previewUnsupported(file) {
+ this.unsupportedPreviewVisible = true;
+ },
+
+ // PDF瀵硅瘽妗嗗叧闂鐞�
+ handlePdfDialogClose() {
+ this.pdfUrl = "";
+ this.currentPage = 1;
+ this.pageCount = 0;
+ },
+
+ // 鏂囦欢涓嬭浇
+ handleDownload(file) {
+ const link = document.createElement("a");
+ link.href = file.fileUrl;
+ link.download = file.fileName;
+ link.style.display = "none";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ this.$message.success("寮�濮嬩笅杞芥枃浠�");
+ },
+
+ // 涓撶敤PDF涓嬭浇鏂规硶
+ downloadPdf(file) {
+ this.handleDownload(file);
+ },
+
+ // 鏂囦欢鍒犻櫎
+ handleDelete(file) {
+ this.$confirm("纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }).then(() => {
+ this.attachmentList = this.attachmentList.filter(
+ item => item.id !== file.id
+ );
+ this.$message.success("鍒犻櫎鎴愬姛");
+ });
+ },
+
+ // 涓婁紶闄勪欢
+ handleUpload() {
+ this.$message.info("涓婁紶鍔熻兘寰呭疄鐜�");
+ },
+
+ // 鏍煎紡鍖栨枃浠跺ぇ灏�
+ formatFileSize(bytes) {
+ if (bytes === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ }
+ }
+};
+</script>
+
+<style scoped>
+.case-detail {
+ padding: 0 20px;
+}
+
+/* PDF棰勮瀵硅瘽妗嗘牱寮� */
+.pdf-preview-dialog {
+ margin-top: 5vh !important;
+}
+
+.pdf-preview-dialog >>> .el-dialog {
+ min-height: 80vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.pdf-preview-dialog >>> .el-dialog__body {
+ flex: 1;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.pdf-preview-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+/* PDF宸ュ叿鏍忔牱寮� */
+.pdf-toolbar {
+ padding: 15px 20px;
+ background: #f5f7fa;
+ border-bottom: 1px solid #ebeef5;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.zoom-controls {
+ margin: 0 15px;
+}
+
+/* PDF瑙嗗浘鍖哄煙鏍峰紡 */
+.pdf-viewport {
+ flex: 1;
+ overflow: auto;
+ padding: 20px;
+ background: #f8f9fa;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+}
+
+/* 鍥剧墖棰勮鏍峰紡 */
+.image-preview-container {
+ text-align: center;
+ padding: 20px;
+}
+
+.preview-image {
+ max-width: 100%;
+ max-height: 70vh;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .pdf-toolbar {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .zoom-controls {
+ margin: 10px 0;
+ }
+
+ .pdf-preview-dialog {
+ width: 95% !important;
+ }
+}
+
+.detail-footer {
+ text-align: center;
+ margin-top: 20px;
+ padding-top: 20px;
+ border-top: 1px solid #ebeef5;
+}
+
+::v-deep .el-descriptions__label {
+ width: 120px;
+ background-color: #f5f7fa;
+ font-weight: bold;
+}
+</style>
diff --git a/src/views/business/appear/index.vue b/src/views/business/appear/index.vue
new file mode 100644
index 0000000..3e975a0
--- /dev/null
+++ b/src/views/business/appear/index.vue
@@ -0,0 +1,382 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储绛涢�夊尯鍩� -->
+ <el-card class="filter-card">
+ <el-form :model="queryParams" ref="queryForm" :inline="true" class="demo-form-inline">
+ <el-form-item label="鎹愮尞缂栧彿" prop="donorNo">
+ <el-input v-model="queryParams.donorNo" placeholder="璇疯緭鍏ユ崘鐚紪鍙�" clearable style="width: 200px"/>
+ </el-form-item>
+ <el-form-item label="鎹愮尞鑰呭鍚�" prop="donorName">
+ <el-input v-model="queryParams.donorName" placeholder="璇疯緭鍏ユ崘鐚�呭鍚�" clearable style="width: 200px"/>
+ </el-form-item>
+ <el-form-item label="妗堜緥鐘舵��" prop="status">
+ <el-select v-model="queryParams.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 200px">
+ <el-option label="鍏ㄩ儴" value=""/>
+ <el-option label="寰呭鎵�" value="0"/>
+ <el-option label="宸查�氳繃" value="1"/>
+ <el-option label="宸查┏鍥�" value="2"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="el-icon-refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" plain icon="el-icon-plus" @click="handleAdd">鏂板妗堜緥</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="success" plain icon="el-icon-edit" :disabled="single" @click="handleUpdate">淇敼</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="danger" plain icon="el-icon-delete" :disabled="multiple" @click="handleDelete">鍒犻櫎</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-table v-loading="loading" :data="caseList" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center"/>
+ <el-table-column label="搴忓彿" type="index" width="60" align="center"/>
+ <el-table-column label="鎹愮尞缂栧彿" align="center" prop="donorNo" width="140"/>
+ <el-table-column label="鎹愮尞鑰呭鍚�" align="center" prop="donorName" width="100"/>
+ <el-table-column label="鎬у埆" align="center" prop="gender" width="80">
+ <template slot-scope="scope">
+ <dict-tag :options="genderOptions" :value="scope.row.gender"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="骞撮緞" align="center" prop="age" width="80"/>
+ <el-table-column label="琛�鍨�" align="center" prop="bloodType" width="80">
+ <template slot-scope="scope">
+ <dict-tag :options="bloodTypeOptions" :value="scope.row.bloodType"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐤剧梾璇婃柇" align="center" prop="diagnosis" min-width="200" show-overflow-tooltip/>
+ <el-table-column label="鍖婚櫌鍚嶇О" align="center" prop="hospitalName" width="150"/>
+ <el-table-column label="妗堜緥鐘舵��" align="center" prop="status" width="100">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.status | statusFilter">
+ {{ scope.row.status | statusTextFilter }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婃姤鏃堕棿" align="center" prop="reportTime" width="160"/>
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width" width="200">
+ <template slot-scope="scope">
+ <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">璇︽儏</el-button>
+ <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">淇敼</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-check"
+ @click="handleApprove(scope.row)"
+ v-if="scope.row.status === '0'"
+ >瀹℃壒</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ v-show="total>0"
+ :total="total"
+ :page.sync="queryParams.pageNum"
+ :limit.sync="queryParams.pageSize"
+ @pagination="getList"
+ />
+
+ <!-- 妗堜緥璇︽儏寮规 -->
+ <el-dialog
+ :title="detailTitle"
+ :visible.sync="detailOpen"
+ width="900px"
+ append-to-body
+ :close-on-click-modal="false"
+ >
+ <case-detail :caseData="currentCase" @close="detailOpen = false"/>
+ </el-dialog>
+
+ <!-- 瀹℃壒寮规 -->
+ <el-dialog
+ title="妗堜緥瀹℃壒"
+ :visible.sync="approveOpen"
+ width="500px"
+ append-to-body
+ >
+ <el-form ref="approveForm" :model="approveForm" :rules="approveRules" label-width="80px">
+ <el-form-item label="瀹℃壒缁撴灉" prop="approveResult">
+ <el-radio-group v-model="approveForm.approveResult">
+ <el-radio label="1">閫氳繃</el-radio>
+ <el-radio label="2">椹冲洖</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="瀹℃壒鎰忚" prop="approveOpinion">
+ <el-input
+ type="textarea"
+ v-model="approveForm.approveOpinion"
+ placeholder="璇疯緭鍏ュ鎵规剰瑙�"
+ :rows="4"
+ maxlength="500"
+ />
+ </el-form-item>
+ </el-form>
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="approveOpen = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="submitApprove">纭� 瀹�</el-button>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+ import CaseDetail from './caseDetail';
+export default {
+ name: "CaseList",
+ components: { CaseDetail },
+
+ data() {
+ return {
+ // 閬僵灞�
+ loading: false,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 妗堜緥琛ㄦ牸鏁版嵁
+ caseList: [],
+ // 璇︽儏寮规鏄惁鏄剧ず
+ detailOpen: false,
+ // 瀹℃壒寮规鏄惁鏄剧ず
+ approveOpen: false,
+ // 璇︽儏寮规鏍囬
+ detailTitle: "",
+ // 褰撳墠鎿嶄綔鐨勬渚�
+ currentCase: {},
+ // 鎬у埆閫夐」
+ genderOptions: [
+ { value: "0", label: "鐢�" },
+ { value: "1", label: "濂�" }
+ ],
+ // 琛�鍨嬮�夐」
+ bloodTypeOptions: [
+ { value: "A", label: "A鍨�" },
+ { value: "B", label: "B鍨�" },
+ { value: "O", label: "O鍨�" },
+ { value: "AB", label: "AB鍨�" }
+ ],
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ donorNo: undefined,
+ donorName: undefined,
+ status: undefined
+ },
+ // 瀹℃壒琛ㄥ崟
+ approveForm: {
+ caseId: null,
+ approveResult: "1",
+ approveOpinion: ""
+ },
+ // 瀹℃壒琛ㄥ崟楠岃瘉
+ approveRules: {
+ approveResult: [
+ { required: true, message: "璇烽�夋嫨瀹℃壒缁撴灉", trigger: "change" }
+ ],
+ approveOpinion: [
+ { required: true, message: "璇疯緭鍏ュ鎵规剰瑙�", trigger: "blur" }
+ ]
+ }
+ };
+ },
+ filters: {
+ statusFilter(status) {
+ const statusMap = {
+ '0': 'warning', // 寰呭鎵�
+ '1': 'success', // 宸查�氳繃
+ '2': 'danger' // 宸查┏鍥�
+ };
+ return statusMap[status];
+ },
+ statusTextFilter(status) {
+ const statusMap = {
+ '0': '寰呭鎵�',
+ '1': '宸查�氳繃',
+ '2': '宸查┏鍥�'
+ };
+ return statusMap[status];
+ }
+ },
+ created() {
+ this.getList();
+ },
+ methods: {
+ /** 鏌ヨ妗堜緥鍒楄〃 */
+ getList() {
+ this.loading = true;
+ // 妯℃嫙API璋冪敤寤惰繜
+ setTimeout(() => {
+ // 娴嬭瘯鏁版嵁
+ this.caseList = [
+ {
+ id: 1,
+ donorNo: 'DON20241219001',
+ donorName: '寮犱笁',
+ gender: '0',
+ age: 38,
+ bloodType: 'A',
+ diagnosis: '鑴戝浼ゅ鑷磋剳姝讳骸锛岀粡鎶㈡晳鏃犳晥瀹e竷鑴戞浜°�傚灞炲悓鎰忓櫒瀹樻崘鐚��',
+ hospitalName: '闈掑矝澶у闄勫睘鍖婚櫌',
+ status: '0',
+ reportTime: '2024-12-19 09:30:00',
+ reporterName: '鏉庡尰鐢�',
+ idCardNo: '370203198510123456',
+ nation: '姹夋棌',
+ phone: '13800138000',
+ address: '灞变笢鐪侀潚宀涘競甯傚崡鍖洪娓腑璺�100鍙�',
+ inpatientNo: 'ZY20241219001',
+ departmentName: '绁炵粡澶栫',
+ doctorName: '鐜嬩富浠�',
+ infectiousDisease: '鏃�',
+ medicalRecord: '鎮h�呭洜浜ら�氫簨鏁呭鑷翠弗閲嶈剳澶栦激锛岀粡鎶㈡晳鏃犳晥瀹e竷鑴戞浜°��',
+ hospitalLevel: '涓夌骇鐢茬瓑',
+ contactPerson: '寮犳姢澹�',
+ contactPhone: '13900139000',
+ hospitalAddress: '灞变笢鐪侀潚宀涘競甯傚崡鍖烘睙鑻忚矾1鍙�'
+ },
+ {
+ id: 2,
+ donorNo: 'DON20241218001',
+ donorName: '鏉庡洓',
+ gender: '1',
+ age: 45,
+ bloodType: 'O',
+ diagnosis: '鎬ユ�у績鑲屾姝伙紝蹇冭剰鍔熻兘琛扮',
+ hospitalName: '闈掑矝甯傜珛鍖婚櫌',
+ status: '1',
+ reportTime: '2024-12-18 14:20:00',
+ approveTime: '2024-12-18 16:30:00',
+ reporterName: '鍒樺尰鐢�',
+ approverName: '瀹℃牳涓撳憳A',
+ approveOpinion: '璧勬枡榻愬叏锛岀鍚堟崘鐚潯浠讹紝鍚屾剰閫氳繃銆�'
+ },
+ {
+ id: 3,
+ donorNo: 'DON20241217001',
+ donorName: '鐜嬩簲',
+ gender: '0',
+ age: 52,
+ bloodType: 'B',
+ diagnosis: '棰呭唴鍑鸿锛岃剳骞插姛鑳戒抚澶�',
+ hospitalName: '闈掑矝鐪肩鍖婚櫌',
+ status: '2',
+ reportTime: '2024-12-17 10:15:00',
+ approveTime: '2024-12-17 14:20:00',
+ reporterName: '闄堝尰鐢�',
+ approverName: '瀹℃牳涓撳憳B',
+ approveOpinion: '瀹跺睘鍚屾剰涔︿笉瀹屾暣锛岄渶琛ュ厖鏉愭枡鍚庨噸鏂版彁浜ゃ��'
+ },
+ {
+ id: 4,
+ donorNo: 'DON20241216001',
+ donorName: '璧靛叚',
+ gender: '1',
+ age: 28,
+ bloodType: 'AB',
+ diagnosis: '閲嶅瀷棰呰剳鎹熶激锛屽鍣ㄥ畼鍔熻兘琛扮',
+ hospitalName: '闈掑矝鍎跨鍖婚櫌',
+ status: '0',
+ reportTime: '2024-12-16 16:45:00',
+ reporterName: '瀛欏尰鐢�'
+ }
+ ];
+ this.total = this.caseList.length;
+ this.loading = false;
+ }, 500);
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.id);
+ this.single = selection.length !== 1;
+ this.multiple = !selection.length;
+ },
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ /** 閲嶇疆鎸夐挳鎿嶄綔 */
+ resetQuery() {
+ this.resetForm("queryForm");
+ this.handleQuery();
+ },
+ /** 璇︽儏鎸夐挳鎿嶄綔 */
+ handleDetail(row) {
+ this.currentCase = row;
+ this.detailTitle = `妗堜緥璇︽儏 - ${row.donorNo}`;
+ this.detailOpen = true;
+ },
+ /** 瀹℃壒鎸夐挳鎿嶄綔 */
+ handleApprove(row) {
+ this.currentCase = row;
+ this.approveForm.caseId = row.id;
+ this.approveForm.approveResult = "1";
+ this.approveForm.approveOpinion = "";
+ this.approveOpen = true;
+ },
+ /** 鎻愪氦瀹℃壒 */
+ submitApprove() {
+ this.$refs["approveForm"].validate(valid => {
+ if (valid) {
+ // 妯℃嫙瀹℃壒鎻愪氦
+ this.$modal.msgSuccess("瀹℃壒鎴愬姛");
+ this.approveOpen = false;
+ // 鏇存柊妗堜緥鐘舵��
+ const caseItem = this.caseList.find(item => item.id === this.approveForm.caseId);
+ if (caseItem) {
+ caseItem.status = this.approveForm.approveResult;
+ caseItem.approveTime = new Date().toLocaleString();
+ caseItem.approverName = '褰撳墠鐢ㄦ埛';
+ caseItem.approveOpinion = this.approveForm.approveOpinion;
+ }
+ }
+ });
+ },
+ /** 鏂板鎸夐挳鎿嶄綔 */
+ handleAdd() {
+ this.$router.push('/case/add');
+ },
+ /** 淇敼鎸夐挳鎿嶄綔 */
+ handleUpdate(row) {
+ const id = row.id || this.ids[0];
+ this.$router.push('/case/edit/' + id);
+ },
+ /** 鍒犻櫎鎸夐挳鎿嶄綔 */
+ handleDelete(row) {
+ const ids = row.id || this.ids;
+ this.$modal.confirm('鏄惁纭鍒犻櫎妗堜緥缂栧彿涓�"' + ids + '"鐨勬暟鎹」锛�').then(() => {
+ // 妯℃嫙鍒犻櫎鎿嶄綔
+ this.caseList = this.caseList.filter(item => !ids.includes(item.id));
+ this.total = this.caseList.length;
+ this.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ }).catch(() => {});
+ }
+ }
+};
+</script>
+
+<style scoped>
+.filter-card {
+ margin-bottom: 20px;
+}
+.mb8 {
+ margin-bottom: 8px;
+}
+</style>
diff --git a/src/views/business/transfer/index.vue b/src/views/business/transfer/index.vue
new file mode 100644
index 0000000..c4d14c4
--- /dev/null
+++ b/src/views/business/transfer/index.vue
@@ -0,0 +1,550 @@
+<template>
+ <div class="app-container">
+ <!-- 鎼滅储绛涢�夊尯鍩� -->
+ <el-card class="filter-card">
+ <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="80px">
+ <el-form-item label="杞繍鍗曞彿" prop="transportNo">
+ <el-input
+ v-model="queryParams.transportNo"
+ placeholder="璇疯緭鍏ヨ浆杩愬崟鍙�"
+ clearable
+ style="width: 200px"
+ @keyup.enter.native="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="妗堜緥缂栧彿" prop="caseNo">
+ <el-input
+ v-model="queryParams.caseNo"
+ placeholder="璇疯緭鍏ユ渚嬬紪鍙�"
+ clearable
+ style="width: 200px"
+ @keyup.enter.native="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="鎹愮尞鑰呭鍚�" prop="donorName">
+ <el-input
+ v-model="queryParams.donorName"
+ placeholder="璇疯緭鍏ユ崘鐚�呭鍚�"
+ clearable
+ style="width: 200px"
+ @keyup.enter.native="handleQuery"
+ />
+ </el-form-item>
+ <el-form-item label="杞繍鐘舵��" prop="status">
+ <el-select v-model="queryParams.status" placeholder="杞繍鐘舵��" clearable style="width: 200px">
+ <el-option label="鍏ㄩ儴" value=""/>
+ <el-option label="寰呭嚭鍙�" value="pending"/>
+ <el-option label="杞繍涓�" value="transporting"/>
+ <el-option label="宸插畬鎴�" value="completed"/>
+ <el-option label="宸插彇娑�" value="cancelled"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍒涘缓鏃堕棿">
+ <el-date-picker
+ v-model="dateRange"
+ style="width: 240px"
+ value-format="yyyy-MM-dd"
+ type="daterange"
+ range-separator="-"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ ></el-date-picker>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" @click="handleQuery">鎼滅储</el-button>
+ <el-button icon="el-icon-refresh" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 缁熻鍗$墖 -->
+ <el-row :gutter="20" class="stats-row">
+ <el-col :span="8">
+ <el-card class="stats-card total">
+ <div class="stat-content">
+ <div class="stat-icon">馃摝</div>
+ <div class="stat-info">
+ <div class="stat-count">{{ stats.totalTransports }}</div>
+ <div class="stat-label">鎬昏浆杩愬崟</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="8">
+ <el-card class="stats-card pending">
+ <div class="stat-content">
+ <div class="stat-icon">鈴�</div>
+ <div class="stat-info">
+ <div class="stat-count">{{ stats.pendingTransports }}</div>
+ <div class="stat-label">寰呭嚭鍙�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="8">
+ <el-card class="stats-card completed">
+ <div class="stat-content">
+ <div class="stat-icon">鉁�</div>
+ <div class="stat-info">
+ <div class="stat-count">{{ stats.completedTransports }}</div>
+ <div class="stat-label">宸插畬鎴�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button type="primary" plain icon="el-icon-plus" @click="handleAdd">鏂板缓杞繍鍗�</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="success" plain icon="el-icon-edit" :disabled="single" @click="handleUpdate">淇敼</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="danger" plain icon="el-icon-delete" :disabled="multiple" @click="handleDelete">鍒犻櫎</el-button>
+ </el-col>
+ <el-col :span="1.5">
+ <el-button type="warning" plain icon="el-icon-download" @click="handleExport">瀵煎嚭</el-button>
+ </el-col>
+ <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+ </el-row>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-table v-loading="loading" :data="transportList" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" align="center"/>
+ <el-table-column label="搴忓彿" type="index" width="60" align="center"/>
+ <el-table-column label="杞繍鍗曞彿" align="center" prop="id" width="140"/>
+ <el-table-column label="妗堜緥缂栧彿" align="center" prop="caseNo" width="140"/>
+ <el-table-column label="鎹愮尞鑰呬俊鎭�" align="center" width="180">
+ <template slot-scope="scope">
+ <div class="donor-info">
+ <div class="donor-name">{{ scope.row.donorName }}</div>
+ <div class="donor-details">{{ scope.row.gender }} | {{ scope.row.age }}宀�</div>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐤剧梾璇婃柇" align="center" prop="diagnosis" min-width="200" show-overflow-tooltip/>
+ <el-table-column label="鍖荤枟鏈烘瀯" align="center" prop="hospitalName" width="150"/>
+ <el-table-column label="璁″垝杞繍鏃堕棿" align="center" prop="transportTime" width="160"/>
+ <el-table-column label="璐熻矗鍗忚皟鍛�" align="center" prop="coordinator" width="100"/>
+ <el-table-column label="杞繍鐘舵��" align="center" prop="status" width="100">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.status | statusFilter">
+ {{ scope.row.statusText }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="160"/>
+ <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width" width="220">
+ <template slot-scope="scope">
+ <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">璇︽儏</el-button>
+ <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">缂栬緫</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-video-play"
+ @click="handleStartTransport(scope.row)"
+ v-if="scope.row.status === 'pending'"
+ >寮�濮嬭浆杩�</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-check"
+ @click="handleCompleteTransport(scope.row)"
+ v-if="scope.row.status === 'transporting'"
+ >瀹屾垚杞繍</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <pagination
+ v-show="total>0"
+ :total="total"
+ :page.sync="queryParams.pageNum"
+ :limit.sync="queryParams.pageSize"
+ @pagination="getList"
+ />
+
+ <!-- 杞繍鍗曡鎯呭脊妗� -->
+ <el-dialog
+ :title="detailTitle"
+ :visible.sync="detailOpen"
+ width="1000px"
+ append-to-body
+ :close-on-click-modal="false"
+ >
+ <transport-detail :transportData="currentTransport" @close="detailOpen = false"/>
+ </el-dialog>
+
+ <!-- 鎿嶄綔纭寮规 -->
+ <el-dialog
+ :title="actionTitle"
+ :visible.sync="actionOpen"
+ width="500px"
+ append-to-body
+ >
+ <div class="action-confirm">
+ <p>纭畾瑕亄{ actionText }}杞繍鍗� "{{ currentTransport.id }}" 鍚楋紵</p>
+ </div>
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="actionOpen = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="confirmAction">纭� 瀹�</el-button>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import { listTransport, getTransport, delTransport, updateTransportStatus } from "@/api/system/business";
+import TransportDetail from './transportDetail';
+
+export default {
+ name: "TransportList",
+ components: { TransportDetail },
+ data() {
+ return {
+ // 閬僵灞�
+ loading: false,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鏄剧ず鎼滅储鏉′欢
+ showSearch: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 杞繍鍗曡〃鏍兼暟鎹�
+ transportList: [],
+ // 璇︽儏寮规鏄惁鏄剧ず
+ detailOpen: false,
+ // 鎿嶄綔纭寮规鏄惁鏄剧ず
+ actionOpen: false,
+ // 璇︽儏寮规鏍囬
+ detailTitle: "",
+ // 鎿嶄綔纭鏍囬
+ actionTitle: "",
+ // 鎿嶄綔鏂囨湰
+ actionText: "",
+ // 褰撳墠鎿嶄綔鐨勮浆杩愬崟
+ currentTransport: {},
+ // 鏃ユ湡鑼冨洿
+ dateRange: [],
+ // 缁熻鏁版嵁
+ stats: {
+ totalTransports: 0,
+ pendingTransports: 0,
+ completedTransports: 0
+ },
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ transportNo: undefined,
+ caseNo: undefined,
+ donorName: undefined,
+ status: undefined
+ }
+ };
+ },
+ filters: {
+ statusFilter(status) {
+ const statusMap = {
+ 'pending': 'warning',
+ 'transporting': 'primary',
+ 'completed': 'success',
+ 'cancelled': 'danger'
+ };
+ return statusMap[status];
+ }
+ },
+ created() {
+ this.getList();
+ },
+ methods: {
+ /** 鏌ヨ杞繍鍗曞垪琛� */
+ getList() {
+ this.loading = true;
+ // 妯℃嫙API璋冪敤寤惰繜
+ setTimeout(() => {
+ // 娴嬭瘯鏁版嵁
+ this.transportList = [
+ {
+ id: 'T20241217001',
+ caseNo: 'DON20241216001',
+ donorName: '寮犱笁',
+ gender: '鐢�',
+ age: 38,
+ diagnosis: '鑴戝浼ゅ鑷磋剳姝讳骸锛岀粡鎶㈡晳鏃犳晥瀹e竷鑴戞浜°�傚灞炲悓鎰忓櫒瀹樻崘鐚��',
+ hospitalName: '闈掑矝闀滄箹鍖婚櫌',
+ transportTime: '2024-12-17 14:30:00',
+ coordinator: '寮犲尰鐢�',
+ createTime: '2024-12-16 09:30:00',
+ status: 'pending',
+ statusText: '寰呭嚭鍙�',
+ departureLocation: '闈掑矝甯傜珛鍖婚櫌鎬ヨ瘖绉�',
+ destinationHospital: '闈掑矝闀滄箹鍖婚櫌',
+ emergencyDoctor: '鐜嬪尰鐢�',
+ nurse: '鏉庢姢澹�',
+ driver: '鍒樺笀鍌�',
+ icuDoctor: '璧靛尰鐢�',
+ contacts: [
+ { role: '鍗忚皟鍛樼數璇�', phone: '13800138000' },
+ { role: '鎬ヨ瘖鍖荤敓鐢佃瘽', phone: '13800138001' },
+ { role: '鎶ゅ+鐢佃瘽', phone: '13800138002' },
+ { role: '鍙告満鐢佃瘽', phone: '13800138003' },
+ { role: 'ICU鍖荤敓鐢佃瘽', phone: '13800138004' }
+ ],
+ remarks: '闇�瑕佸噯澶囧懠鍚告満绛夋�ユ晳璁惧'
+ },
+ {
+ id: 'T20241217002',
+ caseNo: 'DON20241216002',
+ donorName: '鏉庡洓',
+ gender: '濂�',
+ age: 45,
+ diagnosis: '鑴戞姝伙紝鑴戝共鍔熻兘涓уけ',
+ hospitalName: '闈掑矝澶у闄勫睘鍖婚櫌',
+ transportTime: '2024-12-17 16:00:00',
+ coordinator: '鏉庡尰鐢�',
+ createTime: '2024-12-16 11:20:00',
+ status: 'transporting',
+ statusText: '杞繍涓�',
+ departureLocation: '闈掑矝澶у闄勫睘鍖婚櫌ICU',
+ destinationHospital: '闈掑矝鍣ㄥ畼绉绘涓績',
+ currentLocation: '闈掑矝甯傚崡鍖洪娓腑璺�',
+ estimatedTime: '30鍒嗛挓'
+ },
+ {
+ id: 'T20241216003',
+ caseNo: 'DON20241215001',
+ donorName: '鐜嬩簲',
+ gender: '鐢�',
+ age: 52,
+ diagnosis: '蹇冭剰楠ゅ仠锛屽鍣ㄥ畼鍔熻兘琛扮',
+ hospitalName: '闈掑矝甯傜珛鍖婚櫌',
+ transportTime: '2024-12-16 10:15:00',
+ coordinator: '鐜嬪尰鐢�',
+ createTime: '2024-12-15 14:45:00',
+ status: 'completed',
+ statusText: '宸插畬鎴�',
+ departureLocation: '闈掑矝甯傜珛鍖婚櫌蹇冨唴绉�',
+ destinationHospital: '闈掑矝鍣ㄥ畼绉绘涓績',
+ completedTime: '2024-12-16 12:30:00',
+ distance: '15鍏噷',
+ duration: '2灏忔椂15鍒嗛挓'
+ },
+ {
+ id: 'T20241216004',
+ caseNo: 'DON20241214001',
+ donorName: '璧靛叚',
+ gender: '濂�',
+ age: 29,
+ diagnosis: '鎬ユ�ц倽琛扮',
+ hospitalName: '闈掑矝绉戝ぇ鍖婚櫌',
+ transportTime: '2024-12-16 08:30:00',
+ coordinator: '璧靛尰鐢�',
+ createTime: '2024-12-14 16:20:00',
+ status: 'cancelled',
+ statusText: '宸插彇娑�',
+ cancelReason: '瀹跺睘涓存椂鏀瑰彉鍐冲畾'
+ }
+ ];
+
+ // 鏇存柊缁熻鏁版嵁
+ this.updateStats();
+ this.total = this.transportList.length;
+ this.loading = false;
+ }, 500);
+ },
+
+ // 鏇存柊缁熻鏁版嵁
+ updateStats() {
+ this.stats.totalTransports = this.transportList.length;
+ this.stats.pendingTransports = this.transportList.filter(item => item.status === 'pending').length;
+ this.stats.completedTransports = this.transportList.filter(item => item.status === 'completed').length;
+ },
+
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.id);
+ this.single = selection.length !== 1;
+ this.multiple = !selection.length;
+ },
+
+ /** 鎼滅储鎸夐挳鎿嶄綔 */
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+
+ /** 閲嶇疆鎸夐挳鎿嶄綔 */
+ resetQuery() {
+ this.dateRange = [];
+ this.resetForm("queryForm");
+ this.handleQuery();
+ },
+
+ /** 璇︽儏鎸夐挳鎿嶄綔 */
+ handleDetail(row) {
+ this.currentTransport = row;
+ this.detailTitle = `杞繍鍗曡鎯� - ${row.id}`;
+ this.detailOpen = true;
+ },
+
+ /** 寮�濮嬭浆杩愭搷浣� */
+ handleStartTransport(row) {
+ this.currentTransport = row;
+ this.actionTitle = '寮�濮嬭浆杩�';
+ this.actionText = '寮�濮�';
+ this.actionOpen = true;
+ },
+
+ /** 瀹屾垚杞繍鎿嶄綔 */
+ handleCompleteTransport(row) {
+ this.currentTransport = row;
+ this.actionTitle = '瀹屾垚杞繍';
+ this.actionText = '瀹屾垚';
+ this.actionOpen = true;
+ },
+
+ /** 纭鎿嶄綔 */
+ confirmAction() {
+ const index = this.transportList.findIndex(item => item.id === this.currentTransport.id);
+ if (index !== -1) {
+ if (this.actionText === '寮�濮�') {
+ this.transportList[index].status = 'transporting';
+ this.transportList[index].statusText = '杞繍涓�';
+ } else if (this.actionText === '瀹屾垚') {
+ this.transportList[index].status = 'completed';
+ this.transportList[index].statusText = '宸插畬鎴�';
+ this.transportList[index].completedTime = new Date().toLocaleString();
+ }
+
+ // 鏇存柊缁熻鏁版嵁
+ this.updateStats();
+
+ this.$modal.msgSuccess(`${this.actionText}鎴愬姛`);
+ }
+ this.actionOpen = false;
+ },
+
+ /** 鏂板鎸夐挳鎿嶄綔 */
+ handleAdd() {
+ this.$router.push('/transport/add');
+ },
+
+ /** 淇敼鎸夐挳鎿嶄綔 */
+ handleUpdate(row) {
+ const id = row.id || this.ids[0];
+ this.$router.push('/transport/edit/' + id);
+ },
+
+ /** 鍒犻櫎鎸夐挳鎿嶄綔 */
+ handleDelete(row) {
+ const ids = row.id || this.ids;
+ this.$modal.confirm('鏄惁纭鍒犻櫎杞繍鍗曠紪鍙蜂负"' + ids + '"鐨勬暟鎹」锛�').then(() => {
+ // 妯℃嫙鍒犻櫎鎿嶄綔
+ this.transportList = this.transportList.filter(item => !ids.includes(item.id));
+ this.total = this.transportList.length;
+ this.updateStats();
+ this.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ }).catch(() => {});
+ },
+
+ /** 瀵煎嚭鎸夐挳鎿嶄綔 */
+ handleExport() {
+ this.download('system/transport/export', {
+ ...this.queryParams
+ }, `transport_${new Date().getTime()}.xlsx`)
+ }
+ }
+};
+</script>
+
+<style scoped>
+.filter-card {
+ margin-bottom: 20px;
+}
+
+.stats-row {
+ margin-bottom: 20px;
+}
+
+.stats-card {
+ border-radius: 8px;
+ transition: all 0.3s ease;
+}
+
+.stats-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.stats-card.total {
+ border-left: 4px solid #409EFF;
+}
+
+.stats-card.pending {
+ border-left: 4px solid #E6A23C;
+}
+
+.stats-card.completed {
+ border-left: 4px solid #67C23A;
+}
+
+.stat-content {
+ display: flex;
+ align-items: center;
+ padding: 10px;
+}
+
+.stat-icon {
+ font-size: 40px;
+ margin-right: 15px;
+}
+
+.stat-info {
+ flex: 1;
+}
+
+.stat-count {
+ font-size: 28px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 5px;
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #909399;
+}
+
+.donor-info {
+ text-align: left;
+}
+
+.donor-name {
+ font-weight: 500;
+ margin-bottom: 4px;
+}
+
+.donor-details {
+ font-size: 12px;
+ color: #909399;
+}
+
+.mb8 {
+ margin-bottom: 8px;
+}
+
+.action-confirm {
+ text-align: center;
+ font-size: 16px;
+ padding: 20px 0;
+}
+</style>
diff --git a/src/views/business/transfer/transportDetail.vue b/src/views/business/transfer/transportDetail.vue
new file mode 100644
index 0000000..1f66b4b
--- /dev/null
+++ b/src/views/business/transfer/transportDetail.vue
@@ -0,0 +1,672 @@
+<template>
+ <div class="transport-detail">
+ <el-tabs v-model="activeTab">
+ <!-- 鍩虹淇℃伅 -->
+ <el-tab-pane label="鍩虹淇℃伅" name="basic">
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="杞繍鍗曞彿">{{
+ transportData.id
+ }}</el-descriptions-item>
+ <el-descriptions-item label="妗堜緥缂栧彿">{{
+ transportData.caseNo
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鎹愮尞鑰呭鍚�">{{
+ transportData.donorName
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鎬у埆">{{
+ transportData.gender
+ }}</el-descriptions-item>
+ <el-descriptions-item label="骞撮緞"
+ >{{ transportData.age }}宀�</el-descriptions-item
+ >
+ <el-descriptions-item label="鐤剧梾璇婃柇">{{
+ transportData.diagnosis
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鍑哄彂鍖婚櫌">{{
+ transportData.hospitalName
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鐩殑鍖婚櫌">{{
+ transportData.destinationHospital
+ }}</el-descriptions-item>
+ <el-descriptions-item label="璁″垝杞繍鏃堕棿">{{
+ transportData.transportTime
+ }}</el-descriptions-item>
+ <el-descriptions-item label="璐熻矗鍗忚皟鍛�">{{
+ transportData.coordinator
+ }}</el-descriptions-item>
+ <el-descriptions-item label="杞繍鐘舵��">
+ <el-tag :type="transportData.status | statusFilter">
+ {{ transportData.statusText }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓鏃堕棿">{{
+ transportData.createTime
+ }}</el-descriptions-item>
+ <el-descriptions-item
+ label="瀹屾垚鏃堕棿"
+ v-if="transportData.completedTime"
+ >
+ {{ transportData.completedTime }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-tab-pane>
+
+ <!-- 杞繍璇︽儏 -->
+ <el-tab-pane label="杞繍璇︽儏" name="transport">
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="鍑哄彂鍦扮偣">{{
+ transportData.departureLocation
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鐩殑鍦�">{{
+ transportData.destinationHospital
+ }}</el-descriptions-item>
+ <el-descriptions-item
+ label="褰撳墠浣嶇疆"
+ v-if="transportData.currentLocation"
+ >
+ {{ transportData.currentLocation }}
+ </el-descriptions-item>
+ <el-descriptions-item
+ label="棰勮鍒拌揪鏃堕棿"
+ v-if="transportData.estimatedTime"
+ >
+ {{ transportData.estimatedTime }}
+ </el-descriptions-item>
+ <el-descriptions-item label="杞繍璺濈" v-if="transportData.distance">
+ {{ transportData.distance }}
+ </el-descriptions-item>
+ <el-descriptions-item label="杞繍鏃堕暱" v-if="transportData.duration">
+ {{ transportData.duration }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-tab-pane>
+
+ <!-- 鍥㈤槦鎴愬憳 -->
+ <el-tab-pane label="鍥㈤槦鎴愬憳" name="team">
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鍗忚皟鍛�">{{
+ transportData.coordinator
+ }}</el-descriptions-item>
+ <el-descriptions-item label="鍗忚皟鍛樼數璇�">
+ {{ getContactPhone("鍗忚皟鍛樼數璇�") }}
+ </el-descriptions-item>
+ <el-descriptions-item
+ label="鎬ヨ瘖绉戝尰鐢�"
+ v-if="transportData.emergencyDoctor"
+ >
+ {{ transportData.emergencyDoctor }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎬ヨ瘖鍖荤敓鐢佃瘽">
+ {{ getContactPhone("鎬ヨ瘖鍖荤敓鐢佃瘽") }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎶ゅ+" v-if="transportData.nurse">
+ {{ transportData.nurse }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎶ゅ+鐢佃瘽">
+ {{ getContactPhone("鎶ゅ+鐢佃瘽") }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍙告満" v-if="transportData.driver">
+ {{ transportData.driver }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍙告満鐢佃瘽">
+ {{ getContactPhone("鍙告満鐢佃瘽") }}
+ </el-descriptions-item>
+ <el-descriptions-item
+ label="ICU璇勪及鍖荤敓"
+ v-if="transportData.icuDoctor"
+ >
+ {{ transportData.icuDoctor }}
+ </el-descriptions-item>
+ <el-descriptions-item label="ICU鍖荤敓鐢佃瘽">
+ {{ getContactPhone("ICU鍖荤敓鐢佃瘽") }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-tab-pane>
+ <el-tab-pane label="闄勪欢淇℃伅" name="attachments">
+ <el-card class="attachment-card">
+ <div slot="header" class="clearfix">
+ <span>闄勪欢鍒楄〃</span>
+ <!-- <el-button
+ style="float: right; padding: 3px 0"
+ type="text"
+ @click="handleUpload"
+ >
+ 涓婁紶闄勪欢
+ </el-button> -->
+ </div>
+
+ <el-table :data="attachmentList" style="width: 100%">
+ <el-table-column label="鏂囦欢鍚�" width="300">
+ <template slot-scope="scope">
+ <i class="el-icon-document" style="margin-right: 8px;"></i>
+ <span>{{ scope.row.fileName }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂囦欢绫诲瀷" width="120">
+ <template slot-scope="scope">
+ <el-tag size="small">{{ scope.row.fileType }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶у皬" width="100">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婁紶鏃堕棿" width="180">
+ <template slot-scope="scope">
+ <span>{{ scope.row.uploadTime }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔">
+ <template slot-scope="scope">
+ <el-button size="mini" @click="handlePreview(scope.row)"
+ >棰勮</el-button
+ >
+ <el-button
+ size="mini"
+ type="success"
+ @click="handleDownload(scope.row)"
+ >涓嬭浇</el-button
+ >
+ <el-button
+ size="mini"
+ type="danger"
+ @click="handleDelete(scope.row)"
+ >鍒犻櫎</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-tab-pane>
+ <!-- 澶囨敞淇℃伅 -->
+ <el-tab-pane label="澶囨敞淇℃伅" name="remarks" v-if="transportData.remarks">
+ <el-card>
+ <div class="remarks-content">
+ {{ transportData.remarks }}
+ </div>
+ </el-card>
+ </el-tab-pane>
+ </el-tabs>
+ <!-- PDF棰勮寮圭獥 -->
+ <el-dialog
+ :title="previewTitle"
+ :append-to-body="true"
+ :visible.sync="pdfPreviewVisible"
+ width="90%"
+ top="5vh"
+ :close-on-click-modal="true"
+ class="pdf-preview-dialog"
+ @close="handlePdfDialogClose"
+ >
+ <div class="pdf-preview-container" v-loading="pdfLoading">
+ <!-- PDF鎺у埗宸ュ叿鏍� -->
+ <div class="pdf-toolbar">
+ <el-button-group>
+ <el-button
+ size="mini"
+ @click="changePage(currentPage - 1)"
+ :disabled="currentPage <= 1"
+ icon="el-icon-arrow-left"
+ >
+ 涓婁竴椤�
+ </el-button>
+ <el-button size="mini" disabled>
+ 绗� {{ currentPage }} 椤� / 鍏� {{ pageCount }} 椤�
+ </el-button>
+ <el-button
+ size="mini"
+ @click="changePage(currentPage + 1)"
+ :disabled="currentPage >= pageCount"
+ icon="el-icon-arrow-right"
+ >
+ 涓嬩竴椤�
+ </el-button>
+ </el-button-group>
+
+ <el-button-group class="zoom-controls">
+ <el-button size="mini" @click="zoomOut" :disabled="scale <= 50">
+ <i class="el-icon-zoom-out"></i> 缂╁皬
+ </el-button>
+ <el-button size="mini" disabled> {{ scale }}% </el-button>
+ <el-button size="mini" @click="zoomIn" :disabled="scale >= 200">
+ <i class="el-icon-zoom-in"></i> 鏀惧ぇ
+ </el-button>
+ <el-button size="mini" @click="resetZoom">
+ <i class="el-icon-refresh-left"></i> 閲嶇疆
+ </el-button>
+ </el-button-group>
+
+ <el-button
+ size="mini"
+ type="success"
+ @click="downloadPdf(currentFile)"
+ icon="el-icon-download"
+ >
+ 涓嬭浇
+ </el-button>
+ </div>
+
+ <!-- PDF娓叉煋鍖哄煙 -->
+ <div class="pdf-viewport">
+ <pdf
+ ref="pdf"
+ :src="pdfUrl"
+ :page="currentPage"
+ :rotate="pageRotate"
+ @num-pages="pageCount = $event"
+ @page-loaded="currentPage = $event"
+ @loaded="loadPdfHandler"
+ @error="pdfErrorHandler"
+ :style="{
+ width: scale + '%',
+ transform: 'scale(' + scale / 100 + ')',
+ transformOrigin: '0 0'
+ }"
+ ></pdf>
+ </div>
+ </div>
+ </el-dialog>
+
+ <!-- 鍥剧墖棰勮寮圭獥锛堜娇鐢‥lement UI鑷甫棰勮锛� -->
+ <el-dialog
+ :append-to-body="true"
+ :title="previewTitle"
+ :visible.sync="imagePreviewVisible"
+ width="60%"
+ top="10vh"
+ :close-on-click-modal="true"
+ >
+ <div class="image-preview-container">
+ <img :src="previewUrl" alt="棰勮鍥剧墖" class="preview-image" />
+ </div>
+ </el-dialog>
+
+ <!-- 涓嶆敮鎸侀瑙堢殑鏂囦欢绫诲瀷 -->
+ <el-dialog
+ :title="previewTitle"
+ :visible.sync="unsupportedPreviewVisible"
+ width="400px"
+ :close-on-click-modal="true"
+ >
+ <div class="unsupported-preview">
+ <el-alert
+ title="璇ユ枃浠舵牸寮忎笉鏀寔鍦ㄧ嚎棰勮锛岃涓嬭浇鍚庢煡鐪�"
+ type="warning"
+ show-icon
+ :closable="false"
+ />
+ <div style="text-align: center; margin-top: 20px;">
+ <el-button type="primary" @click="handleDownload(currentFile)">
+ <i class="el-icon-download"></i> 涓嬭浇鏂囦欢
+ </el-button>
+ </div>
+ </div>
+ </el-dialog>
+ <div class="detail-footer">
+ <el-button @click="handleClose">鍏抽棴</el-button>
+ </div>
+ </div>
+</template>
+
+<script>
+import pdf from "vue-pdf";
+
+export default {
+ name: "TransportDetail",
+ components: {
+ pdf
+ },
+ props: {
+ transportData: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ filters: {
+ statusFilter(status) {
+ const statusMap = {
+ pending: "warning",
+ transporting: "primary",
+ completed: "success",
+ cancelled: "danger"
+ };
+ return statusMap[status];
+ }
+ },
+ data() {
+ return {
+ activeTab: "basic",
+ // 闄勪欢鐩稿叧鏁版嵁
+ attachmentList: [
+ {
+ id: 1,
+ fileName: "杞繍浜ゆ帴鍗�.jpg",
+ fileType: "docx",
+ fileSize: 102400,
+ uploadTime: "2024-12-19 10:30:00",
+ fileUrl: "https://img95.699pic.com/photo/40142/8262.jpg_wh860.jpg"
+ },
+ {
+ id: 2,
+ fileName: "鍖荤枟璁板綍.pdf",
+ fileType: "pdf",
+ fileSize: 2048000,
+ uploadTime: "2024-12-19 11:20:00",
+ fileUrl:
+ "http://192.168.100.10:8080/profile/upload/2025/12/19/(鍚撮緳8.7)姣忔棩宸ヤ綔鎬荤粨1766131266142.pdf"
+ },
+ {
+ id: 3,
+ fileName: "鎮h�呯収鐗�.jpg",
+ fileType: "jpg",
+ fileSize: 512000,
+ uploadTime: "2024-12-19 14:15:00",
+ fileUrl: "https://img95.699pic.com/photo/40019/3490.jpg_wh860.jpg"
+ }
+ ],
+ // PDF棰勮鐩稿叧鏁版嵁
+ pdfPreviewVisible: false,
+ pdfLoading: false,
+ pdfUrl: "",
+ currentPage: 1,
+ pageCount: 0,
+ scale: 100,
+ pageRotate: 0,
+
+ // 鍥剧墖棰勮鐩稿叧
+ imagePreviewVisible: false,
+
+ // 涓嶆敮鎸侀瑙堢浉鍏�
+ unsupportedPreviewVisible: false,
+
+ // 閫氱敤棰勮鏁版嵁
+ previewTitle: "",
+ previewUrl: "",
+ currentFile: null
+ };
+ },
+ methods: {
+ // 鑾峰彇鑱旂郴鏂瑰紡
+ getContactPhone(role) {
+ if (this.transportData.contacts) {
+ const contact = this.transportData.contacts.find(
+ item => item.role === role
+ );
+ return contact ? contact.phone : "鏈~鍐�";
+ }
+ return "鏈~鍐�";
+ },
+ // 鍏抽棴寮规
+ handleClose() {
+ this.$emit("close");
+ },
+ // 鑾峰彇鏂囦欢绫诲瀷
+ getFileType(fileName) {
+ const extension = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
+ const pdfTypes = ["pdf"];
+
+ if (imageTypes.includes(extension)) return "image";
+ if (pdfTypes.includes(extension)) return "pdf";
+ return "other";
+ },
+
+ // 鏂囦欢棰勮涓诲叆鍙�
+ handlePreview(file) {
+ this.currentFile = file;
+ this.previewTitle = `棰勮 - ${file.fileName}`;
+ this.previewUrl = file.fileUrl;
+ const fileType = this.getFileType(file.fileName);
+
+ switch (fileType) {
+ case "pdf":
+ this.previewPdf(file);
+ break;
+ case "image":
+ this.previewImage(file);
+ break;
+ default:
+ this.previewUnsupported(file);
+ break;
+ }
+ },
+
+ // PDF棰勮鏂规硶 [1,2](@ref)
+ previewPdf(file) {
+ this.pdfPreviewVisible = true;
+ this.pdfLoading = true;
+ this.currentPage = 1;
+ this.scale = 100;
+ this.pageRotate = 0;
+ // 璁剧疆PDF婧� [4](@ref)
+ this.pdfUrl = file.fileUrl;
+ },
+
+ // PDF鍔犺浇瀹屾垚鍥炶皟 [1,2](@ref)
+ loadPdfHandler() {
+ this.pdfLoading = false;
+ this.currentPage = 1;
+ },
+
+ // PDF鍔犺浇閿欒澶勭悊 [4](@ref)
+ pdfErrorHandler(error) {
+ console.error("PDF鍔犺浇澶辫触:", error);
+ this.pdfLoading = false;
+ this.$message.error("PDF鏂囦欢鍔犺浇澶辫触锛岃灏濊瘯涓嬭浇鍚庢煡鐪�");
+ this.pdfPreviewVisible = false;
+ },
+
+ // 缈婚〉鍔熻兘 [2](@ref)
+ changePage(newPage) {
+ if (newPage < 1 || newPage > this.pageCount) return;
+
+ this.currentPage = newPage;
+ },
+
+ // 缂╂斁鍔熻兘 [2,3](@ref)
+ zoomIn() {
+ if (this.scale >= 200) {
+ this.$message.info("宸叉斁澶у埌鏈�澶ф瘮渚�");
+ return;
+ }
+ this.scale += 10;
+ },
+
+ zoomOut() {
+ if (this.scale <= 50) {
+ this.$message.info("宸茬缉灏忓埌鏈�灏忔瘮渚�");
+ return;
+ }
+ this.scale -= 10;
+ },
+
+ resetZoom() {
+ this.scale = 100;
+ },
+
+ // 鍥剧墖棰勮鏂规硶
+ previewImage(file) {
+ this.imagePreviewVisible = true;
+ },
+
+ // 涓嶆敮鎸侀瑙堢殑鏂囦欢绫诲瀷
+ previewUnsupported(file) {
+ this.unsupportedPreviewVisible = true;
+ },
+
+ // PDF瀵硅瘽妗嗗叧闂鐞�
+ handlePdfDialogClose() {
+ this.pdfUrl = "";
+ this.currentPage = 1;
+ this.pageCount = 0;
+ },
+
+ // 鏂囦欢涓嬭浇
+ handleDownload(file) {
+ const link = document.createElement("a");
+ link.href = file.fileUrl;
+ link.download = file.fileName;
+ link.style.display = "none";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ this.$message.success("寮�濮嬩笅杞芥枃浠�");
+ },
+
+ // 涓撶敤PDF涓嬭浇鏂规硶
+ downloadPdf(file) {
+ this.handleDownload(file);
+ },
+
+ // 鏂囦欢鍒犻櫎
+ handleDelete(file) {
+ this.$confirm("纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }).then(() => {
+ this.attachmentList = this.attachmentList.filter(
+ item => item.id !== file.id
+ );
+ this.$message.success("鍒犻櫎鎴愬姛");
+ });
+ },
+ // 涓婁紶闄勪欢
+ handleUpload() {
+ this.$message.info("涓婁紶鍔熻兘寰呭疄鐜�");
+ },
+
+ // 鍘熸湁鏂规硶淇濇寔涓嶅彉
+ getContactPhone(role) {
+ if (this.transportData.contacts) {
+ const contact = this.transportData.contacts.find(
+ item => item.role === role
+ );
+ return contact ? contact.phone : "鏈~鍐�";
+ }
+ return "鏈~鍐�";
+ },
+
+ handleClose() {
+ this.$emit("close");
+ },
+
+ formatFileSize(bytes) {
+ if (bytes === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ }
+ }
+};
+</script>
+
+<style scoped>
+.transport-detail {
+ padding: 0 20px;
+}
+
+/* PDF棰勮瀵硅瘽妗嗘牱寮� */
+.pdf-preview-dialog {
+ margin-top: 5vh !important;
+}
+
+.pdf-preview-dialog >>> .el-dialog {
+ min-height: 80vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.pdf-preview-dialog >>> .el-dialog__body {
+ flex: 1;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.pdf-preview-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+/* PDF宸ュ叿鏍忔牱寮� */
+.pdf-toolbar {
+ padding: 15px 20px;
+ background: #f5f7fa;
+ border-bottom: 1px solid #ebeef5;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.zoom-controls {
+ margin: 0 15px;
+}
+
+/* PDF瑙嗗浘鍖哄煙鏍峰紡 */
+.pdf-viewport {
+ flex: 1;
+ overflow: auto;
+ padding: 20px;
+ background: #f8f9fa;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+}
+
+/* 鍥剧墖棰勮鏍峰紡 */
+.image-preview-container {
+ text-align: center;
+ padding: 20px;
+}
+
+.preview-image {
+ max-width: 100%;
+ max-height: 70vh;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .pdf-toolbar {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .zoom-controls {
+ margin: 10px 0;
+ }
+
+ .pdf-preview-dialog {
+ width: 95% !important;
+ }
+}
+
+.detail-footer {
+ text-align: center;
+ margin-top: 20px;
+ padding-top: 20px;
+ border-top: 1px solid #ebeef5;
+}
+
+.remarks-content {
+ padding: 15px;
+ line-height: 1.6;
+ color: #606266;
+}
+
+::v-deep .el-descriptions__label {
+ width: 120px;
+ background-color: #f5f7fa;
+ font-weight: bold;
+}
+</style>
--
Gitblit v1.9.3