WXL
5 天以前 d189796882d88080aab7fb720dcf794819b0a609
上报、转运相关
已添加6个文件
2250 ■■■■■ 文件已修改
src/api/system/business/index.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/business/transport.js 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/appear/caseDetail.vue 582 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/appear/index.vue 382 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/transfer/index.vue 550 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/transfer/transportDetail.vue 672 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/business/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
export * from "./transport";
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
  })
}
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>
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: '脑外伤导致脑死亡,经抢救无效宣布脑死亡。家属同意器官捐献。',
            hospitalName: '青岛大学附属医院',
            status: '0',
            reportTime: '2024-12-19 09:30:00',
            reporterName: '李医生',
            idCardNo: '370203198510123456',
            nation: '汉族',
            phone: '13800138000',
            address: '山东省青岛市市南区香港中路100号',
            inpatientNo: 'ZY20241219001',
            departmentName: '神经外科',
            doctorName: '王主任',
            infectiousDisease: '无',
            medicalRecord: '患者因交通事故导致严重脑外伤,经抢救无效宣布脑死亡。',
            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>
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: '脑外伤导致脑死亡,经抢救无效宣布脑死亡。家属同意器官捐献。',
            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>
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>
    <!-- å›¾ç‰‡é¢„览弹窗(使用Element 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: "患者照片.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>