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