From 40bd04c1299a0edf63771b90b5f9e78bfb943474 Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期日, 28 十二月 2025 17:27:57 +0800
Subject: [PATCH] 办公及业务页面推送
---
src/views/OfficeRelated/checkingIn/index.vue | 317
src/views/business/OrganUtilization/index.vue | 377 +
src/views/project/donatebaseinfo/index.vue | 2
src/views/OfficeRelated/checkingIn/components/BusinessTripTable.vue | 234
src/views/business/allocation/organAllocation.js | 329
src/views/business/GetWitness/GetWitnessInfo.vue | 1577 ++++
src/views/business/GetWitness/index.vue | 372 +
src/views/business/course/components/MedicalAssessmentStage.vue | 208
src/views/business/course/components/DonationConfirmStage.vue | 217
src/views/business/decide/DecideInfo.vue | 653 +
src/views/OfficeRelated/checkingIn/components/MileageCalculation.vue | 325
src/views/business/course/components/OrganUtilizationStage.vue | 812 ++
src/views/OfficeRelated/checkingIn/components/PersonalAttendanceTable.vue | 331
src/views/business/OrganUtilization/organUtilization.js | 439 +
src/views/business/maintain/components/LiverKidneyPanel.vue | 492 +
src/components/AttachmentPreview/ImagePreview/index.vue | 187
src/views/business/GetWitness/organProcurement.js | 353
src/views/business/course/components/EthicalReviewStage.vue | 206
src/main.js | 2
src/views/business/allocation/allocationInfo.vue | 1014 ++
src/views/business/ethicalReview/index.vue | 480 +
src/views/OfficeRelated/checkingIn/mockData.js | 165
src/views/OfficeRelated/checkingIn/components/PersonBusiness.vue | 500 +
src/views/business/maintain/components/UrineRoutinePanel.vue | 751 ++
src/components/UploadAttachment/index.vue | 35
src/views/OfficeRelated/checkingIn/components/AttendanceTable.vue | 187
src/api/case/deathJudgment.js | 0
src/views/business/ethicalReview/ethicalReviewInfo.vue | 1526 ++++
src/views/business/course/components/DeathJudgmentStage.vue | 206
src/views/OfficeRelated/checkingIn/components/AttendanceCalendar.vue | 588 +
src/views/business/ethicalReview/ethicsReview.js | 392 +
src/components/AttachmentPreview/index.vue | 154
src/views/business/OrganUtilization/OrganUtilizationInfo.vue | 1656 ++++
src/views/business/course/donationProcess.js | 453 +
src/views/business/course/components/OrganProcurementStage.vue | 499 +
src/views/business/maintain/maintainInfo.vue | 1135 ++
src/views/OfficeRelated/checkingIn/components/PersonalAttendanceReport.vue | 682 +
src/views/business/maintain/components/BloodRoutinePanel.vue | 693 +
src/views/business/affirm/mockConfirmationApi.js | 13
src/views/business/course/components/OrganAllocationStage.vue | 447 +
src/views/business/decide/mockDeathJudgmentApi.js | 391 +
src/views/business/allocation/index.vue | 372 +
src/views/business/course/components/DonorMaintenanceStage.vue | 156
src/views/business/decide/index.vue | 437 +
src/views/business/course/index.vue | 677 +
src/views/OfficeRelated/checkingIn/components/AttendanceStatistics.vue | 277
src/views/OfficeRelated/checkingIn/checkingInInfo.vue | 307
src/views/business/appear/caseDetail.vue | 115
src/views/business/course/components/BaseStage.vue | 51
src/components/AttachmentPreview/PdfPreview/index.vue | 200
50 files changed, 21,592 insertions(+), 400 deletions(-)
diff --git a/src/api/case/deathJudgment.js b/src/api/case/deathJudgment.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/api/case/deathJudgment.js
diff --git a/src/components/AttachmentPreview/ImagePreview/index.vue b/src/components/AttachmentPreview/ImagePreview/index.vue
new file mode 100644
index 0000000..fc7b872
--- /dev/null
+++ b/src/components/AttachmentPreview/ImagePreview/index.vue
@@ -0,0 +1,187 @@
+<template>
+ <div class="image-preview-container" v-loading="loading">
+ <!-- 鎺у埗宸ュ叿鏍� -->
+ <div class="image-controls">
+ <el-button-group>
+ <el-button size="mini" @click="zoomOut" :disabled="scale <= 0.2">
+ <i class="el-icon-zoom-out"></i> 缂╁皬
+ </el-button>
+ <el-button size="mini" @click="resetZoom">
+ <i class="el-icon-refresh-left"></i> 閲嶇疆 ({{ Math.round(scale * 100) }}%)
+ </el-button>
+ <el-button size="mini" @click="zoomIn" :disabled="scale >= 3">
+ <i class="el-icon-zoom-in"></i> 鏀惧ぇ
+ </el-button>
+ </el-button-group>
+
+ <el-button-group>
+ <el-button size="mini" @click="rotate(-90)">
+ <i class="el-icon-refresh-left"></i> 宸︽棆
+ </el-button>
+ <el-button size="mini" @click="rotate(90)">
+ <i class="el-icon-refresh-right"></i> 鍙虫棆
+ </el-button>
+ </el-button-group>
+
+ <el-button size="mini" type="success" @click="handleDownload" icon="el-icon-download">
+ 涓嬭浇
+ </el-button>
+ </div>
+
+ <!-- 鍥剧墖娓叉煋鍖哄煙 -->
+ <div class="image-render-area" @mousemove="onMouseMove" @mouseleave="onMouseLeave">
+ <div class="image-wrapper" :style="imageWrapperStyle">
+ <img
+ ref="imageEl"
+ :src="imageUrl"
+ :alt="fileName"
+ :style="imageStyle"
+ @load="onImageLoad"
+ @error="onImageError"
+ />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'ImagePreview',
+ props: {
+ imageUrl: {
+ type: String,
+ required: true
+ },
+ fileName: {
+ type: String,
+ default: ''
+ }
+ },
+ data() {
+ return {
+ loading: false,
+ scale: 1.0,
+ rotation: 0,
+ naturalWidth: 0,
+ naturalHeight: 0
+ }
+ },
+ computed: {
+ imageStyle() {
+ return {
+ transform: `scale(${this.scale}) rotate(${this.rotation}deg)`,
+ cursor: 'default',
+ maxWidth: '100%',
+ maxHeight: '100%',
+ transition: 'transform 0.3s ease'
+ }
+ },
+
+ imageWrapperStyle() {
+ return {
+ width: '100%',
+ height: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ overflow: 'auto'
+ }
+ }
+ },
+ methods: {
+ onImageLoad() {
+ this.loading = false;
+ },
+
+ onImageError() {
+ this.loading = false;
+ this.$message.error('鍥剧墖鍔犺浇澶辫触');
+ },
+
+ zoomIn() {
+ this.scale = Math.min(this.scale + 0.1, 3.0);
+ },
+
+ zoomOut() {
+ this.scale = Math.max(this.scale - 0.1, 0.2);
+ },
+
+ resetZoom() {
+ this.scale = 1.0;
+ this.rotation = 0;
+ },
+
+ rotate(degrees) {
+ this.rotation = (this.rotation + degrees) % 360;
+ },
+
+ onMouseMove() {
+ // 鍙坊鍔犻紶鏍囦氦浜掓晥鏋�
+ },
+
+ onMouseLeave() {
+ // 鍙坊鍔犻紶鏍囩寮�鏁堟灉
+ },
+
+ handleDownload() {
+ const link = document.createElement('a');
+ link.href = this.imageUrl;
+ link.download = this.fileName;
+ link.style.display = 'none';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ this.$message.success('寮�濮嬩笅杞芥枃浠�');
+ }
+ },
+ mounted() {
+ this.loading = true;
+ }
+}
+</script>
+
+<style scoped>
+.image-preview-container {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+}
+
+.image-controls {
+ padding: 15px 20px;
+ background: #f5f7fa;
+ border-bottom: 1px solid #ebeef5;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.image-render-area {
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+ background: #f8f9fa;
+}
+
+.image-wrapper {
+ padding: 20px;
+}
+
+.image-wrapper img {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+@media (max-width: 768px) {
+ .image-controls {
+ flex-direction: column;
+ gap: 10px;
+ }
+}
+</style>
diff --git a/src/components/AttachmentPreview/PdfPreview/index.vue b/src/components/AttachmentPreview/PdfPreview/index.vue
new file mode 100644
index 0000000..730bc5b
--- /dev/null
+++ b/src/components/AttachmentPreview/PdfPreview/index.vue
@@ -0,0 +1,200 @@
+<template>
+ <div class="pdf-preview-container" v-loading="loading">
+ <!-- 鎺у埗宸ュ叿鏍� -->
+ <div class="pdf-controls">
+ <el-button-group>
+ <el-button
+ size="mini"
+ @click="prevPage"
+ :disabled="currentPage <= 1"
+ icon="el-icon-arrow-left"
+ >
+ 涓婁竴椤�
+ </el-button>
+ <el-button size="mini" disabled>
+ 绗� {{ currentPage }} 椤� / 鍏� {{ totalPages }} 椤�
+ </el-button>
+ <el-button
+ size="mini"
+ @click="nextPage"
+ :disabled="currentPage >= totalPages"
+ 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="handleDownload" icon="el-icon-download">
+ 涓嬭浇
+ </el-button>
+ </div>
+
+ <!-- PDF娓叉煋鍖哄煙 -->
+ <div class="pdf-render-area">
+ <pdf
+ ref="pdf"
+ :src="pdfUrl"
+ :page="currentPage"
+ @num-pages="totalPages = $event"
+ @page-loaded="currentPage = $event"
+ @loaded="loadPdfHandler"
+ @error="pdfErrorHandler"
+ :style="{
+ width: scale + '%',
+ transform: 'scale(' + scale / 100 + ')',
+ transformOrigin: '0 0'
+ }"
+ ></pdf>
+ </div>
+ </div>
+</template>
+
+<script>
+import pdf from 'vue-pdf'
+
+export default {
+ name: 'PdfPreview',
+ components: {
+ pdf
+ },
+ props: {
+ pdfUrl: {
+ type: String,
+ required: true
+ },
+ fileName: {
+ type: String,
+ default: ''
+ }
+ },
+ data() {
+ return {
+ loading: false,
+ currentPage: 1,
+ totalPages: 0,
+ scale: 100
+ }
+ },
+ methods: {
+ loadPdfHandler() {
+ this.loading = false;
+ },
+
+ pdfErrorHandler(error) {
+ console.error('PDF鍔犺浇澶辫触:', error);
+ this.loading = false;
+ this.$message.error('PDF鏂囦欢鍔犺浇澶辫触锛岃灏濊瘯涓嬭浇鍚庢煡鐪�');
+ },
+
+ prevPage() {
+ if (this.currentPage > 1) {
+ this.currentPage--;
+ }
+ },
+
+ nextPage() {
+ if (this.currentPage < this.totalPages) {
+ this.currentPage++;
+ }
+ },
+
+ 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;
+ },
+
+ handleDownload() {
+ const link = document.createElement('a');
+ link.href = this.pdfUrl;
+ link.download = this.fileName;
+ link.style.display = 'none';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ this.$message.success('寮�濮嬩笅杞芥枃浠�');
+ }
+ },
+ mounted() {
+ this.loading = true;
+ },
+ watch: {
+ pdfUrl() {
+ this.loading = true;
+ this.currentPage = 1;
+ this.scale = 100;
+ }
+ }
+}
+</script>
+
+<style scoped>
+.pdf-preview-container {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.pdf-controls {
+ padding: 15px 20px;
+ background: #f5f7fa;
+ border-bottom: 1px solid #ebeef5;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.pdf-render-area {
+ flex: 1;
+ overflow: auto;
+ padding: 20px;
+ background: #f8f9fa;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+}
+
+.zoom-controls {
+ margin: 0 15px;
+}
+
+@media (max-width: 768px) {
+ .pdf-controls {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .zoom-controls {
+ margin: 10px 0;
+ }
+}
+</style>
diff --git a/src/components/AttachmentPreview/index.vue b/src/components/AttachmentPreview/index.vue
new file mode 100644
index 0000000..d2c520a
--- /dev/null
+++ b/src/components/AttachmentPreview/index.vue
@@ -0,0 +1,154 @@
+<template>
+ <el-dialog
+ :title="title"
+ :visible.sync="visible"
+ width="90%"
+ top="5vh"
+ :close-on-click-modal="true"
+ class="attachment-preview-dialog"
+ @close="handleClose"
+ >
+ <div class="attachment-preview">
+ <el-tabs v-model="activeTab" type="card">
+ <el-tab-pane
+ v-for="(file, index) in fileList"
+ :key="index"
+ :label="getTabLabel(file)"
+ :name="index.toString()"
+ >
+ <div class="preview-content">
+ <!-- PDF棰勮 -->
+ <PdfPreview
+ v-if="getFileType(file.fileName) === 'pdf'"
+ :pdf-url="file.fileUrl"
+ :file-name="file.fileName"
+ />
+
+ <!-- 鍥剧墖棰勮 -->
+ <ImagePreview
+ v-else-if="getFileType(file.fileName) === 'image'"
+ :image-url="file.fileUrl"
+ :file-name="file.fileName"
+ />
+
+ <!-- 涓嶆敮鎸侀瑙堢殑鏂囦欢绫诲瀷 -->
+ <div v-else class="unsupported-preview">
+ <el-alert
+ title="璇ユ枃浠舵牸寮忎笉鏀寔鍦ㄧ嚎棰勮锛岃涓嬭浇鍚庢煡鐪�"
+ type="warning"
+ show-icon
+ :closable="false"
+ />
+ <div class="download-action">
+ <el-button type="primary" @click="handleDownload(file)">
+ <i class="el-icon-download"></i> 涓嬭浇鏂囦欢
+ </el-button>
+ </div>
+ </div>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
+ </div>
+
+ <span slot="footer" class="dialog-footer">
+ <el-button @click="$emit('close')">鍏抽棴</el-button>
+ </span>
+ </el-dialog>
+</template>
+
+<script>
+import PdfPreview from "./PdfPreview";
+import ImagePreview from "./ImagePreview";
+
+export default {
+ name: "AttachmentPreview",
+ components: { PdfPreview, ImagePreview },
+ props: {
+ visible: Boolean,
+ fileList: {
+ type: Array,
+ default: () => []
+ },
+ title: String
+ },
+ data() {
+ return {
+ activeTab: "0"
+ };
+ },
+ methods: {
+ 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";
+ },
+
+ getTabLabel(file) {
+ const type = this.getFileType(file.fileName);
+ const icon = type === 'pdf' ? 'el-icon-document' :
+ type === 'image' ? 'el-icon-picture' : 'el-icon-files';
+ return `${file.fileName}`;
+ },
+
+ 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("寮�濮嬩笅杞芥枃浠�");
+ },
+
+ handleClose() {
+ this.activeTab = "0";
+ this.$emit('close');
+ }
+ }
+};
+</script>
+
+<style scoped>
+.attachment-preview-dialog >>> .el-dialog {
+ min-height: 80vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.attachment-preview-dialog >>> .el-dialog__body {
+ flex: 1;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.attachment-preview {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.preview-content {
+ flex: 1;
+ height: 800px;
+ padding: 0;
+}
+
+.unsupported-preview {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 40px;
+}
+
+.download-action {
+ margin-top: 20px;
+}
+</style>
diff --git a/src/components/UploadAttachment/index.vue b/src/components/UploadAttachment/index.vue
new file mode 100644
index 0000000..290362c
--- /dev/null
+++ b/src/components/UploadAttachment/index.vue
@@ -0,0 +1,35 @@
+<template>
+ <div class="upload-attachment">
+ <el-upload
+ action="#"
+ :file-list="fileList"
+ :auto-upload="false"
+ :on-change="handleFileChange"
+ :on-remove="handleFileRemove"
+ multiple
+ >
+ <el-button size="small" type="primary">鐐瑰嚮涓婁紶</el-button>
+ <div slot="tip" class="el-upload__tip">鏀寔jpg銆乸ng銆乸df銆乨oc銆乨ocx绛夋牸寮忥紝鍗曚釜鏂囦欢涓嶈秴杩�10MB</div>
+ </el-upload>
+ </div>
+</template>
+
+<script>
+export default {
+ name: "UploadAttachment",
+ props: {
+ fileList: {
+ type: Array,
+ default: () => []
+ }
+ },
+ methods: {
+ handleFileChange(file, fileList) {
+ this.$emit('change', fileList);
+ },
+ handleFileRemove(file, fileList) {
+ this.$emit('change', fileList);
+ }
+ }
+};
+</script>
diff --git a/src/main.js b/src/main.js
index 60b8011..74bada1 100644
--- a/src/main.js
+++ b/src/main.js
@@ -26,6 +26,7 @@
import { getDicts } from "@/api/system/dict/data";
import { getConfigKey } from "@/api/system/config";
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
+import { formatTime } from "@/utils/index";
import dataV from '@jiaminghi/data-view';//dataV
// 鍒嗛〉缁勪欢
import Pagination from "@/components/Pagination";
@@ -65,6 +66,7 @@
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
+Vue.prototype.formatTime = formatTime
Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel
diff --git a/src/views/OfficeRelated/checkingIn/checkingInInfo.vue b/src/views/OfficeRelated/checkingIn/checkingInInfo.vue
new file mode 100644
index 0000000..f0c23c0
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/checkingInInfo.vue
@@ -0,0 +1,307 @@
+<template>
+ <div class="attendance-detail">
+ <!-- 鍛樺伐鍩烘湰淇℃伅 -->
+ <el-card class="employee-info-card">
+ <div class="employee-header">
+ <div class="employee-basic">
+ <el-avatar :size="60" :src="employeeInfo.avatar" class="employee-avatar">
+ {{ employeeInfo.name.charAt(0) }}
+ </el-avatar>
+ <div class="employee-details">
+ <h3>{{ employeeInfo.name }}</h3>
+ <p class="employee-department">{{ employeeInfo.department }} 路 {{ employeeInfo.position }}</p>
+ <p class="employee-contact">
+ <span>宸ュ彿: {{ employeeInfo.employeeId }}</span>
+ <span>鐢佃瘽: {{ employeeInfo.phone }}</span>
+ </p>
+ </div>
+ </div>
+ <div class="employee-stats">
+ <div class="stat-item">
+ <div class="stat-value">{{ employeeStats.attendanceRate }}%</div>
+ <div class="stat-label">鏈湀鍑哄嫟鐜�</div>
+ </div>
+ <div class="stat-item">
+ <div class="stat-value">{{ employeeStats.workHours }}h</div>
+ <div class="stat-label">鎬诲伐浣滄椂闀�</div>
+ </div>
+ <div class="stat-item">
+ <div class="stat-value">{{ employeeStats.businessTripDays }}</div>
+ <div class="stat-label">鍑哄樊澶╂暟</div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+
+ <!-- 閫夐」鍗� -->
+ <el-card>
+ <el-tabs v-model="activeTab">
+ <el-tab-pane label="鏃ュ巻瑙嗗浘" name="calendar">
+ <attendance-calendar
+ :attendance-data="attendanceData"
+ :business-trip-data="businessTripData"
+ />
+ </el-tab-pane>
+
+ <el-tab-pane label="鍑哄嫟璁板綍" name="attendanceList">
+ <personal-attendance-table
+ :data="attendanceData"
+ :loading="loading"
+ />
+ </el-tab-pane>
+
+ <el-tab-pane label="鍑哄樊璁板綍" name="businessTripList">
+ <personal-business-trip-table
+ :data="businessTripData"
+ :loading="loading"
+ />
+ </el-tab-pane>
+
+ <el-tab-pane label="缁熻鎶ヨ〃" name="report">
+ <personal-attendance-report
+ :stats="employeeStats"
+ :attendance-data="attendanceData"
+ />
+ </el-tab-pane>
+ </el-tabs>
+ </el-card>
+ </div>
+</template>
+
+<script>
+ import { generateMockData } from './mockData'
+
+import AttendanceCalendar from './components/AttendanceCalendar.vue'
+import PersonalAttendanceTable from './components/PersonalAttendanceTable.vue'
+import PersonBusiness from './components/PersonBusiness.vue'
+import PersonalAttendanceReport from './components/PersonalAttendanceReport.vue'
+
+export default {
+ name: 'AttendanceDetail',
+ components: {
+ AttendanceCalendar,
+ PersonalAttendanceTable,
+ PersonBusiness,
+ PersonalAttendanceReport
+ },
+ data() {
+ return {
+ activeTab: 'calendar',
+ loading: false,
+ employeeInfo: {
+ name: '寮犱笁',
+ department: 'OPO椤圭洰閮�',
+ position: '椤圭洰缁忕悊',
+ employeeId: 'OPO001',
+ phone: '138-1234-5678',
+ avatar: ''
+ },
+ employeeStats: {
+ attendanceRate: 0,
+ workHours: 0,
+ businessTripDays: 0,
+ lateTimes: 0,
+ leaveEarlyTimes: 0
+ },
+ attendanceData: [],
+ businessTripData: []
+ }
+ },
+ created() {
+ this.loadMockData()
+
+ this.getEmployeeInfo()
+ this.loadAttendanceData()
+ },
+ methods: {
+ getEmployeeInfo() {
+ const { employeeId, employeeName } = this.$route.query
+ // 妯℃嫙鍛樺伐淇℃伅
+ this.employeeInfo = {
+ name: employeeName || '寮犱笁',
+ department: 'OPO椤圭洰閮�',
+ position: '椤圭洰缁忕悊',
+ employeeId: employeeId || 'OPO001',
+ phone: '138****1234',
+ avatar: ''
+ }
+ },
+ loadMockData() {
+ this.loading = true
+
+ // 妯℃嫙寮傛鍔犺浇
+ setTimeout(() => {
+ const mockData = generateMockData()
+ this.attendanceData = mockData.attendanceData
+ this.businessTripData = mockData.businessTripData
+ this.calculateStats()
+ this.loading = false
+ }, 500)
+ },
+
+ calculateStats() {
+ const totalDays = 31 // 12鏈堟湁31澶�
+ const attendanceDays = this.attendanceData.filter(item =>
+ item.status === 'present' || item.status === 'late'
+ ).length
+
+ const lateTimes = this.attendanceData.filter(item =>
+ item.status === 'late'
+ ).length
+
+ // 璁$畻鍑哄樊鎬诲ぉ鏁�
+ const businessTripDays = this.businessTripData.reduce((total, trip) => {
+ const start = new Date(trip.startDate)
+ const end = new Date(trip.endDate)
+ const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1
+ return total + days
+ }, 0)
+
+ // 璁$畻鎬诲伐浣滄椂闀�
+ const totalWorkHours = this.attendanceData.reduce((total, item) => {
+ return total + (item.workHours || 0)
+ }, 0)
+
+ this.employeeStats = {
+ attendanceRate: Math.round((attendanceDays / totalDays) * 100),
+ workHours: totalWorkHours.toFixed(1),
+ businessTripDays: businessTripDays,
+ lateTimes: lateTimes,
+ leaveEarlyTimes: this.attendanceData.filter(item =>
+ item.status === 'leaveEarly'
+ ).length
+ }
+ },
+ async loadAttendanceData() {
+ this.loading = true
+ try {
+ await new Promise(resolve => setTimeout(resolve, 500))
+
+ // 鐢熸垚涓汉鑰冨嫟妯℃嫙鏁版嵁
+ this.attendanceData = this.generatePersonalAttendanceData()
+ this.businessTripData = this.generatePersonalBusinessTripData()
+ this.calculateStats()
+ } catch (error) {
+ console.error('鍔犺浇鏁版嵁澶辫触:', error)
+ } finally {
+ this.loading = false
+ }
+ },
+
+ generatePersonalAttendanceData() {
+ const data = []
+ const currentMonth = 12 // 12鏈�
+
+ for (let day = 1; day <= 31; day++) {
+ if (Math.random() > 0.2) { // 80%鐨勫嚭鍕ょ巼
+ data.push({
+ id: day,
+ date: `2024-${currentMonth.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`,
+ checkIn: `08:${String(Math.floor(Math.random() * 30)).padStart(2, '0')}`,
+ checkOut: `18:${String(Math.floor(Math.random() * 30)).padStart(2, '0')}`,
+ status: Math.random() > 0.1 ? '姝e父' : '杩熷埌',
+ workHours: (8 + Math.random() * 2).toFixed(1)
+ })
+ }
+ }
+ return data
+ },
+
+ generatePersonalBusinessTripData() {
+ return [
+ {
+ id: 1,
+ tripNumber: 'BT202412001',
+ startCity: '鍖椾含',
+ endCity: '涓婃捣',
+ startDate: '2024-12-05',
+ endDate: '2024-12-08',
+ distance: 1200,
+ purpose: '瀹㈡埛浼氳',
+ status: '宸插畬鎴�'
+ },
+ {
+ id: 2,
+ tripNumber: 'BT202412002',
+ startCity: '鍖椾含',
+ endCity: '骞垮窞',
+ startDate: '2024-12-15',
+ endDate: '2024-12-18',
+ distance: 1900,
+ purpose: '椤圭洰璋冪爺',
+ status: '宸插畬鎴�'
+ }
+ ]
+ },
+
+ }
+}
+</script>
+
+<style scoped>
+.attendance-detail {
+ padding: 20px;
+}
+
+.employee-info-card {
+ margin-bottom: 20px;
+}
+
+.employee-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.employee-basic {
+ display: flex;
+ align-items: center;
+}
+
+.employee-avatar {
+ margin-right: 16px;
+ background-color: #409eff;
+}
+
+.employee-details h3 {
+ margin: 0 0 8px 0;
+ font-size: 24px;
+ color: #303133;
+}
+
+.employee-department {
+ margin: 0 0 8px 0;
+ color: #606266;
+}
+
+.employee-contact {
+ margin: 0;
+ color: #909399;
+ font-size: 14px;
+}
+
+.employee-contact span {
+ margin-right: 16px;
+}
+
+.employee-stats {
+ display: flex;
+ gap: 30px;
+}
+
+.stat-item {
+ text-align: center;
+}
+
+.stat-value {
+ font-size: 28px;
+ font-weight: bold;
+ color: #409eff;
+ margin-bottom: 4px;
+}
+
+.stat-label {
+ color: #909399;
+ font-size: 14px;
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/AttendanceCalendar.vue b/src/views/OfficeRelated/checkingIn/components/AttendanceCalendar.vue
new file mode 100644
index 0000000..c43976e
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/AttendanceCalendar.vue
@@ -0,0 +1,588 @@
+<template>
+ <div class="attendance-calendar">
+ <!-- 鏃ュ巻澶撮儴缁熻淇℃伅 -->
+ <div class="calendar-stats">
+ <div class="stat-item">
+ <span class="stat-dot present"></span>
+ <span>姝e父鍑哄嫟: {{ stats.presentDays }}澶�</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-dot absent"></span>
+ <span>缂哄嫟/寮傚父: {{ stats.absentDays }}澶�</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-dot trip"></span>
+ <span>鍑哄樊: {{ stats.tripDays }}澶�</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-dot late"></span>
+ <span>杩熷埌/鏃╅��: {{ stats.lateDays }}澶�</span>
+ </div>
+ </div>
+
+ <!-- Element UI 鏃ュ巻缁勪欢 -->
+ <el-calendar v-model="calendarValue" :first-day-of-week="1">
+ <template #date-cell="{ data }">
+ <div
+ class="calendar-date"
+ :class="getDateStatusClass(data.day)"
+ @click="handleDateClick(data)"
+ >
+ <div class="date-number">{{ data.day.split('-')[2] }}</div>
+
+ <!-- 鐘舵�佽儗鏅壊鍧� -->
+ <div class="status-background" :class="getBackgroundStatus(data.day)"></div>
+
+ <div class="date-events">
+ <!-- 鍑哄嫟鐘舵�佹爣璁� -->
+ <div v-if="getAttendanceStatus(data.day) !== 'absent'" class="status-mark">
+ <el-tooltip
+ :content="getAttendanceTooltip(data.day)"
+ placement="top"
+ >
+ <span class="status-dot" :class="getAttendanceStatus(data.day)"></span>
+ </el-tooltip>
+ </div>
+
+ <!-- 鍑哄樊鏍囪 -->
+ <div v-if="hasBusinessTrip(data.day)" class="trip-mark">
+ <el-tooltip content="鍑哄樊" placement="top">
+ <i class="el-icon-location-outline"></i>
+ </el-tooltip>
+ </div>
+
+ <!-- 缂哄嫟鏍囪 -->
+ <div v-if="getAttendanceStatus(data.day) === 'absent'" class="absent-mark">
+ <el-tooltip content="缂哄嫟" placement="top">
+ <i class="el-icon-close"></i>
+ </el-tooltip>
+ </div>
+ </div>
+
+ <!-- 绠�鐣ヤ俊鎭樉绀� -->
+ <div class="brief-info">
+ <div v-if="getAttendanceStatus(data.day) === 'present'" class="info-item present-info">
+ 鈭�
+ </div>
+ <div v-else-if="getAttendanceStatus(data.day) === 'late'" class="info-item late-info">
+ !
+ </div>
+ <div v-else-if="getAttendanceStatus(data.day) === 'absent'" class="info-item absent-info">
+ 脳
+ </div>
+ <div v-if="hasBusinessTrip(data.day)" class="info-item trip-info">
+ 鉁�
+ </div>
+ </div>
+
+ <!-- 鏃ユ湡璇︾粏淇℃伅锛堟偓娴樉绀猴級 -->
+ <div class="date-details">
+ <div
+ v-for="event in getDateEvents(data.day)"
+ :key="event.id"
+ class="detail-item"
+ >
+ <span class="detail-type">{{ event.type === 'attendance' ? '鍑哄嫟' : '鍑哄樊' }}</span>
+ <span class="detail-info">{{ event.text }}</span>
+ </div>
+ <div v-if="getDateEvents(data.day).length === 0" class="detail-item">
+ <span class="detail-type">鏃犺褰�</span>
+ </div>
+ </div>
+ </div>
+ </template>
+ </el-calendar>
+
+ <!-- 鏃ユ湡璇︽儏瀵硅瘽妗� -->
+ <el-dialog
+ :title="`${selectedDate} 鑰冨嫟璇︽儏`"
+ v-model="detailDialogVisible"
+ width="500px"
+ >
+ <div v-if="selectedDateInfo">
+ <div class="detail-section">
+ <h4>鍑哄嫟淇℃伅</h4>
+ <div v-if="selectedDateInfo.attendance">
+ <p>涓婄彮鏃堕棿: {{ selectedDateInfo.attendance.checkIn || '鏈墦鍗�' }}</p>
+ <p>涓嬬彮鏃堕棿: {{ selectedDateInfo.attendance.checkOut || '鏈墦鍗�' }}</p>
+ <p>鐘舵��:
+ <el-tag :type="getStatusTagType(selectedDateInfo.attendance.status)">
+ {{ getStatusText(selectedDateInfo.attendance.status) }}
+ </el-tag>
+ </p>
+ </div>
+ <div v-else>
+ <p class="no-data">鏃犲嚭鍕よ褰�</p>
+ </div>
+ </div>
+
+ <div class="detail-section" v-if="selectedDateInfo.businessTrip">
+ <h4>鍑哄樊淇℃伅</h4>
+ <p>鐩殑鍦�: {{ selectedDateInfo.businessTrip.destination }}</p>
+ <p>浜嬬敱: {{ selectedDateInfo.businessTrip.reason }}</p>
+ <p>閲岀▼: {{ selectedDateInfo.businessTrip.distance }}鍏噷</p>
+ </div>
+ </div>
+ <template #footer>
+ <el-button @click="detailDialogVisible = false">鍏抽棴</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'AttendanceCalendar',
+ props: {
+ attendanceData: {
+ type: Array,
+ default: () => []
+ },
+ businessTripData: {
+ type: Array,
+ default: () => []
+ }
+ },
+ data() {
+ return {
+ calendarValue: new Date(),
+ detailDialogVisible: false,
+ selectedDate: '',
+ selectedDateInfo: null,
+ stats: {
+ presentDays: 0,
+ absentDays: 0,
+ tripDays: 0,
+ lateDays: 0
+ }
+ }
+ },
+ mounted() {
+ this.calculateStats()
+ },
+ watch: {
+ attendanceData: {
+ handler() {
+ this.calculateStats()
+ },
+ deep: true
+ },
+ businessTripData: {
+ handler() {
+ this.calculateStats()
+ },
+ deep: true
+ }
+ },
+ methods: {
+ // 鑾峰彇鏃ユ湡鐘舵�佺被鍚�
+ getDateStatusClass(date) {
+ const classes = []
+ if (this.isToday(date)) {
+ classes.push('today')
+ }
+ if (this.isSelected(date)) {
+ classes.push('selected')
+ }
+
+ // 娣诲姞鐘舵�佺被鍚�
+ const status = this.getBackgroundStatus(date)
+ if (status) {
+ classes.push(status)
+ }
+
+ return classes
+ },
+
+ // 鑾峰彇鑳屾櫙鐘舵�侊紙鐢ㄤ簬鑳屾櫙鑹诧級
+ getBackgroundStatus(date) {
+ const attendance = this.attendanceData.find(item => item.date === date)
+ const hasTrip = this.hasBusinessTrip(date)
+
+ if (hasTrip) {
+ return 'has-trip'
+ }
+
+ if (attendance) {
+ switch (attendance.status) {
+ case 'present': return 'has-attendance'
+ case 'late': return 'has-late'
+ case 'absent': return 'has-absent'
+ default: return ''
+ }
+ }
+
+ return ''
+ },
+
+ // 鍒ゆ柇鏄惁涓轰粖澶�
+ isToday(date) {
+ const today = new Date()
+ const compareDate = new Date(date)
+ return today.toDateString() === compareDate.toDateString()
+ },
+
+ // 鍒ゆ柇鏄惁琚�変腑
+ isSelected(date) {
+ return this.selectedDate === date
+ },
+
+ // 鑾峰彇鑰冨嫟鐘舵��
+ getAttendanceStatus(date) {
+ const attendance = this.attendanceData.find(item => item.date === date)
+ if (!attendance) return 'absent'
+
+ switch (attendance.status) {
+ case 'present': return 'present'
+ case 'late': return 'late'
+ case 'absent': return 'absent'
+ default: return 'absent'
+ }
+ },
+
+ // 鑾峰彇鐘舵�佹枃鏈�
+ getStatusText(status) {
+ const statusMap = {
+ present: '姝e父鍑哄嫟',
+ late: '杩熷埌/鏃╅��',
+ absent: '缂哄嫟/寮傚父'
+ }
+ return statusMap[status] || '鏈煡鐘舵��'
+ },
+
+ // 鑾峰彇鑰冨嫟鐘舵�佹彁绀�
+ getAttendanceTooltip(date) {
+ const statusMap = {
+ present: '姝e父鍑哄嫟',
+ late: '杩熷埌/鏃╅��',
+ absent: '缂哄嫟/寮傚父'
+ }
+ return statusMap[this.getAttendanceStatus(date)] || '鏃犺褰�'
+ },
+
+ // 鍒ゆ柇鏄惁鏈夊嚭宸�
+ hasBusinessTrip(date) {
+ return this.businessTripData.some(item =>
+ date >= item.startDate && date <= item.endDate
+ )
+ },
+
+ // 鑾峰彇鏃ユ湡浜嬩欢
+ getDateEvents(date) {
+ const events = []
+ const attendance = this.attendanceData.find(item => item.date === date)
+
+ if (attendance) {
+ events.push({
+ id: `attendance-${date}`,
+ type: 'attendance',
+ text: `${attendance.checkIn || '鏈墦鍗�'} - ${attendance.checkOut || '鏈墦鍗�'}`
+ })
+ }
+
+ const businessTrip = this.businessTripData.find(item =>
+ date >= item.startDate && date <= item.endDate
+ )
+ if (businessTrip) {
+ events.push({
+ id: `business-trip-${date}`,
+ type: 'businessTrip',
+ text: `鍓嶅線${businessTrip.destination}`
+ })
+ }
+
+ return events
+ },
+
+ // 澶勭悊鏃ユ湡鐐瑰嚮浜嬩欢
+ handleDateClick(data) {
+ this.selectedDate = data.day
+ this.selectedDateInfo = {
+ attendance: this.attendanceData.find(item => item.date === data.day),
+ businessTrip: this.businessTripData.find(item =>
+ data.day >= item.startDate && data.day <= item.endDate
+ )
+ }
+ this.detailDialogVisible = true
+ },
+
+ // 鑾峰彇鐘舵�佹爣绛剧被鍨�
+ getStatusTagType(status) {
+ const typeMap = {
+ present: 'success',
+ late: 'warning',
+ absent: 'danger'
+ }
+ return typeMap[status] || 'info'
+ },
+
+ // 璁$畻缁熻淇℃伅
+ calculateStats() {
+ // 閲嶇疆缁熻
+ this.stats = { presentDays: 0, absentDays: 0, tripDays: 0, lateDays: 0 }
+
+ this.attendanceData.forEach(item => {
+ switch (item.status) {
+ case 'present': this.stats.presentDays++; break
+ case 'late': this.stats.lateDays++; break
+ case 'absent': this.stats.absentDays++; break
+ }
+ })
+
+ // 璁$畻鍑哄樊澶╂暟
+ const tripDays = new Set()
+ this.businessTripData.forEach(item => {
+ const start = new Date(item.startDate)
+ const end = new Date(item.endDate)
+ for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
+ tripDays.add(d.toISOString().split('T')[0])
+ }
+ })
+ this.stats.tripDays = tripDays.size
+ }
+ }
+}
+</script>
+
+<style scoped>
+.attendance-calendar {
+ padding: 20px;
+ max-width: 100%;
+}
+
+.calendar-stats {
+ display: flex;
+ justify-content: space-around;
+ margin-bottom: 20px;
+ padding: 15px;
+ background: #f5f7fa;
+ border-radius: 8px;
+}
+
+.stat-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.stat-dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.stat-dot.present { background-color: #67c23a; }
+.stat-dot.absent { background-color: #f56c6c; }
+.stat-dot.trip { background-color: #409eff; }
+.stat-dot.late { background-color: #e6a23c; }
+
+.calendar-date {
+ height: 80px;
+ padding: 4px;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.3s;
+ position: relative;
+ overflow: hidden;
+}
+
+.calendar-date:hover {
+ background-color: #f0f9ff;
+ border-color: #409eff;
+ transform: translateY(-2px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.calendar-date.today {
+ border-color: #409eff;
+ background-color: #f0f9ff;
+}
+
+.calendar-date.selected {
+ background-color: #ecf5ff;
+ border-color: #409eff;
+}
+
+/* 鐘舵�佽儗鏅壊 */
+.status-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ opacity: 0.1;
+ z-index: 0;
+}
+
+.calendar-date.has-attendance .status-background {
+ background-color: #67c23a;
+}
+
+.calendar-date.has-late .status-background {
+ background-color: #e6a23c;
+}
+
+.calendar-date.has-absent .status-background {
+ background-color: #f56c6c;
+}
+
+.calendar-date.has-trip .status-background {
+ background: linear-gradient(135deg, #409eff 0%, #67c23a 100%);
+}
+
+.date-number {
+ font-weight: bold;
+ font-size: 14px;
+ margin-bottom: 2px;
+ position: relative;
+ z-index: 1;
+}
+
+.date-events {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ position: relative;
+ z-index: 1;
+}
+
+.status-mark, .trip-mark, .absent-mark {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.status-dot.present { background-color: #67c23a; }
+.status-dot.late { background-color: #e6a23c; }
+.status-dot.absent { background-color: #f56c6c; }
+
+.trip-mark i {
+ color: #409eff;
+ font-size: 12px;
+}
+
+.absent-mark i {
+ color: #f56c6c;
+ font-size: 12px;
+}
+
+/* 绠�鐣ヤ俊鎭樉绀� */
+.brief-info {
+ position: absolute;
+ bottom: 4px;
+ right: 4px;
+ display: flex;
+ gap: 2px;
+ z-index: 1;
+}
+
+.info-item {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 10px;
+ font-weight: bold;
+}
+
+.present-info {
+ background-color: #67c23a;
+ color: white;
+}
+
+.late-info {
+ background-color: #e6a23c;
+ color: white;
+}
+
+.absent-info {
+ background-color: #f56c6c;
+ color: white;
+}
+
+.trip-info {
+ background-color: #409eff;
+ color: white;
+}
+
+.date-details {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 8px;
+ z-index: 1000;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ display: none;
+}
+
+.calendar-date:hover .date-details {
+ display: block;
+}
+
+.detail-item {
+ font-size: 12px;
+ margin-bottom: 4px;
+ display: flex;
+ align-items: center;
+}
+
+.detail-type {
+ font-weight: bold;
+ margin-right: 4px;
+ min-width: 40px;
+}
+
+.detail-info {
+ color: #606266;
+}
+
+.detail-section {
+ margin-bottom: 20px;
+}
+
+.detail-section h4 {
+ margin-bottom: 10px;
+ color: #303133;
+ border-left: 4px solid #409eff;
+ padding-left: 8px;
+}
+
+.no-data {
+ color: #909399;
+ font-style: italic;
+}
+
+:deep(.el-calendar__header) {
+ padding: 10px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+:deep(.el-calendar-day) {
+ padding: 0 !important;
+ height: 80px;
+}
+
+:deep(.el-calendar-table:not(.is-range) td) {
+ border: 1px solid #f0f0f0;
+}
+
+:deep(.el-calendar-table .el-calendar-day) {
+ height: 80px !important;
+ padding: 0 !important;
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/AttendanceStatistics.vue b/src/views/OfficeRelated/checkingIn/components/AttendanceStatistics.vue
new file mode 100644
index 0000000..10e94ca
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/AttendanceStatistics.vue
@@ -0,0 +1,277 @@
+<template>
+ <div class="attendance-statistics">
+ <div class="statistics-controls">
+ <el-date-picker
+ v-model="selectedMonth"
+ type="month"
+ placeholder="閫夋嫨缁熻鏈堜唤"
+ value-format="yyyy-MM"
+ @change="handleMonthChange"
+ />
+ <el-button type="primary" icon="el-icon-refresh" @click="refreshData">
+ 鍒锋柊鏁版嵁
+ </el-button>
+ </div>
+
+ <el-row :gutter="20" class="stats-cards">
+ <el-col :span="6">
+ <el-card shadow="hover">
+ <div class="stat-card">
+ <div class="stat-icon primary">
+ <i class="el-icon-user-solid"></i>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ stats.totalEmployees }}</div>
+ <div class="stat-label">鎬诲憳宸ユ暟</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card shadow="hover">
+ <div class="stat-card">
+ <div class="stat-icon success">
+ <i class="el-icon-success"></i>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ stats.attendanceRate }}%</div>
+ <div class="stat-label">骞冲潎鍑哄嫟鐜�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card shadow="hover">
+ <div class="stat-card">
+ <div class="stat-icon warning">
+ <i class="el-icon-warning"></i>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ stats.totalLate }}</div>
+ <div class="stat-label">鎬昏繜鍒版鏁�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card shadow="hover">
+ <div class="stat-card">
+ <div class="stat-icon danger">
+ <i class="el-icon-error"></i>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ stats.totalAbsence }}</div>
+ <div class="stat-label">鎬荤己鍕ゅぉ鏁�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" class="charts-section">
+ <el-col :span="12">
+ <el-card header="鍑哄嫟瓒嬪娍鍒嗘瀽" shadow="never">
+ <div class="chart-container">
+ <!-- 杩欓噷鍙互闆嗘垚 ECharts 鍥捐〃 -->
+ <div class="chart-placeholder">
+ <i class="el-icon-data-analysis"></i>
+ <p>鍑哄嫟瓒嬪娍鍥捐〃</p>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="12">
+ <el-card header="鑰冨嫟鍒嗗竷" shadow="never">
+ <div class="chart-container">
+ <div class="chart-placeholder">
+ <i class="el-icon-pie-chart"></i>
+ <p>鑰冨嫟鍒嗗竷鍥捐〃</p>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card header="璇︾粏缁熻琛�" shadow="never" class="detail-table">
+ <el-table :data="stats.detailData" border>
+ <el-table-column prop="department" label="閮ㄩ棬" />
+ <el-table-column prop="employeeCount" label="鍛樺伐鏁�" />
+ <el-table-column prop="attendanceDays" label="搴斿嚭鍕ゅぉ鏁�" />
+ <el-table-column prop="actualDays" label="瀹為檯鍑哄嫟" />
+ <el-table-column prop="lateTimes" label="杩熷埌娆℃暟" >
+ <template #default="scope">
+ <el-tag v-if="scope.row.lateTimes > 0" type="warning" size="small">
+ {{ scope.row.lateTimes }}
+ </el-tag>
+ <span v-else>{{ scope.row.lateTimes }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="absenceDays" label="缂哄嫟澶╂暟" >
+ <template #default="scope">
+ <el-tag v-if="scope.row.absenceDays > 0" type="danger" size="small">
+ {{ scope.row.absenceDays }}
+ </el-tag>
+ <span v-else>{{ scope.row.absenceDays }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="attendanceRate" label="鍑哄嫟鐜�" >
+ <template #default="scope">
+ <el-progress
+ :percentage="scope.row.attendanceRate"
+ :show-text="false"
+ />
+ <span>{{ scope.row.attendanceRate }}%</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'attendance-statistics',
+ props: {
+ data: {
+ type: Array,
+ default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ selectedMonth: new Date().toISOString().slice(0, 7), // 褰撳墠骞存湀
+ stats: {
+ totalEmployees: 0,
+ attendanceRate: 0,
+ totalLate: 0,
+ totalAbsence: 0,
+ detailData: []
+ }
+ }
+ },
+ watch: {
+ data: {
+ handler(newData) {
+ this.calculateStats(newData)
+ },
+ immediate: true
+ }
+ },
+ methods: {
+ calculateStats(data) {
+ // 妯℃嫙缁熻璁$畻
+ this.stats = {
+ totalEmployees: 156,
+ attendanceRate: 92.5,
+ totalLate: 24,
+ totalAbsence: 12,
+ detailData: [
+ { department: '鎶�鏈儴', employeeCount: 45, attendanceDays: 22,
+ actualDays: 41, lateTimes: 8, absenceDays: 2, attendanceRate: 93.2 },
+ { department: '甯傚満閮�', employeeCount: 32, attendanceDays: 22,
+ actualDays: 30, lateTimes: 5, absenceDays: 1, attendanceRate: 95.5 },
+ { department: '浜轰簨閮�', employeeCount: 18, attendanceDays: 22,
+ actualDays: 17, lateTimes: 3, absenceDays: 4, attendanceRate: 89.3 },
+ { department: '璐㈠姟閮�', employeeCount: 15, attendanceDays: 22,
+ actualDays: 14, lateTimes: 2, absenceDays: 1, attendanceRate: 93.8 }
+ ]
+ }
+ },
+
+ handleMonthChange(month) {
+ this.$message.info(`鍒囨崲缁熻鏈堜唤: ${month}`)
+ this.refreshData()
+ },
+
+ refreshData() {
+ this.$emit('refresh')
+ }
+ }
+}
+</script>
+
+<style scoped>
+.attendance-statistics {
+ padding: 20px;
+}
+
+.statistics-controls {
+ margin-bottom: 24px;
+ display: flex;
+ gap: 16px;
+ align-items: center;
+}
+
+.stats-cards {
+ margin-bottom: 24px;
+}
+
+.stat-card {
+ display: flex;
+ align-items: center;
+ padding: 16px;
+}
+
+.stat-icon {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16px;
+ font-size: 28px;
+ color: white;
+}
+
+.stat-icon.primary { background: #409EFF; }
+.stat-icon.success { background: #67C23A; }
+.stat-icon.warning { background: #E6A23C; }
+.stat-icon.danger { background: #F56C6C; }
+
+.stat-content {
+ flex: 1;
+}
+
+.stat-value {
+ font-size: 28px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 4px;
+}
+
+.stat-label {
+ color: #909399;
+ font-size: 14px;
+}
+
+.charts-section {
+ margin-bottom: 24px;
+}
+
+.chart-container {
+ height: 300px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.chart-placeholder {
+ text-align: center;
+ color: #909399;
+}
+
+.chart-placeholder i {
+ font-size: 48px;
+ margin-bottom: 16px;
+ display: block;
+}
+
+.detail-table {
+ margin-top: 24px;
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/AttendanceTable.vue b/src/views/OfficeRelated/checkingIn/components/AttendanceTable.vue
new file mode 100644
index 0000000..87dbcb5
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/AttendanceTable.vue
@@ -0,0 +1,187 @@
+<template>
+ <div class="attendance-table">
+ <div class="table-actions">
+ <el-button type="primary" size="small" icon="el-icon-download" @click="exportData">
+ 瀵煎嚭鏁版嵁
+ </el-button>
+ <el-button size="small" icon="el-icon-refresh" @click="$emit('refresh')">
+ 鍒锋柊
+ </el-button>
+ </div>
+
+ <el-table
+ :data="tableData"
+ border
+ v-loading="loading"
+ style="width: 100%"
+ @sort-change="handleSortChange"
+ >
+ <el-table-column prop="id" label="ID" width="80" sortable />
+ <el-table-column prop="employeeName" label="鍛樺伐濮撳悕" sortable />
+ <el-table-column prop="date" label="鏃ユ湡" sortable />
+ <el-table-column prop="checkIn" label="绛惧埌鏃堕棿" />
+ <el-table-column prop="checkOut" label="绛鹃��鏃堕棿" />
+ <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" sortable>
+ <template #default="scope">
+ <el-tag :type="getHoursType(scope.row.workHours)" size="small">
+ {{ scope.row.workHours }}h
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="status" label="鐘舵��" >
+ <template #default="scope">
+ <el-tag
+ :type="getStatusType(scope.row.status)"
+ effect="light"
+ >
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150" fixed="right">
+ <template #default="scope">
+ <el-button
+ size="mini"
+ type="text"
+ @click="handleViewDetail(scope.row)"
+ icon="el-icon-view"
+ >
+ 璇︽儏
+ </el-button>
+ <el-button
+ size="mini"
+ type="text"
+ @click="handleEdit(scope.row)"
+ icon="el-icon-edit"
+ >
+ 缂栬緫
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination-container">
+ <el-pagination
+ :current-page="currentPage"
+ :page-size="pageSize"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'AttendanceTable',
+ props: {
+ data: {
+ type: Array,
+ default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ currentPage: 1,
+ pageSize: 10,
+ sortProp: '',
+ sortOrder: ''
+ }
+ },
+ computed: {
+ total() {
+ return this.data.length
+ },
+ tableData() {
+ let data = [...this.data]
+
+ // 鎺掑簭澶勭悊
+ if (this.sortProp) {
+ data.sort((a, b) => {
+ let aVal = a[this.sortProp]
+ let bVal = b[this.sortProp]
+
+ if (this.sortOrder === 'ascending') {
+ return aVal > bVal ? 1 : -1
+ } else {
+ return aVal < bVal ? 1 : -1
+ }
+ })
+ }
+
+ // 鍒嗛〉澶勭悊
+ const start = (this.currentPage - 1) * this.pageSize
+ const end = start + this.pageSize
+ return data.slice(start, end)
+ }
+ },
+ methods: {
+ getStatusType(status) {
+ const typeMap = {
+ '姝e父': 'success',
+ '杩熷埌': 'warning',
+ '鏃╅��': 'warning',
+ '缂哄嫟': 'danger'
+ }
+ return typeMap[status] || 'info'
+ },
+
+ getHoursType(hours) {
+ const numHours = parseFloat(hours)
+ if (numHours >= 8) return 'success'
+ if (numHours >= 6) return 'warning'
+ return 'danger'
+ },
+
+ handleViewDetail(row) {
+ this.$emit('view-detail', row)
+ },
+
+ handleEdit(row) {
+ this.$message.info(`缂栬緫鍑哄嫟璁板綍: ${row.employeeName} - ${row.date}`)
+ },
+
+ exportData() {
+ this.$message.success('瀵煎嚭鍔熻兘寮�鍙戜腑')
+ },
+
+ handleSortChange({ prop, order }) {
+ this.sortProp = prop
+ this.sortOrder = order
+ },
+
+ handleSizeChange(size) {
+ this.pageSize = size
+ this.currentPage = 1
+ },
+
+ handleCurrentChange(page) {
+ this.currentPage = page
+ }
+ }
+}
+</script>
+
+<style scoped>
+.attendance-table {
+ padding: 20px;
+}
+
+.table-actions {
+ margin-bottom: 16px;
+ display: flex;
+ gap: 10px;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/BusinessTripTable.vue b/src/views/OfficeRelated/checkingIn/components/BusinessTripTable.vue
new file mode 100644
index 0000000..a07a9d4
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/BusinessTripTable.vue
@@ -0,0 +1,234 @@
+<template>
+ <div class="business-trip-table">
+ <div class="table-header">
+ <div class="header-actions">
+ <el-button type="primary" size="small" icon="el-icon-plus" @click="handleAddTrip">
+ 鏂板鍑哄樊
+ </el-button>
+ <el-button size="small" icon="el-icon-download" @click="exportData">
+ 瀵煎嚭鏁版嵁
+ </el-button>
+ </div>
+
+ <div class="header-filters">
+ <el-input
+ v-model="filters.employeeName"
+ placeholder="鎼滅储鍛樺伐濮撳悕"
+ prefix-icon="el-icon-search"
+ style="width: 200px"
+ clearable
+ />
+ <el-select v-model="filters.status" placeholder="鐘舵�佺瓫閫�" clearable>
+ <el-option label="杩涜涓�" value="杩涜涓�" />
+ <el-option label="宸插畬鎴�" value="宸插畬鎴�" />
+ </el-select>
+ </div>
+ </div>
+
+ <el-table
+ :data="filteredData"
+ border
+ v-loading="loading"
+ style="width: 100%"
+ >
+ <el-table-column prop="tripNumber" label="鍑哄樊鍗曞彿" width="140" />
+ <el-table-column prop="employeeName" label="鍛樺伐濮撳悕" />
+ <el-table-column prop="startCity" label="鍑哄彂鍩庡競" />
+ <el-table-column prop="endCity" label="鐩殑鍩庡競" />
+ <el-table-column prop="startDate" label="寮�濮嬫棩鏈�" sortable />
+ <el-table-column prop="endDate" label="缁撴潫鏃ユ湡" sortable />
+ <el-table-column prop="duration" label="鍑哄樊澶╂暟" >
+ <template #default="scope">
+ <el-tag size="small">{{ calculateDuration(scope.row) }}澶�</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="distance" label="閲岀▼(km)" sortable />
+ <el-table-column prop="status" label="鐘舵��" >
+ <template #default="scope">
+ <el-tag
+ :type="scope.row.status === '宸插畬鎴�' ? 'success' : 'primary'"
+ effect="light"
+ >
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="180" fixed="right">
+ <template #default="scope">
+ <el-button
+ size="mini"
+ type="text"
+ @click="handleViewDetail(scope.row)"
+ icon="el-icon-view"
+ >
+ 璇︽儏
+ </el-button>
+ <el-button
+ size="mini"
+ type="text"
+ @click="handleEdit(scope.row)"
+ icon="el-icon-edit"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ size="mini"
+ type="text"
+ @click="handleDelete(scope.row)"
+ icon="el-icon-delete"
+ style="color: #f56c6c;"
+ >
+ 鍒犻櫎
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="pagination-container">
+ <el-pagination
+ :current-page="currentPage"
+ :page-size="pageSize"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'BusinessTripTable',
+ props: {
+ data: {
+ type: Array,
+ default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ currentPage: 1,
+ pageSize: 10,
+ filters: {
+ employeeName: '',
+ status: ''
+ }
+ }
+ },
+ computed: {
+ filteredData() {
+ let data = this.data.filter(item => {
+ const nameMatch = !this.filters.employeeName ||
+ item.employeeName.includes(this.filters.employeeName)
+ const statusMatch = !this.filters.status ||
+ item.status === this.filters.status
+ return nameMatch && statusMatch
+ })
+
+ const start = (this.currentPage - 1) * this.pageSize
+ const end = start + this.pageSize
+ return data.slice(start, end)
+ },
+ total() {
+ return this.data.filter(item => {
+ const nameMatch = !this.filters.employeeName ||
+ item.employeeName.includes(this.filters.employeeName)
+ const statusMatch = !this.filters.status ||
+ item.status === this.filters.status
+ return nameMatch && statusMatch
+ }).length
+ }
+ },
+ methods: {
+ calculateDuration(row) {
+ const start = new Date(row.startDate)
+ const end = new Date(row.endDate)
+ const duration = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1
+ return duration
+ },
+
+ handleViewDetail(row) {
+ this.$emit('view-detail', row)
+ },
+
+ handleAddTrip() {
+ this.$message.info('鎵撳紑鏂板鍑哄樊瀵硅瘽妗�')
+ },
+
+ handleEdit(row) {
+ this.$message.info(`缂栬緫鍑哄樊璁板綍: ${row.tripNumber}`)
+ },
+
+ handleDelete(row) {
+ this.$confirm('纭畾瑕佸垹闄よ繖鏉″嚭宸褰曞悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.$message.success('鍒犻櫎鎴愬姛')
+ }).catch(() => {})
+ },
+
+ exportData() {
+ this.$message.success('瀵煎嚭鍔熻兘寮�鍙戜腑')
+ },
+
+ handleSizeChange(size) {
+ this.pageSize = size
+ this.currentPage = 1
+ },
+
+ handleCurrentChange(page) {
+ this.currentPage = page
+ }
+ }
+}
+</script>
+
+<style scoped>
+.business-trip-table {
+ padding: 20px;
+}
+
+.table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+ flex-wrap: wrap;
+ gap: 16px;
+}
+
+.header-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.header-filters {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.pagination-container {
+ margin-top: 20px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+@media (max-width: 768px) {
+ .table-header {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .header-actions {
+ justify-content: space-between;
+ }
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/MileageCalculation.vue b/src/views/OfficeRelated/checkingIn/components/MileageCalculation.vue
new file mode 100644
index 0000000..be879ec
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/MileageCalculation.vue
@@ -0,0 +1,325 @@
+<template>
+ <div class="mileage-calculation">
+ <div class="calculation-header">
+ <h3>鍑哄樊閲岀▼鏍哥畻</h3>
+ <div class="header-actions">
+ <el-button type="primary" icon="el-icon-calculator" @click="recalculateAll">
+ 閲嶆柊璁$畻閲岀▼
+ </el-button>
+ <el-button icon="el-icon-download" @click="exportReport">
+ 瀵煎嚭鏍哥畻鎶ュ憡
+ </el-button>
+ </div>
+ </div>
+
+ <div class="filters-section">
+ <el-form :model="filters" inline>
+ <el-form-item label="鍛樺伐濮撳悕">
+ <el-input
+ v-model="filters.employeeName"
+ placeholder="杈撳叆鍛樺伐濮撳悕"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鏃堕棿鑼冨洿">
+ <el-date-picker
+ v-model="filters.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleFilter">鏌ヨ</el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <el-table
+ :data="filteredMileageData"
+ border
+ v-loading="loading"
+ style="width: 100%"
+ >
+ <el-table-column prop="tripNumber" label="鍑哄樊鍗曞彿" width="140" />
+ <el-table-column prop="employeeName" label="鍛樺伐濮撳悕" />
+ <el-table-column prop="startCity" label="鍑哄彂鍩庡競" />
+ <el-table-column prop="endCity" label="鐩殑鍩庡競" />
+ <el-table-column prop="startDate" label="寮�濮嬫棩鏈�" />
+ <el-table-column prop="endDate" label="缁撴潫鏃ユ湡" />
+ <el-table-column prop="calculatedDistance" label="鏍哥畻閲岀▼(km)" sortable>
+ <template #default="scope">
+ <el-tag type="info" size="small">
+ {{ scope.row.calculatedDistance }}km
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="fuelCost" label="鐕冩补璐圭敤(鍏�)" sortable>
+ <template #default="scope">
+ <span class="amount">楼{{ scope.row.fuelCost }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="allowance" label="鍑哄樊琛ヨ创(鍏�)" sortable>
+ <template #default="scope">
+ <span class="amount">楼{{ scope.row.allowance }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="totalCost" label="鎬昏垂鐢�(鍏�)" sortable>
+ <template #default="scope">
+ <span class="amount total">楼{{ scope.row.totalCost }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150" fixed="right">
+ <template #default="scope">
+ <el-button
+ size="mini"
+ type="text"
+ @click="handleRecalculate(scope.row)"
+ icon="el-icon-refresh"
+ >
+ 閲嶆柊璁$畻
+ </el-button>
+ <el-button
+ size="mini"
+ type="text"
+ @click="handleDetail(scope.row)"
+ icon="el-icon-document"
+ >
+ 鏄庣粏
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div class="summary-section">
+ <el-card shadow="never">
+ <h4>璐圭敤姹囨��</h4>
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="summary-item">
+ <span class="label">鎬婚噷绋�:</span>
+ <span class="value">{{ totalMileage }}鍏噷</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="summary-item">
+ <span class="label">鐕冩补璐圭敤:</span>
+ <span class="value">楼{{ totalFuelCost }}</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="summary-item">
+ <span class="label">鍑哄樊琛ヨ创:</span>
+ <span class="value">楼{{ totalAllowance }}</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="summary-item">
+ <span class="label">鎬昏垂鐢�:</span>
+ <span class="value total">楼{{ totalCost }}</span>
+ </div>
+ </el-col>
+ </el-row>
+ </el-card>
+ </div>
+
+ <div class="pagination-container">
+ <el-pagination
+ :current-page="currentPage"
+ :page-size="pageSize"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'MileageCalculation',
+ props: {
+ data: {
+ type: Array,
+ default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ currentPage: 1,
+ pageSize: 10,
+ filters: {
+ employeeName: '',
+ dateRange: []
+ }
+ }
+ },
+ computed: {
+ filteredMileageData() {
+ let data = this.data.filter(item => {
+ const nameMatch = !this.filters.employeeName ||
+ item.employeeName.includes(this.filters.employeeName)
+ const dateMatch = !this.filters.dateRange || this.filters.dateRange.length !== 2 ||
+ (item.startDate >= this.filters.dateRange[0] && item.endDate <= this.filters.dateRange[1])
+ return nameMatch && dateMatch
+ })
+
+ const start = (this.currentPage - 1) * this.pageSize
+ const end = start + this.pageSize
+ return data.slice(start, end)
+ },
+ total() {
+ return this.data.filter(item => {
+ const nameMatch = !this.filters.employeeName ||
+ item.employeeName.includes(this.filters.employeeName)
+ const dateMatch = !this.filters.dateRange || this.filters.dateRange.length !== 2 ||
+ (item.startDate >= this.filters.dateRange[0] && item.endDate <= this.filters.dateRange[1])
+ return nameMatch && dateMatch
+ }).length
+ },
+ totalMileage() {
+ return this.filteredMileageData.reduce((sum, item) =>
+ sum + parseFloat(item.calculatedDistance), 0
+ ).toFixed(2)
+ },
+ totalFuelCost() {
+ return this.filteredMileageData.reduce((sum, item) =>
+ sum + parseFloat(item.fuelCost), 0
+ ).toFixed(2)
+ },
+ totalAllowance() {
+ return this.filteredMileageData.reduce((sum, item) =>
+ sum + parseFloat(item.allowance), 0
+ ).toFixed(2)
+ },
+ totalCost() {
+ return this.filteredMileageData.reduce((sum, item) =>
+ sum + parseFloat(item.totalCost), 0
+ ).toFixed(2)
+ }
+ },
+ methods: {
+ handleFilter() {
+ this.currentPage = 1
+ },
+
+ handleReset() {
+ this.filters = {
+ employeeName: '',
+ dateRange: []
+ }
+ this.currentPage = 1
+ },
+
+ recalculateAll() {
+ this.$confirm('纭畾瑕侀噸鏂拌绠楁墍鏈夊嚭宸褰曠殑閲岀▼鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.$message.success('閲岀▼閲嶆柊璁$畻瀹屾垚')
+ }).catch(() => {})
+ },
+
+ handleRecalculate(row) {
+ this.$message.info(`閲嶆柊璁$畻 ${row.employeeName} 鐨勯噷绋媊)
+ },
+
+ handleDetail(row) {
+ this.$message.info(`鏌ョ湅 ${row.tripNumber} 鐨勮缁嗚垂鐢ㄦ槑缁哷)
+ },
+
+ exportReport() {
+ this.$message.success('瀵煎嚭鍔熻兘寮�鍙戜腑')
+ },
+
+ handleSizeChange(size) {
+ this.pageSize = size
+ this.currentPage = 1
+ },
+
+ handleCurrentChange(page) {
+ this.currentPage = page
+ }
+ }
+}
+</script>
+
+<style scoped>
+.mileage-calculation {
+ padding: 20px;
+}
+
+.calculation-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+}
+
+.calculation-header h3 {
+ margin: 0;
+ color: #303133;
+}
+
+.header-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.filters-section {
+ margin-bottom: 20px;
+ padding: 16px;
+ background: #f8f9fa;
+ border-radius: 4px;
+}
+
+.amount {
+ font-weight: 600;
+ color: #409EFF;
+}
+
+.amount.total {
+ color: #F56C6C;
+ font-size: 14px;
+}
+
+.summary-section {
+ margin: 24px 0;
+}
+
+.summary-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px 0;
+}
+
+.summary-item .label {
+ color: #606266;
+}
+
+.summary-item .value {
+ font-weight: 600;
+ color: #303133;
+}
+
+.summary-item .value.total {
+ color: #F56C6C;
+ font-size: 16px;
+}
+
+.pagination-container {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 20px;
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/PersonBusiness.vue b/src/views/OfficeRelated/checkingIn/components/PersonBusiness.vue
new file mode 100644
index 0000000..2165dbc
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/PersonBusiness.vue
@@ -0,0 +1,500 @@
+<template>
+ <div class="personal-business-trip-table">
+ <div class="table-header">
+ <h4>鍑哄樊璁板綍</h4>
+ <div class="header-actions">
+ <el-button
+ size="small"
+ type="primary"
+ @click="handleAddTrip"
+ icon="el-icon-plus"
+ >
+ 鏂板鍑哄樊
+ </el-button>
+ <el-button size="small" @click="exportToExcel" icon="el-icon-download">
+ 瀵煎嚭Excel
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 绛涢�夋潯浠� -->
+ <el-card class="filter-card" shadow="never">
+ <el-form :model="filterForm" inline>
+ <el-form-item label="鍑哄樊鏃ユ湡">
+ <el-date-picker
+ v-model="filterForm.dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ />
+ </el-form-item>
+ <el-form-item label="鐩殑鍦�">
+ <el-input
+ v-model="filterForm.destination"
+ placeholder="杈撳叆鐩殑鍦�"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鐘舵��">
+ <el-select v-model="filterForm.status" clearable>
+ <el-option label="鍏ㄩ儴" value="" />
+ <el-option label="杩涜涓�" value="杩涜涓�" />
+ <el-option label="宸插畬鎴�" value="宸插畬鎴�" />
+ <el-option label="宸插彇娑�" value="宸插彇娑�" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleFilter">鏌ヨ</el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <el-table
+ :data="filteredTrips"
+ border
+ style="width: 100%"
+ v-loading="loading"
+ class="business-trip-table"
+ >
+ <el-table-column prop="tripNumber" label="鍑哄樊鍗曞彿" width="140" fixed>
+ <template #default="scope">
+ <el-tag type="info" size="small">{{ scope.row.tripNumber }}</el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="startCity" label="鍑哄彂鍩庡競" >
+ <template #default="scope">
+ <span class="city-cell">{{ scope.row.startCity }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="endCity" label="鐩殑鍩庡競" >
+ <template #default="scope">
+ <span class="city-cell">{{ scope.row.endCity }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="startDate" label="寮�濮嬫棩鏈�" sortable>
+ <template #default="scope">
+ <span class="date-cell">{{ scope.row.startDate }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="endDate" label="缁撴潫鏃ユ湡" sortable>
+ <template #default="scope">
+ <span class="date-cell">{{ scope.row.endDate }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="duration" label="鍑哄樊澶╂暟" >
+ <template #default="scope">
+ <el-tag>{{ scope.row.duration }}澶�</el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="distance" label="閲岀▼(km)" sortable>
+ <template #default="scope">
+ <span class="distance-cell">{{ scope.row.distance }}km</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ prop="purpose"
+ label="鍑哄樊鐩殑"
+ min-width="150"
+ show-overflow-tooltip
+ >
+ <template #default="scope">
+ <span class="purpose-cell">{{ scope.row.purpose }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="status" label="鐘舵��" fixed="right">
+ <template #default="scope">
+ <el-tag :type="getStatusType(scope.row.status)" effect="light">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鎿嶄綔" width="150" fixed="right">
+ <template #default="scope">
+ <el-button
+ size="mini"
+ type="text"
+ @click="viewTripDetails(scope.row)"
+ icon="el-icon-view"
+ >
+ 璇︽儏
+ </el-button>
+ <el-button
+ size="mini"
+ type="text"
+ @click="editTrip(scope.row)"
+ icon="el-icon-edit"
+ :disabled="scope.row.status === '宸插畬鎴�'"
+ >
+ 缂栬緫
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-container">
+ <el-pagination
+ :current-page="currentPage"
+ :page-size="pageSize"
+ :total="totalRecords"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+
+ <!-- 鍑哄樊璇︽儏瀵硅瘽妗� -->
+ <el-dialog
+ :title="`鍑哄樊璇︽儏 - ${currentTrip ? currentTrip.tripNumber : ''}`"
+ :visible.sync="detailDialogVisible"
+ width="600px"
+ >
+ <div v-if="currentTrip" class="trip-details">
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鍑哄樊鍗曞彿">
+ {{ currentTrip.tripNumber }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鐘舵��">
+ <el-tag :type="getStatusType(currentTrip.status)">
+ {{ currentTrip.status }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍑哄彂鍩庡競">
+ {{ currentTrip.startCity }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鐩殑鍩庡競">
+ {{ currentTrip.endCity }}
+ </el-descriptions-item>
+ <el-descriptions-item label="寮�濮嬫棩鏈�">
+ {{ currentTrip.startDate }}
+ </el-descriptions-item>
+ <el-descriptions-item label="缁撴潫鏃ユ湡">
+ {{ currentTrip.endDate }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍑哄樊澶╂暟">
+ {{ currentTrip.duration }}澶�
+ </el-descriptions-item>
+ <el-descriptions-item label="閲岀▼璺濈">
+ {{ currentTrip.distance }}鍏噷
+ </el-descriptions-item>
+ <el-descriptions-item label="鍑哄樊鐩殑" :span="2">
+ {{ currentTrip.purpose }}
+ </el-descriptions-item>
+ <el-descriptions-item label="澶囨敞" :span="2">
+ {{ currentTrip.remarks || "鏃�" }}
+ </el-descriptions-item>
+ </el-descriptions>
+
+ <div
+ v-if="currentTrip.expenses && currentTrip.expenses.length > 0"
+ class="expenses-section"
+ >
+ <h5>璐圭敤鏄庣粏</h5>
+ <el-table :data="currentTrip.expenses" size="small">
+ <el-table-column prop="item" label="璐圭敤椤圭洰" />
+ <el-table-column prop="amount" label="閲戦" />
+ <el-table-column prop="date" label="鏃ユ湡" />
+ </el-table>
+ </div>
+ </div>
+ </el-dialog>
+
+ <!-- 鏂板/缂栬緫鍑哄樊瀵硅瘽妗� -->
+ <el-dialog
+ :title="isEditing ? '缂栬緫鍑哄樊璁板綍' : '鏂板鍑哄樊璁板綍'"
+ :visible.sync="editDialogVisible"
+ width="500px"
+ >
+ <el-form
+ :model="tripForm"
+ :rules="tripRules"
+ ref="tripForm"
+ label-width="100px"
+ >
+ <el-form-item label="鐩殑鍩庡競" prop="endCity">
+ <el-input v-model="tripForm.endCity" placeholder="璇疯緭鍏ョ洰鐨勫煄甯�" />
+ </el-form-item>
+ <el-form-item label="寮�濮嬫棩鏈�" prop="startDate">
+ <el-date-picker
+ v-model="tripForm.startDate"
+ type="date"
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="缁撴潫鏃ユ湡" prop="endDate">
+ <el-date-picker
+ v-model="tripForm.endDate"
+ type="date"
+ placeholder="閫夋嫨缁撴潫鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="鍑哄樊鐩殑" prop="purpose">
+ <el-input
+ v-model="tripForm.purpose"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ュ嚭宸洰鐨�"
+ />
+ </el-form-item>
+ </el-form>
+ <span slot="footer">
+ <el-button @click="editDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="saveTrip">淇濆瓨</el-button>
+ </span>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+export default {
+ name: "PersonBusiness",
+ props: {
+ data: {
+ type: Array,
+ default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ currentPage: 1,
+ pageSize: 10,
+ filterForm: {
+ dateRange: [],
+ destination: "",
+ status: ""
+ },
+ detailDialogVisible: false,
+ editDialogVisible: false,
+ isEditing: false,
+ currentTrip: null,
+ tripForm: {
+ endCity: "",
+ startDate: "",
+ endDate: "",
+ purpose: ""
+ },
+ tripRules: {
+ endCity: [
+ { required: true, message: "璇疯緭鍏ョ洰鐨勫煄甯�", trigger: "blur" }
+ ],
+ startDate: [
+ { required: true, message: "璇烽�夋嫨寮�濮嬫棩鏈�", trigger: "change" }
+ ],
+ endDate: [
+ { required: true, message: "璇烽�夋嫨缁撴潫鏃ユ湡", trigger: "change" }
+ ],
+ purpose: [
+ { required: true, message: "璇疯緭鍏ュ嚭宸洰鐨�", trigger: "blur" }
+ ]
+ }
+ };
+ },
+ computed: {
+ totalRecords() {
+ return this.filteredTrips.length;
+ },
+ filteredTrips() {
+ let filtered = this.data;
+
+ // 鎸夋棩鏈熻寖鍥磋繃婊�
+ if (this.filterForm.dateRange && this.filterForm.dateRange.length === 2) {
+ const [start, end] = this.filterForm.dateRange;
+ filtered = filtered.filter(item => {
+ const itemStart = new Date(item.startDate);
+ return itemStart >= new Date(start) && itemStart <= new Date(end);
+ });
+ }
+
+ // 鎸夌洰鐨勫湴杩囨护
+ if (this.filterForm.destination) {
+ filtered = filtered.filter(item =>
+ item.endCity.includes(this.filterForm.destination)
+ );
+ }
+
+ // 鎸夌姸鎬佽繃婊�
+ if (this.filterForm.status) {
+ filtered = filtered.filter(
+ item => item.status === this.filterForm.status
+ );
+ }
+
+ return filtered;
+ }
+ },
+ methods: {
+ getStatusType(status) {
+ const typeMap = {
+ 杩涜涓�: "primary",
+ 宸插畬鎴�: "success",
+ 宸插彇娑�: "danger"
+ };
+ return typeMap[status] || "info";
+ },
+
+ viewTripDetails(trip) {
+ this.currentTrip = trip;
+ this.detailDialogVisible = true;
+ },
+
+ handleAddTrip() {
+ this.isEditing = false;
+ this.tripForm = {
+ endCity: "",
+ startDate: "",
+ endDate: "",
+ purpose: ""
+ };
+ this.editDialogVisible = true;
+ },
+
+ editTrip(trip) {
+ this.isEditing = true;
+ this.currentTrip = trip;
+ this.tripForm = { ...trip };
+ this.editDialogVisible = true;
+ },
+
+ saveTrip() {
+ this.$refs.tripForm.validate(valid => {
+ if (valid) {
+ // 杩欓噷搴旇璋冪敤API淇濆瓨鏁版嵁
+ this.$message.success(this.isEditing ? "淇敼鎴愬姛" : "鏂板鎴愬姛");
+ this.editDialogVisible = false;
+ this.$emit("refresh");
+ }
+ });
+ },
+
+ handleFilter() {
+ this.currentPage = 1;
+ },
+
+ handleReset() {
+ this.filterForm = {
+ dateRange: [],
+ destination: "",
+ status: ""
+ };
+ this.currentPage = 1;
+ },
+
+ exportToExcel() {
+ // 绠�鍖栫増瀵煎嚭閫昏緫
+ this.$message.success("瀵煎嚭鍔熻兘寮�鍙戜腑");
+ },
+
+ handleSizeChange(size) {
+ this.pageSize = size;
+ this.currentPage = 1;
+ },
+
+ handleCurrentChange(page) {
+ this.currentPage = page;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.personal-business-trip-table {
+ padding: 20px;
+}
+
+.table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.table-header h4 {
+ margin: 0;
+ color: #303133;
+ font-size: 16px;
+}
+
+.header-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.filter-card {
+ margin-bottom: 20px;
+}
+
+.business-trip-table {
+ margin-bottom: 20px;
+}
+
+.city-cell {
+ font-weight: 500;
+}
+
+.date-cell {
+ color: #606266;
+}
+
+.distance-cell {
+ font-weight: 600;
+ color: #409eff;
+}
+
+.purpose-cell {
+ color: #606266;
+ font-size: 13px;
+}
+
+.pagination-container {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 20px;
+}
+
+.trip-details {
+ padding: 10px;
+}
+
+.expenses-section {
+ margin-top: 20px;
+}
+
+.expenses-section h5 {
+ margin-bottom: 10px;
+ color: #303133;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .table-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .header-actions {
+ width: 100%;
+ justify-content: flex-end;
+ }
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/PersonalAttendanceReport.vue b/src/views/OfficeRelated/checkingIn/components/PersonalAttendanceReport.vue
new file mode 100644
index 0000000..8c5d0bd
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/PersonalAttendanceReport.vue
@@ -0,0 +1,682 @@
+<template>
+ <div class="personal-attendance-report">
+ <div class="report-header">
+ <h4>鑰冨嫟缁熻鎶ヨ〃</h4>
+ <div class="header-actions">
+ <el-select v-model="reportPeriod" @change="handlePeriodChange" size="small">
+ <el-option label="鏈湀" value="month" />
+ <el-option label="鏈搴�" value="quarter" />
+ <el-option label="鏈勾搴�" value="year" />
+ </el-select>
+ <el-button
+ size="small"
+ @click="exportReport"
+ icon="el-icon-download"
+ >
+ 瀵煎嚭鎶ヨ〃
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 缁熻姒傝 -->
+ <el-row :gutter="20" class="stats-overview">
+ <el-col :span="6">
+ <el-card shadow="hover" class="stat-card">
+ <div class="stat-content">
+ <div class="stat-icon attendance-icon">
+ <i class="el-icon-date"></i>
+ </div>
+ <div class="stat-info">
+ <div class="stat-value">{{ overview.totalDays }}</div>
+ <div class="stat-label">鎬诲嚭鍕ゅぉ鏁�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card shadow="hover" class="stat-card">
+ <div class="stat-content">
+ <div class="stat-icon present-icon">
+ <i class="el-icon-success"></i>
+ </div>
+ <div class="stat-info">
+ <div class="stat-value">{{ overview.presentDays }}</div>
+ <div class="stat-label">姝e父鍑哄嫟</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card shadow="hover" class="stat-card">
+ <div class="stat-content">
+ <div class="stat-icon abnormal-icon">
+ <i class="el-icon-warning"></i>
+ </div>
+ <div class="stat-info">
+ <div class="stat-value">{{ overview.abnormalDays }}</div>
+ <div class="stat-label">寮傚父澶╂暟</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ <el-col :span="6">
+ <el-card shadow="hover" class="stat-card">
+ <div class="stat-content">
+ <div class="stat-icon rate-icon">
+ <i class="el-icon-data-analysis"></i>
+ </div>
+ <div class="stat-info">
+ <div class="stat-value">{{ overview.attendanceRate }}%</div>
+ <div class="stat-label">鍑哄嫟鐜�</div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <el-row :gutter="20" class="charts-section">
+ <el-col :span="12">
+ <el-card header="鍑哄嫟瓒嬪娍" shadow="never">
+ <div id="attendanceTrendChart" class="chart-container"></div>
+ </el-card>
+ </el-col>
+ <el-col :span="12">
+ <el-card header="鑰冨嫟鍒嗗竷" shadow="never">
+ <div id="attendanceDistributionChart" class="chart-container"></div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 璇︾粏缁熻琛ㄦ牸 -->
+ <el-card header="璇︾粏缁熻" class="detail-table-card" shadow="never">
+ <el-table :data="detailedStats" border style="width: 100%">
+ <el-table-column prop="month" label="鏈堜唤" />
+ <el-table-column prop="workDays" label="搴斿嚭鍕ゅぉ鏁�" />
+ <el-table-column prop="actualDays" label="瀹為檯鍑哄嫟" />
+ <el-table-column prop="lateTimes" label="杩熷埌娆℃暟" >
+ <template #default="scope">
+ <el-tag v-if="scope.row.lateTimes > 0" type="warning" size="small">
+ {{ scope.row.lateTimes }}
+ </el-tag>
+ <span v-else>{{ scope.row.lateTimes }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="leaveEarlyTimes" label="鏃╅��娆℃暟" >
+ <template #default="scope">
+ <el-tag v-if="scope.row.leaveEarlyTimes > 0" type="warning" size="small">
+ {{ scope.row.leaveEarlyTimes }}
+ </el-tag>
+ <span v-else>{{ scope.row.leaveEarlyTimes }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="absenceDays" label="缂哄嫟澶╂暟" >
+ <template #default="scope">
+ <el-tag v-if="scope.row.absenceDays > 0" type="danger" size="small">
+ {{ scope.row.absenceDays }}
+ </el-tag>
+ <span v-else>{{ scope.row.absenceDays }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="businessTripDays" label="鍑哄樊澶╂暟" >
+ <template #default="scope">
+ <el-tag v-if="scope.row.businessTripDays > 0" type="primary" size="small">
+ {{ scope.row.businessTripDays }}
+ </el-tag>
+ <span v-else>{{ scope.row.businessTripDays }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="attendanceRate" label="鍑哄嫟鐜�" >
+ <template #default="scope">
+ <el-progress
+ :percentage="scope.row.attendanceRate"
+ :show-text="false"
+ :color="getProgressColor(scope.row.attendanceRate)"
+ />
+ <span>{{ scope.row.attendanceRate }}%</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 寮傚父璁板綍 -->
+ <el-card header="寮傚父璁板綍" class="abnormal-records-card" shadow="never">
+ <el-table :data="abnormalRecords" border style="width: 100%">
+ <el-table-column prop="date" label="鏃ユ湡" />
+ <el-table-column prop="type" label="寮傚父绫诲瀷" >
+ <template #default="scope">
+ <el-tag :type="getAbnormalType(scope.row.type)" size="small">
+ {{ scope.row.type }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="description" label="鎻忚堪" min-width="200" />
+ <el-table-column prop="duration" label="鏃堕暱" />
+ <el-table-column prop="status" label="鐘舵��" >
+ <template #default="scope">
+ <el-tag
+ :type="scope.row.status === '宸插鐞�' ? 'success' : 'warning'"
+ size="small"
+ >
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" >
+ <template #default="scope">
+ <el-button type="text" size="mini" @click="viewAbnormalDetail(scope.row)">
+ 鏌ョ湅
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+export default {
+ name: 'PersonalAttendanceReport',
+ props: {
+ stats: {
+ type: Object,
+ default: () => ({})
+ },
+ attendanceData: {
+ type: Array,
+ default: () => []
+ }
+ },
+ data() {
+ return {
+ reportPeriod: 'month',
+ overview: {
+ totalDays: 0,
+ presentDays: 0,
+ abnormalDays: 0,
+ attendanceRate: 0
+ },
+ detailedStats: [],
+ abnormalRecords: [],
+ trendChart: null,
+ distributionChart: null
+ }
+ },
+ mounted() {
+ this.initData()
+ this.$nextTick(() => {
+ this.initCharts()
+ })
+ },
+ beforeDestroy() {
+ // 閿�姣佸浘琛ㄥ疄渚�
+ if (this.trendChart) {
+ this.trendChart.dispose()
+ }
+ if (this.distributionChart) {
+ this.distributionChart.dispose()
+ }
+ },
+ methods: {
+ initData() {
+ // 鍒濆鍖栨瑙堟暟鎹�
+ this.overview = {
+ totalDays: this.stats.totalDays || 22,
+ presentDays: this.stats.presentDays || 20,
+ abnormalDays: this.stats.abnormalDays || 2,
+ attendanceRate: this.stats.attendanceRate || 90.9
+ }
+
+ // 鍒濆鍖栬缁嗙粺璁�
+ this.detailedStats = [
+ { month: '2024-12', workDays: 22, actualDays: 20, lateTimes: 2,
+ leaveEarlyTimes: 1, absenceDays: 0, businessTripDays: 3, attendanceRate: 90.9 },
+ { month: '2024-11', workDays: 21, actualDays: 19, lateTimes: 1,
+ leaveEarlyTimes: 0, absenceDays: 1, businessTripDays: 2, attendanceRate: 90.5 },
+ { month: '2024-10', workDays: 23, actualDays: 22, lateTimes: 0,
+ leaveEarlyTimes: 0, absenceDays: 0, businessTripDays: 1, attendanceRate: 95.7 }
+ ]
+
+ // 鍒濆鍖栧紓甯歌褰�
+ this.abnormalRecords = [
+ { date: '2024-12-15', type: '杩熷埌', description: '鏃╀笂杩熷埌30鍒嗛挓', duration: '30鍒嗛挓', status: '宸插鐞�' },
+ { date: '2024-12-08', type: '鏃╅��', description: '涓嬪崍鎻愬墠1灏忔椂绂诲紑', duration: '1灏忔椂', status: '宸插鐞�' },
+ { date: '2024-11-20', type: '缂哄嫟', description: '鐥呭亣', duration: '1澶�', status: '宸插鐞�' }
+ ]
+ },
+
+ initCharts() {
+ this.initTrendChart()
+ this.initDistributionChart()
+ this.setupChartResize()
+ },
+
+ initTrendChart() {
+ const chartDom = document.getElementById('attendanceTrendChart')
+ if (!chartDom) return
+
+ this.trendChart = echarts.init(chartDom)
+ const option = {
+ tooltip: {
+ trigger: 'axis',
+ formatter: function(params) {
+ let result = params[0].axisValue + '<br/>'
+ params.forEach(param => {
+ result += `${param.seriesName}: ${param.value}<br/>`
+ })
+ return result
+ }
+ },
+ legend: {
+ data: ['鍑哄嫟澶╂暟', '寮傚父澶╂暟', '鍑哄嫟鐜�'],
+ bottom: 10
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '15%',
+ top: '10%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: ['10鏈�', '11鏈�', '12鏈�']
+ },
+ yAxis: [
+ {
+ type: 'value',
+ name: '澶╂暟',
+ min: 0,
+ max: 30
+ },
+ {
+ type: 'value',
+ name: '鍑哄嫟鐜�(%)',
+ min: 0,
+ max: 100,
+ axisLabel: {
+ formatter: '{value}%'
+ }
+ }
+ ],
+ series: [
+ {
+ name: '鍑哄嫟澶╂暟',
+ type: 'bar',
+ barWidth: '30%',
+ data: [22, 19, 20],
+ itemStyle: {
+ color: '#409EFF'
+ }
+ },
+ {
+ name: '寮傚父澶╂暟',
+ type: 'bar',
+ barWidth: '30%',
+ data: [1, 2, 2],
+ itemStyle: {
+ color: '#F56C6C'
+ }
+ },
+ {
+ name: '鍑哄嫟鐜�',
+ type: 'line',
+ yAxisIndex: 1,
+ data: [95.7, 90.5, 90.9],
+ itemStyle: {
+ color: '#67C23A'
+ },
+ lineStyle: {
+ width: 3
+ }
+ }
+ ]
+ }
+ this.trendChart.setOption(option)
+ },
+
+ initDistributionChart() {
+ const chartDom = document.getElementById('attendanceDistributionChart')
+ if (!chartDom) return
+
+ this.distributionChart = echarts.init(chartDom)
+ const option = {
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b}: {c} ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ right: 10,
+ top: 'center',
+ data: ['姝e父鍑哄嫟', '杩熷埌', '鏃╅��', '缂哄嫟', '鍑哄樊']
+ },
+ series: [
+ {
+ name: '鑰冨嫟鍒嗗竷',
+ type: 'pie',
+ radius: ['40%', '70%'],
+ center: ['40%', '50%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: false,
+ position: 'center'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: 18,
+ fontWeight: 'bold'
+ }
+ },
+ labelLine: {
+ show: false
+ },
+ data: [
+ { value: 20, name: '姝e父鍑哄嫟', itemStyle: { color: '#67C23A' } },
+ { value: 2, name: '杩熷埌', itemStyle: { color: '#E6A23C' } },
+ { value: 1, name: '鏃╅��', itemStyle: { color: '#F56C6C' } },
+ { value: 0, name: '缂哄嫟', itemStyle: { color: '#909399' } },
+ { value: 3, name: '鍑哄樊', itemStyle: { color: '#409EFF' } }
+ ]
+ }
+ ]
+ }
+ this.distributionChart.setOption(option)
+ },
+
+ setupChartResize() {
+ // 鐩戝惉绐楀彛鍙樺寲锛岄噸鏂版覆鏌撳浘琛�
+ const handleResize = () => {
+ if (this.trendChart) {
+ this.trendChart.resize()
+ }
+ if (this.distributionChart) {
+ this.distributionChart.resize()
+ }
+ }
+
+ window.addEventListener('resize', handleResize)
+ this.$once('hook:beforeDestroy', () => {
+ window.removeEventListener('resize', handleResize)
+ })
+ },
+
+ handlePeriodChange(period) {
+ this.reportPeriod = period
+ this.updateChartData()
+ },
+
+ updateChartData() {
+ // 鏍规嵁閫夋嫨鐨勫懆鏈熸洿鏂板浘琛ㄦ暟鎹�
+ let data
+ switch (this.reportPeriod) {
+ case 'month':
+ data = this.getMonthlyData()
+ break
+ case 'quarter':
+ data = this.getQuarterlyData()
+ break
+ case 'year':
+ data = this.getYearlyData()
+ break
+ default:
+ data = this.getMonthlyData()
+ }
+
+ this.updateCharts(data)
+ },
+
+ getMonthlyData() {
+ // 妯℃嫙鏈堝害鏁版嵁
+ return {
+ xAxis: ['10鏈�', '11鏈�', '12鏈�'],
+ attendance: [22, 19, 20],
+ abnormal: [1, 2, 2],
+ rate: [95.7, 90.5, 90.9]
+ }
+ },
+
+ getQuarterlyData() {
+ // 妯℃嫙瀛e害鏁版嵁
+ return {
+ xAxis: ['Q1', 'Q2', 'Q3', 'Q4'],
+ attendance: [65, 62, 58, 61],
+ abnormal: [5, 8, 6, 4],
+ rate: [92.8, 88.6, 90.2, 93.5]
+ }
+ },
+
+ getYearlyData() {
+ // 妯℃嫙骞村害鏁版嵁
+ return {
+ xAxis: ['2022', '2023', '2024'],
+ attendance: [240, 248, 252],
+ abnormal: [25, 18, 15],
+ rate: [90.2, 92.5, 94.1]
+ }
+ },
+
+ updateCharts(data) {
+ if (this.trendChart) {
+ const option = this.trendChart.getOption()
+ option.xAxis[0].data = data.xAxis
+ option.series[0].data = data.attendance
+ option.series[1].data = data.abnormal
+ option.series[2].data = data.rate
+ this.trendChart.setOption(option)
+ }
+ },
+
+ getProgressColor(rate) {
+ if (rate >= 95) return '#67C23A'
+ if (rate >= 90) return '#E6A23C'
+ if (rate >= 80) return '#F56C6C'
+ return '#909399'
+ },
+
+ getAbnormalType(type) {
+ const typeMap = {
+ '杩熷埌': 'warning',
+ '鏃╅��': 'warning',
+ '缂哄嫟': 'danger',
+ '璇峰亣': 'info'
+ }
+ return typeMap[type] || 'info'
+ },
+
+ viewAbnormalDetail(record) {
+ this.$message.info(`鏌ョ湅寮傚父璁板綍: ${record.date} - ${record.type}`)
+ // 杩欓噷鍙互鎵撳紑璇︽儏瀵硅瘽妗�
+ },
+
+ exportReport() {
+ this.$message.success('鎶ヨ〃瀵煎嚭鍔熻兘寮�鍙戜腑')
+ // 杩欓噷鍙互瀹炵幇瀵煎嚭PDF鎴朎xcel鍔熻兘
+ }
+ }
+}
+</script>
+
+<style scoped>
+.personal-attendance-report {
+ padding: 20px;
+ background: #fff;
+ border-radius: 8px;
+}
+
+.report-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+ padding-bottom: 16px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.report-header h4 {
+ margin: 0;
+ color: #303133;
+ font-size: 20px;
+ font-weight: 600;
+}
+
+.header-actions {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.stats-overview {
+ margin-bottom: 24px;
+}
+
+.stat-card {
+ border-radius: 8px;
+ transition: all 0.3s ease;
+}
+
+.stat-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.stat-content {
+ display: flex;
+ align-items: center;
+ padding: 16px;
+}
+
+.stat-icon {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16px;
+ font-size: 24px;
+ color: white;
+}
+
+.attendance-icon {
+ background: linear-gradient(135deg, #409EFF, #79BBFF);
+}
+
+.present-icon {
+ background: linear-gradient(135deg, #67C23A, #95D475);
+}
+
+.abnormal-icon {
+ background: linear-gradient(135deg, #E6A23C, #EEBD6D);
+}
+
+.rate-icon {
+ background: linear-gradient(135deg, #F56C6C, #F89898);
+}
+
+.stat-info {
+ flex: 1;
+}
+
+.stat-value {
+ font-size: 28px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 4px;
+}
+
+.stat-label {
+ color: #909399;
+ font-size: 14px;
+}
+
+.charts-section {
+ margin-bottom: 24px;
+}
+
+.chart-container {
+ width: 100%;
+ height: 300px;
+}
+
+.detail-table-card,
+.abnormal-records-card {
+ margin-bottom: 20px;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 1200px) {
+ .stats-overview .el-col {
+ margin-bottom: 16px;
+ }
+}
+
+@media (max-width: 768px) {
+ .personal-attendance-report {
+ padding: 12px;
+ }
+
+ .report-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+ }
+
+ .header-actions {
+ width: 100%;
+ justify-content: space-between;
+ }
+
+ .stat-content {
+ padding: 12px;
+ }
+
+ .stat-icon {
+ width: 50px;
+ height: 50px;
+ font-size: 20px;
+ margin-right: 12px;
+ }
+
+ .stat-value {
+ font-size: 24px;
+ }
+
+ .chart-container {
+ height: 250px;
+ }
+}
+
+/* 鍔ㄧ敾鏁堟灉 */
+.fade-enter-active, .fade-leave-active {
+ transition: opacity 0.3s;
+}
+.fade-enter, .fade-leave-to {
+ opacity: 0;
+}
+
+/* 琛ㄦ牸鏍峰紡浼樺寲 */
+.el-table {
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.el-table::before {
+ display: none;
+}
+
+/* 鍗$墖鏍囬鏍峰紡 */
+.el-card__header {
+ background: #f8f9fa;
+ border-bottom: 1px solid #ebeef5;
+ font-weight: 600;
+ color: #303133;
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/components/PersonalAttendanceTable.vue b/src/views/OfficeRelated/checkingIn/components/PersonalAttendanceTable.vue
new file mode 100644
index 0000000..55e3e3a
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/components/PersonalAttendanceTable.vue
@@ -0,0 +1,331 @@
+<template>
+ <div class="personal-attendance-table">
+ <div class="table-header">
+ <h4>鍑哄嫟璁板綍璇︽儏</h4>
+ <div class="header-actions">
+ <el-button size="small" @click="exportToCSV" icon="el-icon-download">
+ 瀵煎嚭CSV
+ </el-button>
+ <el-tooltip content="鍒锋柊鏁版嵁" placement="top">
+ <el-button size="small" @click="refreshData" icon="el-icon-refresh">
+ 鍒锋柊
+ </el-button>
+ </el-tooltip>
+ </div>
+ </div>
+
+ <el-table
+ :data="filteredData"
+ border
+ style="width: 100%"
+ v-loading="loading"
+ class="attendance-table"
+ >
+ <el-table-column prop="date" label="鏃ユ湡" sortable>
+ <template #default="scope">
+ <span class="date-cell">{{ scope.row.date }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="checkIn" label="绛惧埌鏃堕棿" >
+ <template #default="scope">
+ <span :class="getTimeClass(scope.row.checkIn, 'checkIn')">
+ {{ scope.row.checkIn || '-' }}
+ </span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="checkOut" label="绛鹃��鏃堕棿" >
+ <template #default="scope">
+ <span :class="getTimeClass(scope.row.checkOut, 'checkOut')">
+ {{ scope.row.checkOut || '-' }}
+ </span>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" sortable>
+ <template #default="scope">
+ <el-tag
+ :type="getWorkHoursType(scope.row.workHours)"
+ size="small"
+ >
+ {{ scope.row.workHours }}h
+ </el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="status" label="鐘舵��" >
+ <template #default="scope">
+ <el-tag
+ :type="getStatusType(scope.row.status)"
+ effect="plain"
+ >
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="remarks" label="澶囨敞" min-width="150" show-overflow-tooltip>
+ <template #default="scope">
+ <span class="remarks-cell">{{ scope.row.remarks || '鏃�' }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鎿嶄綔" fixed="right">
+ <template #default="scope">
+ <el-button
+ size="mini"
+ type="text"
+ @click="viewDetails(scope.row)"
+ icon="el-icon-view"
+ >
+ 璇︽儏
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 鍒嗛〉 -->
+ <div class="pagination-container">
+ <el-pagination
+ :current-page="currentPage"
+ :page-size="pageSize"
+ :total="total"
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+
+ <!-- 璇︽儏瀵硅瘽妗� -->
+ <el-dialog
+ title="鍑哄嫟璁板綍璇︽儏"
+ :visible.sync="detailDialogVisible"
+ width="500px"
+ >
+ <div v-if="currentRecord" class="record-details">
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="鏃ユ湡">
+ {{ currentRecord.date }}
+ </el-descriptions-item>
+ <el-descriptions-item label="绛惧埌鏃堕棿">
+ <el-tag :type="getTimeClass(currentRecord.checkIn, 'checkIn')">
+ {{ currentRecord.checkIn }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="绛鹃��鏃堕棿">
+ <el-tag :type="getTimeClass(currentRecord.checkOut, 'checkOut')">
+ {{ currentRecord.checkOut }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="宸ヤ綔鏃堕暱">
+ <el-tag :type="getWorkHoursType(currentRecord.workHours)">
+ {{ currentRecord.workHours }} 灏忔椂
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鐘舵��">
+ <el-tag :type="getStatusType(currentRecord.status)">
+ {{ currentRecord.status }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="澶囨敞">
+ {{ currentRecord.remarks || '鏃�' }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'PersonalAttendanceTable',
+ props: {
+ data: {
+ type: Array,
+ default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ currentPage: 1,
+ pageSize: 10,
+ detailDialogVisible: false,
+ currentRecord: null,
+ filterForm: {
+ dateRange: [],
+ status: ''
+ }
+ }
+ },
+ computed: {
+ total() {
+ return this.data.length
+ },
+ filteredData() {
+ let filtered = this.data
+
+ // 鎸夋棩鏈熻寖鍥磋繃婊�
+ if (this.filterForm.dateRange && this.filterForm.dateRange.length === 2) {
+ const [start, end] = this.filterForm.dateRange
+ filtered = filtered.filter(item => {
+ const itemDate = new Date(item.date)
+ return itemDate >= new Date(start) && itemDate <= new Date(end)
+ })
+ }
+
+ // 鎸夌姸鎬佽繃婊�
+ if (this.filterForm.status) {
+ filtered = filtered.filter(item => item.status === this.filterForm.status)
+ }
+
+ // 鍒嗛〉
+ const start = (this.currentPage - 1) * this.pageSize
+ const end = start + this.pageSize
+ return filtered.slice(start, end)
+ }
+ },
+ methods: {
+ getTimeClass(time, type) {
+ if (!time) return 'text-muted'
+
+ const hour = parseInt(time.split(':')[0])
+ if (type === 'checkIn') {
+ return hour > 9 ? 'text-danger' : 'text-success'
+ } else {
+ return hour < 18 ? 'text-warning' : 'text-success'
+ }
+ },
+
+ getWorkHoursType(hours) {
+ const numHours = parseFloat(hours)
+ if (numHours >= 8) return 'success'
+ if (numHours >= 6) return 'warning'
+ return 'danger'
+ },
+
+ getStatusType(status) {
+ const typeMap = {
+ '姝e父': 'success',
+ '杩熷埌': 'warning',
+ '鏃╅��': 'warning',
+ '缂哄嫟': 'danger',
+ '鍑哄樊': 'primary'
+ }
+ return typeMap[status] || 'info'
+ },
+
+ viewDetails(record) {
+ this.currentRecord = record
+ this.detailDialogVisible = true
+ },
+
+ exportToCSV() {
+ // 绠�鍖栫増CSV瀵煎嚭閫昏緫
+ const headers = ['鏃ユ湡', '绛惧埌鏃堕棿', '绛鹃��鏃堕棿', '宸ヤ綔鏃堕暱', '鐘舵��', '澶囨敞']
+ const csvData = this.data.map(item => [
+ item.date,
+ item.checkIn,
+ item.checkOut,
+ item.workHours,
+ item.status,
+ item.remarks || ''
+ ])
+
+ const csvContent = [headers, ...csvData]
+ .map(row => row.map(field => `"${field}"`).join(','))
+ .join('\n')
+
+ const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv;charset=utf-8;' })
+ const link = document.createElement('a')
+ link.href = URL.createObjectURL(blob)
+ link.download = `鍑哄嫟璁板綍_${new Date().toISOString().split('T')[0]}.csv`
+ link.click()
+ },
+
+ refreshData() {
+ this.$emit('refresh')
+ },
+
+ handleSizeChange(size) {
+ this.pageSize = size
+ this.currentPage = 1
+ },
+
+ handleCurrentChange(page) {
+ this.currentPage = page
+ }
+ }
+}
+</script>
+
+<style scoped>
+.personal-attendance-table {
+ padding: 20px;
+}
+
+.table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.table-header h4 {
+ margin: 0;
+ color: #303133;
+ font-size: 16px;
+}
+
+.header-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.attendance-table {
+ margin-bottom: 20px;
+}
+
+.date-cell {
+ font-weight: 500;
+}
+
+.text-success { color: #67c23a; }
+.text-warning { color: #e6a23c; }
+.text-danger { color: #f56c6c; }
+.text-muted { color: #909399; }
+.text-primary { color: #409eff; }
+
+.remarks-cell {
+ color: #606266;
+ font-size: 13px;
+}
+
+.pagination-container {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 20px;
+}
+
+.record-details {
+ padding: 10px;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .table-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .header-actions {
+ width: 100%;
+ justify-content: flex-end;
+ }
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/index.vue b/src/views/OfficeRelated/checkingIn/index.vue
new file mode 100644
index 0000000..18d21aa
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/index.vue
@@ -0,0 +1,317 @@
+<template>
+ <div class="attendance-management">
+ <!-- 椤堕儴缁熻淇℃伅 -->
+ <el-card class="statistics-card">
+ <div class="statistics-header">
+ <h3>鑰冨嫟缁熻姒傝</h3>
+ <span class="statistics-date">{{ currentDate }}</span>
+ </div>
+
+ <el-row :gutter="20" class="statistics-content">
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-value">{{ statistics.totalEmployees }}</div>
+ <div class="stat-label">鎬诲憳宸ユ暟</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-value text-success">{{ statistics.onDuty }}</div>
+ <div class="stat-label">浠婃棩鍑哄嫟</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-value text-warning">{{ statistics.onBusinessTrip }}</div>
+ <div class="stat-label">鍑哄樊涓�</div>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <div class="stat-value text-danger">{{ statistics.absent }}</div>
+ <div class="stat-label">缂哄嫟</div>
+ </div>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鎼滅储鍜岀瓫閫夊尯鍩� -->
+ <el-card class="filter-card">
+ <el-form :model="queryParams" inline>
+ <el-form-item label="骞存湀">
+ <el-date-picker
+ v-model="queryParams.month"
+ type="month"
+ placeholder="閫夋嫨骞存湀"
+ value-format="yyyy-MM"
+ />
+ </el-form-item>
+ <el-form-item label="鍛樺伐濮撳悕">
+ <el-input
+ v-model="queryParams.employeeName"
+ placeholder="璇疯緭鍏ュ憳宸ュ鍚�"
+ clearable
+ />
+ </el-form-item>
+ <el-form-item label="鑰冨嫟绫诲瀷">
+ <el-select v-model="queryParams.attendanceType" clearable>
+ <el-option label="鍏ㄩ儴" value="" />
+ <el-option label="鍑哄嫟" value="attendance" />
+ <el-option label="鍑哄樊" value="business_trip" />
+ </el-select>
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+ <el-button @click="handleReset">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 閫夐」鍗� -->
+ <el-card>
+ <el-tabs v-model="activeTab" @tab-click="handleTabChange">
+ <el-tab-pane label="鍑哄嫟璁板綍" name="attendance">
+ <attendance-table
+ :data="attendanceData"
+ :loading="loading"
+ @view-detail="handleViewDetail"
+ />
+ </el-tab-pane>
+
+ <el-tab-pane label="鍑哄樊璁板綍" name="businessTrip">
+ <business-trip-table
+ :data="businessTripData"
+ :loading="loading"
+ @view-detail="handleViewDetail"
+ />
+ </el-tab-pane>
+
+ <el-tab-pane label="缁熻鍑哄嫟" name="statistics">
+ <attendance-statistics
+ :data="statisticsData"
+ :loading="loading"
+ />
+ </el-tab-pane>
+
+ <el-tab-pane label="鍑哄樊閲岀▼鏍哥畻" name="mileage">
+ <mileage-calculation
+ :data="mileageData"
+ :loading="loading"
+ />
+ </el-tab-pane>
+ </el-tabs>
+ </el-card>
+ </div>
+</template>
+
+<script>
+import AttendanceTable from './components/AttendanceTable.vue'
+import BusinessTripTable from './components/BusinessTripTable.vue'
+import AttendanceStatistics from './components/AttendanceStatistics.vue'
+import MileageCalculation from './components/MileageCalculation.vue'
+
+export default {
+ name: 'AttendanceList',
+ components: {
+ AttendanceTable,
+ BusinessTripTable,
+ AttendanceStatistics,
+ MileageCalculation
+ },
+ data() {
+ return {
+ currentDate: new Date().toLocaleDateString('zh-CN'),
+ statistics: {
+ totalEmployees: 156,
+ onDuty: 142,
+ onBusinessTrip: 8,
+ absent: 6
+ },
+ queryParams: {
+ month: '',
+ employeeName: '',
+ attendanceType: ''
+ },
+ activeTab: 'attendance',
+ loading: false,
+ attendanceData: [],
+ businessTripData: [],
+ statisticsData: [],
+ mileageData: []
+ }
+ },
+ created() {
+ this.loadData()
+ },
+ methods: {
+ // 鍔犺浇妯℃嫙鏁版嵁
+ async loadData() {
+ this.loading = true
+ try {
+ // 妯℃嫙API璋冪敤寤惰繜
+ await new Promise(resolve => setTimeout(resolve, 500))
+
+ // 鐢熸垚妯℃嫙鏁版嵁
+ this.attendanceData = this.generateAttendanceData()
+ this.businessTripData = this.generateBusinessTripData()
+ this.statisticsData = this.generateStatisticsData()
+ this.mileageData = this.generateMileageData()
+ } catch (error) {
+ console.error('鍔犺浇鏁版嵁澶辫触:', error)
+ } finally {
+ this.loading = false
+ }
+ },
+
+ // 鐢熸垚鍑哄嫟妯℃嫙鏁版嵁
+ generateAttendanceData() {
+ const employees = ['寮犱笁', '鏉庡洓', '鐜嬩簲', '璧靛叚', '閽变竷', '瀛欏叓']
+ const statuses = ['姝e父', '杩熷埌', '鏃╅��', '缂哄嫟']
+ const data = []
+
+ for (let i = 0; i < 20; i++) {
+ data.push({
+ id: i + 1,
+ employeeName: employees[Math.floor(Math.random() * employees.length)],
+ date: `2024-12-${String(Math.floor(Math.random() * 28) + 1).padStart(2, '0')}`,
+ checkIn: `08:${String(Math.floor(Math.random() * 30)).padStart(2, '0')}`,
+ checkOut: `18:${String(Math.floor(Math.random() * 30)).padStart(2, '0')}`,
+ status: statuses[Math.floor(Math.random() * statuses.length)],
+ workHours: (8 + Math.random() * 2).toFixed(1)
+ })
+ }
+ return data
+ },
+
+ // 鐢熸垚鍑哄樊妯℃嫙鏁版嵁
+ generateBusinessTripData() {
+ const cities = ['鍖椾含', '涓婃捣', '骞垮窞', '娣卞湷', '鏉窞', '鎴愰兘']
+ const data = []
+
+ for (let i = 0; i < 15; i++) {
+ const startDate = new Date(2024, 11, Math.floor(Math.random() * 28) + 1)
+ const endDate = new Date(startDate.getTime() + Math.random() * 5 * 24 * 60 * 60 * 1000)
+
+ data.push({
+ id: i + 1,
+ employeeName: `鍛樺伐${String(i + 1).padStart(3, '0')}`,
+ tripNumber: `BT202412${String(i + 1).padStart(3, '0')}`,
+ startCity: cities[Math.floor(Math.random() * cities.length)],
+ endCity: cities[Math.floor(Math.random() * cities.length)],
+ startDate: startDate.toISOString().split('T')[0],
+ endDate: endDate.toISOString().split('T')[0],
+ distance: Math.floor(Math.random() * 1000) + 200,
+ status: ['杩涜涓�', '宸插畬鎴�'][Math.floor(Math.random() * 2)]
+ })
+ }
+ return data
+ },
+
+ // 鐢熸垚缁熻妯℃嫙鏁版嵁
+ generateStatisticsData() {
+ const months = ['2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06']
+ return months.map(month => ({
+ month,
+ attendanceDays: Math.floor(Math.random() * 20) + 15,
+ lateTimes: Math.floor(Math.random() * 5),
+ leaveEarlyTimes: Math.floor(Math.random() * 3),
+ absenceDays: Math.floor(Math.random() * 3)
+ }))
+ },
+
+ // 鐢熸垚閲岀▼妯℃嫙鏁版嵁
+ generateMileageData() {
+ return this.businessTripData.map(item => ({
+ ...item,
+ calculatedDistance: item.distance,
+ fuelCost: (item.distance * 0.8).toFixed(2),
+ allowance: (item.distance * 0.5).toFixed(2)
+ }))
+ },
+
+ handleQuery() {
+ this.loadData()
+ },
+
+ handleReset() {
+ this.queryParams = {
+ month: '',
+ employeeName: '',
+ attendanceType: ''
+ }
+ this.loadData()
+ },
+
+ handleTabChange(tab) {
+ this.activeTab = tab.name
+ },
+
+ handleViewDetail(employee) {
+ this.$router.push({
+ path: '/office/checkingInInfo',
+ query: {
+ employeeId: employee.id,
+ employeeName: employee.employeeName
+ }
+ })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.attendance-management {
+ padding: 20px;
+}
+
+.statistics-card {
+ margin-bottom: 20px;
+}
+
+.statistics-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.statistics-header h3 {
+ margin: 0;
+ color: #303133;
+}
+
+.statistics-date {
+ color: #909399;
+ font-size: 14px;
+}
+
+.statistics-content {
+ text-align: center;
+}
+
+.stat-item {
+ padding: 20px;
+ border-radius: 8px;
+ background: #f8f9fa;
+}
+
+.stat-value {
+ font-size: 32px;
+ font-weight: bold;
+ color: #409eff;
+ margin-bottom: 8px;
+}
+
+.stat-label {
+ color: #606266;
+ font-size: 14px;
+}
+
+.text-success { color: #67c23a; }
+.text-warning { color: #e6a23c; }
+.text-danger { color: #f56c6c; }
+
+.filter-card {
+ margin-bottom: 20px;
+}
+</style>
diff --git a/src/views/OfficeRelated/checkingIn/mockData.js b/src/views/OfficeRelated/checkingIn/mockData.js
new file mode 100644
index 0000000..f419c38
--- /dev/null
+++ b/src/views/OfficeRelated/checkingIn/mockData.js
@@ -0,0 +1,165 @@
+// 鍦ㄧ埗缁勪欢 data() 涓垨鍗曠嫭鍒涘缓 mockData.js 鏂囦欢
+export const generateMockData = () => {
+ return {
+ // 鍛樺伐鑰冨嫟鏁版嵁
+ attendanceData: [
+ {
+ id: 1,
+ date: '2024-12-01',
+ checkIn: '08:30',
+ checkOut: '18:00',
+ status: 'present',
+ workHours: 9.5
+ },
+ {
+ id: 2,
+ date: '2024-12-02',
+ checkIn: '09:15',
+ checkOut: '18:00',
+ status: 'late',
+ workHours: 8.75
+ },
+ {
+ id: 3,
+ date: '2024-12-03',
+ checkIn: '08:45',
+ checkOut: '17:30',
+ status: 'present',
+ workHours: 8.75
+ },
+ {
+ id: 4,
+ date: '2024-12-04',
+ checkIn: '08:25',
+ checkOut: '18:10',
+ status: 'present',
+ workHours: 9.75
+ },
+ {
+ id: 5,
+ date: '2024-12-05',
+ checkIn: null,
+ checkOut: null,
+ status: 'absent',
+ workHours: 0
+ },
+ {
+ id: 6,
+ date: '2024-12-08',
+ checkIn: '08:40',
+ checkOut: '17:45',
+ status: 'present',
+ workHours: 9.0
+ },
+ {
+ id: 7,
+ date: '2024-12-09',
+ checkIn: '08:35',
+ checkOut: '18:05',
+ status: 'present',
+ workHours: 9.5
+ },
+ {
+ id: 8,
+ date: '2024-12-10',
+ checkIn: '09:05',
+ checkOut: '17:50',
+ status: 'late',
+ workHours: 8.75
+ },
+ {
+ id: 9,
+ date: '2024-12-11',
+ checkIn: '08:50',
+ checkOut: '18:15',
+ status: 'present',
+ workHours: 9.5
+ },
+ {
+ id: 10,
+ date: '2024-12-12',
+ checkIn: '08:30',
+ checkOut: '17:40',
+ status: 'present',
+ workHours: 9.0
+ },
+ {
+ id: 11,
+ date: '2024-12-15',
+ checkIn: '08:45',
+ checkOut: '18:00',
+ status: 'present',
+ workHours: 9.25
+ },
+ {
+ id: 12,
+ date: '2024-12-16',
+ checkIn: '08:55',
+ checkOut: '17:55',
+ status: 'present',
+ workHours: 9.0
+ },
+ {
+ id: 13,
+ date: '2024-12-17',
+ checkIn: '08:40',
+ checkOut: '18:10',
+ status: 'present',
+ workHours: 9.5
+ },
+ {
+ id: 14,
+ date: '2024-12-18',
+ checkIn: '09:20',
+ checkOut: '17:30',
+ status: 'late',
+ workHours: 8.0
+ },
+ {
+ id: 15,
+ date: '2024-12-19',
+ checkIn: '08:35',
+ checkOut: '18:05',
+ status: 'present',
+ workHours: 9.5
+ }
+ ],
+
+ // 鍑哄樊鏁版嵁
+ businessTripData: [
+ {
+ id: 1,
+ tripNumber: 'BT202412001',
+ startCity: '鍖椾含',
+ endCity: '涓婃捣',
+ startDate: '2024-12-05',
+ endDate: '2024-12-08',
+ distance: 1200,
+ purpose: '瀹㈡埛浼氳',
+ status: 'completed'
+ },
+ {
+ id: 2,
+ tripNumber: 'BT202412002',
+ startCity: '鍖椾含',
+ endCity: '骞垮窞',
+ startDate: '2024-12-15',
+ endDate: '2024-12-18',
+ distance: 1900,
+ purpose: '椤圭洰璋冪爺',
+ status: 'completed'
+ },
+ {
+ id: 3,
+ tripNumber: 'BT202412003',
+ startCity: '鍖椾含',
+ endCity: '娣卞湷',
+ startDate: '2024-12-22',
+ endDate: '2024-12-24',
+ distance: 1950,
+ purpose: '鎶�鏈氦娴�',
+ status: 'completed'
+ }
+ ]
+ }
+}
diff --git a/src/views/business/GetWitness/GetWitnessInfo.vue b/src/views/business/GetWitness/GetWitnessInfo.vue
new file mode 100644
index 0000000..41bde72
--- /dev/null
+++ b/src/views/business/GetWitness/GetWitnessInfo.vue
@@ -0,0 +1,1577 @@
+<template>
+ <div class="organ-procurement-detail">
+ <!-- 鍩烘湰淇℃伅 -->
+ <el-card class="detail-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍣ㄥ畼鑾峰彇鍩烘湰淇℃伅</span>
+ <div style="float: right;">
+ <el-button type="primary" @click="handleSave" :loading="saveLoading">
+ 淇濆瓨
+ </el-button>
+ <el-button
+ type="success"
+ @click="handleProcure"
+ :disabled="form.procurementStatus === 'procured'"
+ >
+ 纭鑾峰彇
+ </el-button>
+ </div>
+ </div>
+
+ <el-form :model="form" ref="form" :rules="rules" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input v-model="form.hospitalNo" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="妗堜緥缂栧彿" prop="caseNo">
+ <el-input v-model="form.caseNo" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎹愮尞鑰呭鍚�" prop="donorName">
+ <el-input v-model="form.donorName" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鎬у埆" prop="gender">
+ <el-select v-model="form.gender" style="width: 100%">
+ <el-option label="鐢�" value="0" />
+ <el-option label="濂�" value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="骞撮緞" prop="age">
+ <el-input v-model="form.age" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鍑虹敓鏃ユ湡" prop="birthDate">
+ <el-date-picker
+ v-model="form.birthDate"
+ type="date"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐤剧梾璇婃柇" prop="diagnosis">
+ <el-input
+ type="textarea"
+ :rows="2"
+ v-model="form.diagnosis"
+ placeholder="璇疯緭鍏ョ柧鐥呰瘖鏂俊鎭�"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑾峰彇鏃堕棿" prop="procurementTime">
+ <el-date-picker
+ v-model="form.procurementTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ :disabled="form.procurementStatus !== 'procured'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鎵嬫湳鍚嶇О" prop="surgeryName">
+ <el-input v-model="form.surgeryName" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎵嬫湳寮�濮嬫椂闂�" prop="surgeryStartTime">
+ <el-date-picker
+ v-model="form.surgeryStartTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="渚涗綋姝讳骸鏃堕棿" prop="donorDeathTime">
+ <el-date-picker
+ v-model="form.donorDeathTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鑵逛富鍔ㄨ剦鎻掔鏃堕棿" prop="abdominalAortaCannulationTime">
+ <el-date-picker
+ v-model="form.abdominalAortaCannulationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="涓嬭厰闈欒剦鎻掔鏃堕棿" prop="inferiorVenaCavaCannulationTime">
+ <el-date-picker
+ v-model="form.inferiorVenaCavaCannulationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鑲犵郴鑶滀笂闈欒剦鎻掔鏃堕棿" prop="superiorMesentericVeinCannulationTime">
+ <el-date-picker
+ v-model="form.superiorMesentericVeinCannulationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐧昏浜�" prop="registrant">
+ <el-input v-model="form.registrant" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐧昏鏃堕棿" prop="registrationTime">
+ <el-date-picker
+ v-model="form.registrationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ readonly
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </el-card>
+
+ <!-- 鍣ㄥ畼鑾峰彇璁板綍閮ㄥ垎 -->
+ <el-card class="procurement-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍣ㄥ畼鑾峰彇璁板綍</span>
+ <div style="float: right;">
+ <el-tag :type="form.procurementStatus === 'procured' ? 'success' : 'warning'">
+ {{ form.procurementStatus === 'procured' ? '宸茶幏鍙�' : '寰呰幏鍙�' }}
+ </el-tag>
+ </div>
+ </div>
+
+ <el-form
+ ref="procurementForm"
+ :rules="procurementRules"
+ :model="procurementData"
+ label-position="right"
+ >
+ <el-row>
+ <el-col>
+ <el-form-item label-width="100px" label="鑾峰彇鍣ㄥ畼">
+ <el-checkbox-group v-model="selectedOrgans" @change="handleOrganSelectionChange">
+ <el-checkbox
+ v-for="dict in dict.type.sys_Organ || []"
+ :key="dict.value"
+ :label="dict.value"
+ :disabled="form.procurementStatus === 'procured'"
+ >
+ {{ dict.label }}
+ </el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row>
+ <el-col>
+ <el-form-item>
+ <el-table
+ :data="procurementData.records"
+ v-loading="loading"
+ border
+ style="width: 100%"
+ :row-class-name="getOrganRowClassName"
+ >
+ <el-table-column
+ label="鍣ㄥ畼鍚嶇О"
+ align="center"
+ width="120"
+ prop="organName"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.organName"
+ placeholder="鍣ㄥ畼鍚嶇О"
+ :disabled="true"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鑾峰彇寮�濮嬫椂闂�"
+ align="center"
+ width="180"
+ prop="organStartTime"
+ >
+ <template slot-scope="scope">
+ <el-date-picker
+ clearable
+ size="small"
+ style="width: 100%"
+ v-model="scope.row.organStartTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ placeholder="閫夋嫨鑾峰彇寮�濮嬫椂闂�"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鍣ㄥ畼绂讳綋鏃堕棿"
+ align="center"
+ width="180"
+ prop="organGetTime"
+ >
+ <template slot-scope="scope">
+ <el-date-picker
+ clearable
+ size="small"
+ style="width: 100%"
+ v-model="scope.row.organGetTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ placeholder="閫夋嫨鍣ㄥ畼绂讳綋鏃堕棿"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鑾峰彇鍖婚櫌"
+ align="center"
+ width="200"
+ prop="gainHospitalNo"
+ >
+ <template slot-scope="scope">
+ <el-select
+ v-model="scope.row.gainHospitalNo"
+ placeholder="璇烽�夋嫨鑾峰彇鍖婚櫌"
+ style="width: 100%"
+ :disabled="form.procurementStatus === 'procured'"
+ @change="handleHospitalChange(scope.row, $event)"
+ >
+ <el-option
+ v-for="hospital in hospitalList"
+ :key="hospital.hospitalNo"
+ :label="hospital.hospitalName"
+ :value="hospital.hospitalNo"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鑾峰彇鍖诲笀"
+ align="center"
+ width="120"
+ prop="organGetDoctor"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.organGetDoctor"
+ placeholder="鑾峰彇鍖诲笀"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鍔╂墜"
+ align="center"
+ width="120"
+ prop="assistant"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.assistant"
+ placeholder="鍔╂墜"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鑾峰彇鎶ゅ+"
+ align="center"
+ width="120"
+ prop="procurementNurse"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.procurementNurse"
+ placeholder="鑾峰彇鎶ゅ+"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鎵嬫湳瀹ゆ姢澹�"
+ align="center"
+ width="120"
+ prop="operatingRoomNurse"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.operatingRoomNurse"
+ placeholder="鎵嬫湳瀹ゆ姢澹�"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="楹婚唹鍖荤敓"
+ align="center"
+ width="120"
+ prop="anesthesiologist"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.anesthesiologist"
+ placeholder="楹婚唹鍖荤敓"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鑾峰彇鐘舵��"
+ align="center"
+ width="120"
+ prop="organState"
+ >
+ <template slot-scope="scope">
+ <el-select
+ v-model="scope.row.organState"
+ placeholder="璇烽�夋嫨鑾峰彇鐘舵��"
+ style="width: 100%"
+ :disabled="form.procurementStatus === 'procured'"
+ >
+ <el-option
+ v-for="dict in organStateList"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="璇存槑"
+ align="center"
+ prop="notGetReason"
+ min-width="200"
+ >
+ <template slot-scope="scope">
+ <el-input
+ type="textarea"
+ clearable
+ v-model="scope.row.notGetReason"
+ placeholder="璇疯緭鍏ユ湭鑾峰彇璇存槑"
+ :disabled="form.procurementStatus === 'procured'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="120"
+ class-name="small-padding fixed-width"
+ v-if="form.procurementStatus !== 'procured'"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-edit"
+ @click="handleEditProcurement(scope.row)"
+ >
+ 缂栬緫
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 鑾峰彇缁熻淇℃伅 -->
+ <div class="procurement-stats" v-if="procurementData.records.length > 0">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">宸茶幏鍙栧櫒瀹�:</span>
+ <span class="stat-value">{{ procurementData.records.length }} 涓�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">寰呭畬鍠勪俊鎭�:</span>
+ <span class="stat-value">{{ incompleteRecords }} 涓�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">娑夊強鍖婚櫌:</span>
+ <span class="stat-value">{{ uniqueHospitals }} 瀹�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">鑾峰彇鐘舵��:</span>
+ <span class="stat-value">
+ <el-tag :type="form.procurementStatus === 'procured' ? 'success' : 'warning'">
+ {{ form.procurementStatus === 'procured' ? '宸插畬鎴�' : '杩涜涓�' }}
+ </el-tag>
+ </span>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+
+ <div v-else class="empty-procurement">
+ <el-empty description="鏆傛棤鑾峰彇璁板綍" :image-size="80">
+ <span>璇峰厛閫夋嫨瑕佽幏鍙栫殑鍣ㄥ畼</span>
+ </el-empty>
+ </div>
+ </el-form>
+
+ <div class="dialog-footer" v-if="form.procurementStatus !== 'procured'">
+ <el-button
+ type="primary"
+ @click="handleSaveProcurement"
+ :loading="saveLoading"
+ :disabled="procurementData.records.length === 0"
+ >
+ 淇濆瓨鑾峰彇璁板綍
+ </el-button>
+ <el-button
+ type="success"
+ @click="handleConfirmProcurement"
+ :loading="confirmLoading"
+ :disabled="incompleteRecords > 0"
+ >
+ 纭瀹屾垚鑾峰彇
+ </el-button>
+ </div>
+ </el-card>
+
+ <!-- 闄勪欢绠$悊閮ㄥ垎 -->
+ <el-card class="attachment-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鐩稿叧闄勪欢</span>
+ <el-button
+ type="primary"
+ size="mini"
+ icon="el-icon-upload"
+ @click="handleUploadAttachment"
+ >
+ 涓婁紶闄勪欢
+ </el-button>
+ </div>
+
+ <div class="attachment-list">
+ <el-table :data="attachments" style="width: 100%">
+ <el-table-column label="鏂囦欢鍚嶇О" min-width="200">
+ <template slot-scope="scope">
+ <div class="file-info">
+ <i :class="getFileIcon(scope.row.fileName)" style="margin-right: 8px; color: #409EFF;"></i>
+ <span>{{ scope.row.fileName }}</span>
+ </div>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢绫诲瀷" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag size="small">{{ getFileType(scope.row.fileName) }}</el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢澶у皬" width="100" align="center">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓婁紶鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.uploadTime) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鎿嶄綔" width="150" align="center">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handlePreviewAttachment(scope.row)"
+ >棰勮</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-download"
+ @click="handleDownloadAttachment(scope.row)"
+ >涓嬭浇</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C;"
+ @click="handleRemoveAttachment(scope.row)"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </el-card>
+
+ <!-- 缂栬緫鑾峰彇璁板綍瀵硅瘽妗� -->
+ <el-dialog
+ title="缂栬緫鍣ㄥ畼鑾峰彇璁板綍"
+ :visible.sync="editDialogVisible"
+ width="600px"
+ >
+ <el-form :model="currentRecord" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍣ㄥ畼鍚嶇О">
+ <el-input v-model="currentRecord.organName" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑾峰彇鐘舵��">
+ <el-select v-model="currentRecord.organState" style="width: 100%">
+ <el-option
+ v-for="dict in organStateList"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鑾峰彇鍖诲笀">
+ <el-input v-model="currentRecord.organGetDoctor" placeholder="璇疯緭鍏ヨ幏鍙栧尰甯�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍔╂墜">
+ <el-input v-model="currentRecord.assistant" placeholder="璇疯緭鍏ュ姪鎵嬪鍚�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鑾峰彇鎶ゅ+">
+ <el-input v-model="currentRecord.procurementNurse" placeholder="璇疯緭鍏ヨ幏鍙栨姢澹�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎵嬫湳瀹ゆ姢澹�">
+ <el-input v-model="currentRecord.operatingRoomNurse" placeholder="璇疯緭鍏ユ墜鏈鎶ゅ+" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="楹婚唹鍖荤敓">
+ <el-input v-model="currentRecord.anesthesiologist" placeholder="璇疯緭鍏ラ夯閱夊尰鐢�" />
+ </el-form-item>
+
+ <el-form-item label="鏈幏鍙栬鏄�" v-if="currentRecord.organState === '0'">
+ <el-input
+ type="textarea"
+ :rows="3"
+ v-model="currentRecord.notGetReason"
+ placeholder="璇疯緭鍏ユ湭鑾峰彇鐨勫師鍥犺鏄�"
+ />
+ </el-form-item>
+ </el-form>
+
+ <div slot="footer">
+ <el-button @click="editDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleEditConfirm">纭</el-button>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import {
+ getOrganProcurementDetail,
+ updateOrganProcurement,
+ saveProcurementRecords,
+ getHospitalList,
+ getCoordinatorList
+} from "./organProcurement";
+
+export default {
+ name: "OrganProcurementDetail",
+ dicts: ["sys_user_sex", "sys_Organ", "sys_0_1", "sys_DonationCategory"],
+ data() {
+ return {
+ // 琛ㄥ崟鏁版嵁
+ form: {
+ id: undefined,
+ hospitalNo: "",
+ caseNo: "",
+ donorName: "",
+ gender: "",
+ age: "",
+ birthDate: "",
+ diagnosis: "",
+ procurementStatus: "pending",
+ procurementTime: "",
+ registrant: "",
+ registrationTime: "",
+ surgeryName: "",
+ surgeryStartTime: "",
+ donorDeathTime: "",
+ abdominalAortaCannulationTime: "",
+ inferiorVenaCavaCannulationTime: "",
+ superiorMesentericVeinCannulationTime: "",
+ donationCategory: "1",
+ deathJudgeDoctor1: "",
+ deathJudgeDoctor2: "",
+ deathReason: "",
+ operationEndTime: "",
+ coordinatorInOperating: "",
+ coordinatorOutOperating: "",
+ coordinatorSignTime: "",
+ responsibleUserName: "",
+ coordinatedUserId1: "",
+ coordinatedUserId2: "",
+ isSpendRemember: 1,
+ isRestoreRemains: 1
+ },
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ rules: {
+ donorName: [
+ { required: true, message: "鎹愮尞鑰呭鍚嶄笉鑳戒负绌�", trigger: "blur" }
+ ],
+ diagnosis: [
+ { required: true, message: "鐤剧梾璇婃柇涓嶈兘涓虹┖", trigger: "blur" }
+ ],
+ surgeryName: [
+ { required: true, message: "鎵嬫湳鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }
+ ]
+ },
+ // 鑾峰彇璁板綍楠岃瘉瑙勫垯
+ procurementRules: {},
+ // 淇濆瓨鍔犺浇鐘舵��
+ saveLoading: false,
+ confirmLoading: false,
+ // 鍔犺浇鐘舵��
+ loading: false,
+ // 閫変腑鐨勫櫒瀹�
+ selectedOrgans: [],
+ // 鍖婚櫌鍒楄〃
+ hospitalList: [],
+ // 鍗忚皟鍛樺垪琛�
+ coordinatorList: [],
+ // 鍣ㄥ畼鐘舵�佸垪琛�
+ organStateList: [
+ { value: "1", label: "宸茶幏鍙�" },
+ { value: "0", label: "鏈幏鍙�" },
+ { value: "2", label: "閮ㄥ垎鑾峰彇" }
+ ],
+ // 鑾峰彇璁板綍鏁版嵁
+ procurementData: {
+ records: []
+ },
+ // 闄勪欢鏁版嵁
+ attachments: [],
+ // 缂栬緫瀵硅瘽妗�
+ editDialogVisible: false,
+ currentRecord: {},
+ currentEditIndex: -1
+ };
+ },
+ computed: {
+ // 褰撳墠鐢ㄦ埛淇℃伅
+ currentUser() {
+ return JSON.parse(sessionStorage.getItem("user") || "{}");
+ },
+ // 涓嶅畬鏁寸殑璁板綍鏁伴噺
+ incompleteRecords() {
+ return this.procurementData.records.filter(
+ record =>
+ !record.organStartTime ||
+ !record.organGetTime ||
+ !record.gainHospitalNo ||
+ !record.organGetDoctor
+ ).length;
+ },
+ // 鍞竴鍖婚櫌鏁伴噺
+ uniqueHospitals() {
+ const hospitals = this.procurementData.records
+ .map(record => record.gainHospitalNo)
+ .filter(Boolean);
+ return new Set(hospitals).size;
+ }
+ },
+ created() {
+ const id = this.$route.query.id;
+ if (id) {
+ this.getDetail(id);
+ } else {
+ this.generateCaseNo();
+ this.form.registrant = this.currentUser.username || "褰撳墠鐢ㄦ埛";
+ this.form.registrationTime = new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19);
+ }
+ this.getHospitalData();
+ this.getCoordinatorData();
+ },
+ methods: {
+ // 鐢熸垚妗堜緥缂栧彿
+ generateCaseNo() {
+ const timestamp = Date.now().toString();
+ this.form.hospitalNo = "D" + timestamp.slice(-6);
+ this.form.caseNo = "C" + timestamp.slice(-6);
+ },
+ // 鑾峰彇璇︽儏
+ getDetail(id) {
+ this.loading = true;
+ getOrganProcurementDetail(id)
+ .then(response => {
+ if (response.code === 200) {
+ this.form = response.data;
+ if (response.data.procurementRecords) {
+ this.procurementData.records = response.data.procurementRecords;
+ this.selectedOrgans = response.data.procurementRecords.map(
+ item => item.organNo
+ );
+ }
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇鍣ㄥ畼鑾峰彇璇︽儏澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇璇︽儏澶辫触");
+ });
+ },
+ // 鑾峰彇鍖婚櫌鏁版嵁
+ getHospitalData() {
+ getHospitalList().then(response => {
+ if (response.code === 200) {
+ this.hospitalList = response.data;
+ }
+ });
+ },
+ // 鑾峰彇鍗忚皟鍛樻暟鎹�
+ getCoordinatorData() {
+ getCoordinatorList().then(response => {
+ if (response.code === 200) {
+ this.coordinatorList = response.data;
+ }
+ });
+ },
+ // 鍣ㄥ畼閫夋嫨鐘舵�佸彉鍖�
+ handleOrganSelectionChange(selectedValues) {
+ const currentOrganNos = this.procurementData.records.map(
+ item => item.organNo
+ );
+
+ // 鏂板閫夋嫨鐨勫櫒瀹�
+ selectedValues.forEach(organValue => {
+ if (!currentOrganNos.includes(organValue)) {
+ const organInfo = this.dict.type.sys_Organ.find(
+ item => item.value === organValue
+ );
+ if (organInfo) {
+ this.procurementData.records.push({
+ organName: organInfo.label,
+ organNo: organValue,
+ id: null,
+ procurementId: this.form.id,
+ organStartTime: "",
+ organGetTime: "",
+ gainHospitalNo: "",
+ gainHospitalName: "",
+ organGetDoctor: "",
+ assistant: "",
+ procurementNurse: "",
+ operatingRoomNurse: "",
+ anesthesiologist: "",
+ organState: "1",
+ notGetReason: ""
+ });
+ }
+ }
+ });
+
+ // 绉婚櫎鍙栨秷閫夋嫨鐨勫櫒瀹�
+ this.procurementData.records = this.procurementData.records.filter(
+ record => {
+ if (selectedValues.includes(record.organNo)) {
+ return true;
+ } else {
+ if (record.id) {
+ this.$confirm(
+ "鍒犻櫎鍣ㄥ畼鑾峰彇鏁版嵁鍚庡皢鏃犳硶鎭㈠锛屾偍纭鍒犻櫎璇ユ潯璁板綍鍚楋紵",
+ "鎻愮ず",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ )
+ .then(() => {
+ this.procurementData.records = this.procurementData.records.filter(
+ r => r.organNo !== record.organNo
+ );
+ this.$message.success("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {
+ this.selectedOrgans.push(record.organNo);
+ });
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ );
+ },
+ // 鍖婚櫌閫夋嫨鍙樺寲
+ handleHospitalChange(row, hospitalNo) {
+ const hospital = this.hospitalList.find(
+ item => item.hospitalNo === hospitalNo
+ );
+ if (hospital) {
+ row.gainHospitalName = hospital.hospitalName;
+ }
+ },
+ // 缂栬緫鑾峰彇璁板綍
+ handleEditProcurement(row) {
+ const index = this.procurementData.records.findIndex(
+ item => item.organNo === row.organNo
+ );
+ if (index !== -1) {
+ this.currentRecord = { ...row };
+ this.currentEditIndex = index;
+ this.editDialogVisible = true;
+ }
+ },
+ // 纭缂栬緫
+ handleEditConfirm() {
+ if (this.currentEditIndex !== -1) {
+ this.procurementData.records[this.currentEditIndex] = {
+ ...this.currentRecord
+ };
+ this.$message.success("鑾峰彇璁板綍鏇存柊鎴愬姛");
+ this.editDialogVisible = false;
+ }
+ },
+ // 鍣ㄥ畼琛屾牱寮�
+ getOrganRowClassName({ row }) {
+ if (
+ !row.organStartTime ||
+ !row.organGetTime ||
+ !row.gainHospitalNo ||
+ !row.organGetDoctor
+ ) {
+ return "warning-row";
+ }
+ return "";
+ },
+ // 淇濆瓨鍩烘湰淇℃伅
+ handleSave() {
+ this.$refs.form.validate(valid => {
+ if (valid) {
+ this.saveLoading = true;
+ const apiMethod = this.form.id
+ ? updateOrganProcurement
+ : addOrganProcurement;
+
+ apiMethod(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("淇濆瓨鎴愬姛");
+ if (!this.form.id) {
+ this.form.id = response.data.id;
+ this.$router.replace({
+ query: { ...this.$route.query, id: this.form.id }
+ });
+ }
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨澶辫触:", error);
+ this.$message.error("淇濆瓨澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ }
+ });
+ },
+ // 淇濆瓨鑾峰彇璁板綍
+ handleSaveProcurement() {
+ if (!this.form.id) {
+ this.$message.warning("璇峰厛淇濆瓨鍩烘湰淇℃伅");
+ return;
+ }
+
+ this.saveLoading = true;
+ saveProcurementRecords(this.form.id, this.procurementData.records)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鑾峰彇璁板綍淇濆瓨鎴愬姛");
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨鑾峰彇璁板綍澶辫触:", error);
+ this.$message.error("淇濆瓨鑾峰彇璁板綍澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ },
+ // 纭瀹屾垚鑾峰彇
+ handleConfirmProcurement() {
+ if (this.incompleteRecords > 0) {
+ this.$message.warning("璇峰厛瀹屽杽鎵�鏈夎幏鍙栬褰曠殑淇℃伅");
+ return;
+ }
+
+ this.$confirm("纭瀹屾垚鍣ㄥ畼鑾峰彇鍚楋紵瀹屾垚鍚庡皢鏃犳硶淇敼鑾峰彇淇℃伅銆�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.confirmLoading = true;
+ this.form.procurementStatus = "procured";
+ this.form.procurementTime = new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19);
+
+ updateOrganProcurement(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍣ㄥ畼鑾峰彇宸插畬鎴�");
+ }
+ })
+ .catch(error => {
+ console.error("纭鑾峰彇澶辫触:", error);
+ this.$message.error("纭鑾峰彇澶辫触");
+ })
+ .finally(() => {
+ this.confirmLoading = false;
+ });
+ })
+ .catch(() => {});
+ },
+ // 涓婁紶闄勪欢
+ handleUploadAttachment() {
+ this.$message.info("闄勪欢涓婁紶鍔熻兘");
+ },
+ // 棰勮闄勪欢
+ handlePreviewAttachment(attachment) {
+ this.$message.info("闄勪欢棰勮鍔熻兘");
+ },
+ // 涓嬭浇闄勪欢
+ handleDownloadAttachment(attachment) {
+ this.$message.info("闄勪欢涓嬭浇鍔熻兘");
+ },
+ // 鍒犻櫎闄勪欢
+ handleRemoveAttachment(attachment) {
+ this.$confirm("纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.$message.success("闄勪欢鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+ },
+ // 鑾峰彇鏂囦欢鍥炬爣
+ getFileIcon(fileName) {
+ const ext = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const iconMap = {
+ pdf: "el-icon-document",
+ doc: "el-icon-document",
+ docx: "el-icon-document",
+ xls: "el-icon-document",
+ xlsx: "el-icon-document",
+ jpg: "el-icon-picture",
+ jpeg: "el-icon-picture",
+ png: "el-icon-picture"
+ };
+ return iconMap[ext] || "el-icon-document";
+ },
+ // 鑾峰彇鏂囦欢绫诲瀷
+ getFileType(fileName) {
+ const ext = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const typeMap = {
+ pdf: "PDF",
+ doc: "DOC",
+ docx: "DOCX",
+ xls: "XLS",
+ xlsx: "XLSX",
+ jpg: "JPG",
+ jpeg: "JPEG",
+ png: "PNG"
+ };
+ return typeMap[ext] || ext.toUpperCase();
+ },
+ // 鏂囦欢澶у皬鏍煎紡鍖�
+ formatFileSize(size) {
+ if (size === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(size) / Math.log(k));
+ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time) {
+ if (!time) return "";
+ const date = new Date(time);
+ return `${date.getFullYear()}-${(date.getMonth() + 1)
+ .toString()
+ .padStart(2, "0")}-${date
+ .getDate()
+ .toString()
+ .padStart(2, "0")} ${date
+ .getHours()
+ .toString()
+ .padStart(2, "0")}:${date
+ .getMinutes()
+ .toString()
+ .padStart(2, "0")}`;
+ }
+ }
+};
+</script>
+<style scoped>
+.organ-procurement-detail {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.detail-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ border: 1px solid #e4e7ed;
+}
+
+.procurement-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ border: 1px solid #e4e7ed;
+}
+
+.attachment-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ border: 1px solid #e4e7ed;
+}
+
+.detail-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 1.4;
+}
+
+/* 缁熻淇℃伅鏍峰紡 */
+.procurement-stats {
+ margin-top: 20px;
+ padding: 15px;
+ background: linear-gradient(135deg, #a6b2e7 0%, #8a66ad 100%);
+ border-radius: 8px;
+ color: white;
+}
+
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 10px;
+ text-align: center;
+}
+
+.stat-label {
+ font-size: 12px;
+ opacity: 0.9;
+ margin-bottom: 5px;
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.stat-value {
+ font-size: 18px;
+ font-weight: bold;
+ color: white;
+}
+
+/* 绌虹姸鎬佹牱寮� */
+.empty-procurement {
+ text-align: center;
+ padding: 40px 0;
+ color: #909399;
+ background: #fafafa;
+ border-radius: 4px;
+ margin: 20px 0;
+}
+
+/* 瀵硅瘽妗嗗簳閮ㄦ寜閽� */
+.dialog-footer {
+ margin-top: 20px;
+ text-align: center;
+ padding-top: 20px;
+ border-top: 1px solid #e4e7ed;
+}
+
+.dialog-footer .el-button {
+ margin: 0 10px;
+ min-width: 120px;
+}
+
+/* 琛ㄦ牸琛屾牱寮� */
+:deep(.warning-row) {
+ background-color: #fff7e6 !important;
+}
+
+:deep(.warning-row:hover) {
+ background-color: #ffecc2 !important;
+}
+
+:deep(.el-table .cell) {
+ padding: 8px 12px;
+ line-height: 1.5;
+}
+
+:deep(.el-table th) {
+ background-color: #f5f7fa;
+ color: #606266;
+ font-weight: 600;
+}
+
+:deep(.el-table--border) {
+ border: 1px solid #e4e7ed;
+ border-radius: 4px;
+}
+
+:deep(.el-table--border th) {
+ border-right: 1px solid #e4e7ed;
+}
+
+:deep(.el-table--border td) {
+ border-right: 1px solid #e4e7ed;
+}
+
+/* 琛ㄥ崟鏍峰紡浼樺寲 */
+:deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #606266;
+}
+
+:deep(.el-input__inner) {
+ border-radius: 4px;
+ border: 1px solid #dcdfe6;
+ transition: border-color 0.3s ease;
+}
+
+:deep(.el-input__inner:focus) {
+ border-color: #409EFF;
+ box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+}
+
+:deep(.el-textarea__inner) {
+ border-radius: 4px;
+ resize: vertical;
+ min-height: 60px;
+}
+
+:deep(.el-select) {
+ width: 100%;
+}
+
+/* 鎸夐挳鏍峰紡浼樺寲 */
+:deep(.el-button--primary) {
+ background: linear-gradient(135deg, #409EFF 0%, #3375e0 100%);
+ border: none;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+}
+
+:deep(.el-button--primary:hover) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+}
+
+:deep(.el-button--success) {
+ background: linear-gradient(135deg, #67C23A 0%, #529b2f 100%);
+ border: none;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+}
+
+:deep(.el-button--success:hover) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(103, 194, 58, 0.4);
+}
+
+:deep(.el-button--warning) {
+ background: linear-gradient(135deg, #E6A23C 0%, #d18c2a 100%);
+ border: none;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+}
+
+:deep(.el-button--warning:hover) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(230, 162, 60, 0.4);
+}
+
+:deep(.el-button--danger) {
+ background: linear-gradient(135deg, #F56C6C 0%, #e05b5b 100%);
+ border: none;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+}
+
+:deep(.el-button--danger:hover) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(245, 108, 108, 0.4);
+}
+
+/* 鏍囩鏍峰紡 */
+:deep(.el-tag) {
+ border-radius: 12px;
+ border: none;
+ font-weight: 500;
+ padding: 4px 12px;
+}
+
+:deep(.el-tag--success) {
+ background: linear-gradient(135deg, #67C23A 0%, #529b2f 100%);
+ color: white;
+}
+
+:deep(.el-tag--warning) {
+ background: linear-gradient(135deg, #E6A23C 0%, #d18c2a 100%);
+ color: white;
+}
+
+/* 澶嶉�夋缁勬牱寮� */
+:deep(.el-checkbox-group) {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin-top: 10px;
+}
+
+:deep(.el-checkbox) {
+ margin-right: 0;
+}
+
+:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
+ background: linear-gradient(135deg, #409EFF 0%, #3375e0 100%);
+ border-color: #409EFF;
+}
+
+:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
+ color: #409EFF;
+ font-weight: 500;
+}
+
+/* 鏃ユ湡閫夋嫨鍣ㄦ牱寮� */
+:deep(.el-date-editor) {
+ width: 100%;
+}
+
+:deep(.el-picker-panel) {
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+/* 鍔犺浇鐘舵�� */
+:deep(.el-loading-mask) {
+ border-radius: 4px;
+}
+
+/* 鍗$墖澶撮儴鏍峰紡浼樺寲 */
+:deep(.el-card__header) {
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
+ border-bottom: 1px solid #e4e7ed;
+ padding: 15px 20px;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 1200px) {
+ .organ-procurement-detail {
+ padding: 15px;
+ }
+
+ :deep(.el-col) {
+ margin-bottom: 10px;
+ }
+}
+
+@media (max-width: 768px) {
+ .organ-procurement-detail {
+ padding: 10px;
+ }
+
+ .detail-title {
+ font-size: 16px;
+ }
+
+ .stat-item {
+ padding: 5px;
+ }
+
+ .stat-label {
+ font-size: 10px;
+ }
+
+ .stat-value {
+ font-size: 14px;
+ }
+
+ :deep(.el-table .cell) {
+ padding: 4px 8px;
+ font-size: 12px;
+ }
+
+ :deep(.el-checkbox-group) {
+ gap: 8px;
+ }
+
+ .dialog-footer .el-button {
+ margin: 5px;
+ min-width: 100px;
+ }
+}
+
+@media (max-width: 480px) {
+ .organ-procurement-detail {
+ padding: 5px;
+ }
+
+ :deep(.el-card__header) {
+ padding: 10px 15px;
+ }
+
+ :deep(.el-form-item__label) {
+ font-size: 12px;
+ }
+
+ :deep(.el-table) {
+ font-size: 11px;
+ }
+}
+
+/* 鍔ㄧ敾鏁堟灉 */
+.fade-enter-active, .fade-leave-active {
+ transition: opacity 0.3s ease;
+}
+
+.fade-enter, .fade-leave-to {
+ opacity: 0;
+}
+
+/* 鑷畾涔夋粴鍔ㄦ潯 */
+:deep(::-webkit-scrollbar) {
+ width: 6px;
+ height: 6px;
+}
+
+:deep(::-webkit-scrollbar-track) {
+ background: #f1f1f1;
+ border-radius: 3px;
+}
+
+:deep(::-webkit-scrollbar-thumb) {
+ background: #c1c1c1;
+ border-radius: 3px;
+}
+
+:deep(::-webkit-scrollbar-thumb:hover) {
+ background: #a8a8a8;
+}
+
+/* 鏂囦欢淇℃伅鏍峰紡 */
+.file-info {
+ display: flex;
+ align-items: center;
+ padding: 5px 0;
+}
+
+.file-info i {
+ font-size: 18px;
+ margin-right: 10px;
+}
+
+/* 鎿嶄綔鎸夐挳缁勬牱寮� */
+:deep(.small-padding .el-button) {
+ margin: 0 2px;
+ padding: 4px 8px;
+}
+
+/* 琛ㄥ崟琛岄棿璺濅紭鍖� */
+:deep(.el-form-item) {
+ margin-bottom: 18px;
+}
+
+:deep(.el-row) {
+ margin-bottom: 10px;
+}
+
+/* 瀵硅瘽妗嗘牱寮忎紭鍖� */
+:deep(.el-dialog) {
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+:deep(.el-dialog__header) {
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
+ border-bottom: 1px solid #e4e7ed;
+ padding: 15px 20px;
+ border-radius: 8px 8px 0 0;
+}
+
+:deep(.el-dialog__title) {
+ font-weight: 600;
+ color: #303133;
+}
+
+:deep(.el-dialog__body) {
+ padding: 20px;
+}
+
+:deep(.el-dialog__footer) {
+ padding: 15px 20px;
+ border-top: 1px solid #e4e7ed;
+}
+
+/* 鐗规畩鐘舵�佹彁绀� */
+.procurement-warning {
+ background-color: #fff7e6;
+ border: 1px solid #ffecc2;
+ border-radius: 4px;
+ padding: 10px 15px;
+ margin: 10px 0;
+ color: #e6a23c;
+ font-size: 14px;
+}
+
+.procurement-success {
+ background-color: #f0f9ff;
+ border: 1px solid #b3e0ff;
+ border-radius: 4px;
+ padding: 10px 15px;
+ margin: 10px 0;
+ color: #409EFF;
+ font-size: 14px;
+}
+
+/* 鏃堕棿绾挎牱寮忥紙鐢ㄤ簬鎵嬫湳鏃堕棿灞曠ず锛� */
+.procurement-timeline {
+ margin: 20px 0;
+ padding: 15px;
+ background: #f8f9fa;
+ border-radius: 4px;
+}
+
+.timeline-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+ padding: 8px 12px;
+ background: white;
+ border-radius: 4px;
+ border-left: 4px solid #409EFF;
+}
+
+.timeline-label {
+ font-weight: 500;
+ min-width: 120px;
+ color: #606266;
+}
+
+.timeline-value {
+ color: #303133;
+ font-weight: 500;
+}
+
+/* 鎵撳嵃鏍峰紡 */
+@media print {
+ .organ-procurement-detail {
+ padding: 0;
+ background: white;
+ }
+
+ .detail-card,
+ .procurement-card,
+ .attachment-card {
+ box-shadow: none;
+ border: 1px solid #ddd;
+ margin-bottom: 15px;
+ page-break-inside: avoid;
+ }
+
+ .dialog-footer,
+ .el-button {
+ display: none;
+ }
+}
+</style>
diff --git a/src/views/business/GetWitness/index.vue b/src/views/business/GetWitness/index.vue
new file mode 100644
index 0000000..79c858e
--- /dev/null
+++ b/src/views/business/GetWitness/index.vue
@@ -0,0 +1,372 @@
+<template>
+ <div class="organ-procurement-list">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-card class="search-card">
+ <el-form
+ :model="queryParams"
+ ref="queryForm"
+ :inline="true"
+ label-width="100px"
+ >
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input
+ v-model="queryParams.hospitalNo"
+ 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="procurementStatus">
+ <el-select
+ v-model="queryParams.procurementStatus"
+ placeholder="璇烽�夋嫨鑾峰彇鐘舵��"
+ clearable
+ style="width: 200px"
+ >
+ <el-option label="宸茶幏鍙�" value="procured" />
+ <el-option label="寰呰幏鍙�" value="pending" />
+ </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-card class="tool-card">
+ <el-row :gutter="10">
+ <el-col :span="16">
+ <el-button type="primary" icon="el-icon-plus" @click="handleCreate"
+ >鏂板缓鑾峰彇</el-button
+ >
+ <el-button
+ type="success"
+ icon="el-icon-edit"
+ :disabled="single"
+ @click="handleUpdate"
+ >淇敼</el-button
+ >
+ <el-button
+ type="danger"
+ icon="el-icon-delete"
+ :disabled="multiple"
+ @click="handleDelete"
+ >鍒犻櫎</el-button
+ >
+ </el-col>
+ <el-col :span="8" style="text-align: right">
+ <el-tooltip content="鍒锋柊" placement="top">
+ <el-button icon="el-icon-refresh" circle @click="getList" />
+ </el-tooltip>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-card>
+ <el-table
+ v-loading="loading"
+ :data="organProcurementList"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column
+ label="浣忛櫌鍙�"
+ align="center"
+ prop="hospitalNo"
+ width="120"
+ />
+ <el-table-column
+ label="鎹愮尞鑰呭鍚�"
+ align="center"
+ prop="donorName"
+ width="120"
+ />
+ <el-table-column label="鎬у埆" align="center" prop="gender" width="80">
+ <template slot-scope="scope">
+ <dict-tag
+ :options="dict.type.sys_user_sex"
+ :value="parseInt(scope.row.gender)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="骞撮緞" align="center" prop="age" width="80" />
+ <el-table-column
+ label="鐤剧梾璇婃柇"
+ align="center"
+ prop="diagnosis"
+ min-width="180"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鑾峰彇鐘舵��"
+ align="center"
+ prop="procurementStatus"
+ width="100"
+ >
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.procurementStatus === 'procured' ? 'success' : 'warning'">
+ {{ scope.row.procurementStatus === 'procured' ? '宸茶幏鍙�' : '寰呰幏鍙�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鑾峰彇鏃堕棿"
+ align="center"
+ prop="procurementTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.procurementTime
+ ? parseTime(scope.row.procurementTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鐧昏浜�"
+ align="center"
+ prop="registrant"
+ width="100"
+ />
+ <el-table-column
+ label="鐧昏鏃堕棿"
+ align="center"
+ prop="registrationTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.registrationTime
+ ? parseTime(scope.row.registrationTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="150"
+ class-name="small-padding fixed-width"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleView(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-delete"
+ style="color: #F56C6C"
+ @click="handleDelete(scope.row)"
+ >鍒犻櫎</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-card>
+ </div>
+</template>
+
+<script>
+import { listOrganProcurement, delOrganProcurement } from "./organProcurement";
+import Pagination from "@/components/Pagination";
+
+export default {
+ name: "OrganProcurementList",
+ components: { Pagination },
+ dicts: ["sys_user_sex"],
+ data() {
+ return {
+ // 閬僵灞�
+ loading: true,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 鍣ㄥ畼鑾峰彇琛ㄦ牸鏁版嵁
+ organProcurementList: [],
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ hospitalNo: undefined,
+ donorName: undefined,
+ procurementStatus: undefined
+ }
+ };
+ },
+ created() {
+ this.getList();
+ },
+ methods: {
+ // 鏌ヨ鍣ㄥ畼鑾峰彇鍒楄〃
+ getList() {
+ this.loading = true;
+ listOrganProcurement(this.queryParams)
+ .then(response => {
+ if (response.code === 200) {
+ this.organProcurementList = response.data.rows;
+ this.total = response.data.total;
+ } else {
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇鍣ㄥ畼鑾峰彇鍒楄〃澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ });
+ },
+ // 鎼滅储鎸夐挳鎿嶄綔
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ // 閲嶇疆鎸夐挳鎿嶄綔
+ resetQuery() {
+ this.$refs.queryForm.resetFields();
+ this.handleQuery();
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.id);
+ this.single = selection.length !== 1;
+ this.multiple = !selection.length;
+ },
+ // 鏌ョ湅璇︽儏
+ handleView(row) {
+ this.$router.push({
+ path: "/case/GetWitnessInfo",
+ query: { id: row.id }
+ });
+ },
+ // 鏂板鎸夐挳鎿嶄綔
+ handleCreate() {
+ this.$router.push("/case/GetWitnessInfo");
+ },
+ // 淇敼鎸夐挳鎿嶄綔
+ handleUpdate(row) {
+ const id = row.id || this.ids[0];
+ this.$router.push({
+ path: "/case/GetWitnessInfo",
+ query: { id: id }
+ });
+ },
+ // 鍒犻櫎鎸夐挳鎿嶄綔
+ handleDelete(row) {
+ const ids = row.id ? [row.id] : this.ids;
+ this.$confirm("鏄惁纭鍒犻櫎閫変腑鐨勬暟鎹」锛�", "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return delOrganProcurement(ids);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒犻櫎鎴愬姛");
+ this.getList();
+ }
+ })
+ .catch(() => {});
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time, pattern) {
+ if (!time) return "";
+ const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
+ let date;
+ if (typeof time === "object") {
+ date = time;
+ } else {
+ if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+ time = parseInt(time);
+ }
+ if (typeof time === "number" && time.toString().length === 10) {
+ time = time * 1000;
+ }
+ date = new Date(time);
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ };
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key];
+ if (key === "a") {
+ return ["鏃�", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�"][value];
+ }
+ if (result.length > 0 && value < 10) {
+ value = "0" + value;
+ }
+ return value || 0;
+ });
+ return time_str;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.organ-procurement-list {
+ padding: 20px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.tool-card {
+ margin-bottom: 20px;
+}
+
+.fixed-width .el-button {
+ margin: 0 5px;
+}
+</style>
diff --git a/src/views/business/GetWitness/organProcurement.js b/src/views/business/GetWitness/organProcurement.js
new file mode 100644
index 0000000..c64774e
--- /dev/null
+++ b/src/views/business/GetWitness/organProcurement.js
@@ -0,0 +1,353 @@
+// 妯℃嫙鍣ㄥ畼鑾峰彇鏁版嵁
+const mockOrganProcurementData = [
+ {
+ id: 1,
+ hospitalNo: "D202312001",
+ caseNo: "C202312001",
+ donorName: "寮犱笁",
+ gender: "0",
+ age: 45,
+ birthDate: "1978-05-15",
+ diagnosis: "鑴戝浼�",
+ procurementStatus: "procured",
+ procurementTime: "2023-12-01 16:30:00",
+ registrant: "鏉庡崗璋冨憳",
+ registrationTime: "2023-12-01 15:00:00",
+ createTime: "2023-12-01 10:00:00",
+ surgeryName: "澶氬櫒瀹樿幏鍙栨墜鏈�",
+ surgeryStartTime: "2023-12-01 14:00:00",
+ donorDeathTime: "2023-12-01 13:30:00",
+ abdominalAortaCannulationTime: "2023-12-01 14:30:00",
+ inferiorVenaCavaCannulationTime: "2023-12-01 14:35:00",
+ superiorMesentericVeinCannulationTime: "2023-12-01 14:40:00"
+ },
+ {
+ id: 2,
+ hospitalNo: "D202312002",
+ caseNo: "C202312002",
+ donorName: "鏉庡洓",
+ gender: "1",
+ age: 38,
+ birthDate: "1985-08-22",
+ diagnosis: "蹇冭剰楠ゅ仠",
+ procurementStatus: "procured",
+ procurementTime: "2023-12-02 11:20:00",
+ registrant: "寮犲崗璋冨憳",
+ registrationTime: "2023-12-02 10:00:00",
+ createTime: "2023-12-02 08:30:00",
+ surgeryName: "蹇冭剰鑾峰彇鎵嬫湳",
+ surgeryStartTime: "2023-12-02 10:30:00",
+ donorDeathTime: "2023-12-02 10:00:00",
+ abdominalAortaCannulationTime: "2023-12-02 10:45:00",
+ inferiorVenaCavaCannulationTime: "2023-12-02 10:50:00",
+ superiorMesentericVeinCannulationTime: "2023-12-02 10:55:00"
+ },
+ {
+ id: 3,
+ hospitalNo: "D202312003",
+ caseNo: "C202312003",
+ donorName: "鐜嬩簲",
+ gender: "0",
+ age: 52,
+ birthDate: "1971-03-10",
+ diagnosis: "鑴戞姝�",
+ procurementStatus: "pending",
+ procurementTime: "",
+ registrant: "璧靛崗璋冨憳",
+ registrationTime: "2023-12-03 17:20:00",
+ createTime: "2023-12-03 14:00:00",
+ surgeryName: "",
+ surgeryStartTime: "",
+ donorDeathTime: "",
+ abdominalAortaCannulationTime: "",
+ inferiorVenaCavaCannulationTime: "",
+ superiorMesentericVeinCannulationTime: ""
+ }
+];
+
+// 妯℃嫙鍣ㄥ畼鑾峰彇璁板綍鏁版嵁
+const mockProcurementRecordData = [
+ {
+ id: 1,
+ procurementId: 1,
+ organName: "鑲濊剰",
+ organNo: "L001",
+ organStartTime: "2023-12-01 15:00:00",
+ organGetTime: "2023-12-01 15:45:00",
+ gainHospitalNo: "H1001",
+ gainHospitalName: "鍖椾含鍗忓拰鍖婚櫌",
+ organGetDoctor: "鐜嬪尰鐢�",
+ assistant: "鏉庡尰鐢�",
+ procurementNurse: "寮犳姢澹�",
+ operatingRoomNurse: "鍒樻姢澹�",
+ anesthesiologist: "闄堥夯閱夊笀",
+ organState: "1",
+ notGetReason: ""
+ },
+ {
+ id: 2,
+ procurementId: 1,
+ organName: "鑲捐剰",
+ organNo: "K001",
+ organStartTime: "2023-12-01 15:10:00",
+ organGetTime: "2023-12-01 15:50:00",
+ gainHospitalNo: "H1002",
+ gainHospitalName: "涓婃捣鐟為噾鍖婚櫌",
+ organGetDoctor: "璧靛尰鐢�",
+ assistant: "閽卞尰鐢�",
+ procurementNurse: "瀛欐姢澹�",
+ operatingRoomNurse: "鍛ㄦ姢澹�",
+ anesthesiologist: "鍚撮夯閱夊笀",
+ organState: "1",
+ notGetReason: ""
+ },
+ {
+ id: 3,
+ procurementId: 1,
+ organName: "蹇冭剰",
+ organNo: "H001",
+ organStartTime: "2023-12-01 15:20:00",
+ organGetTime: "2023-12-01 16:00:00",
+ gainHospitalNo: "H1003",
+ gainHospitalName: "骞垮窞涓北鍖婚櫌",
+ organGetDoctor: "閮戝尰鐢�",
+ assistant: "鐜嬪尰鐢�",
+ procurementNurse: "鏋楁姢澹�",
+ operatingRoomNurse: "榛勬姢澹�",
+ anesthesiologist: "鏉ㄩ夯閱夊笀",
+ organState: "1",
+ notGetReason: ""
+ }
+];
+
+// 妯℃嫙鍖婚櫌鏁版嵁
+const mockHospitalData = [
+ { id: 1, hospitalNo: "H1001", hospitalName: "鍖椾含鍗忓拰鍖婚櫌", type: "4" },
+ { id: 2, hospitalNo: "H1002", hospitalName: "涓婃捣鐟為噾鍖婚櫌", type: "4" },
+ { id: 3, hospitalNo: "H1003", hospitalName: "骞垮窞涓北鍖婚櫌", type: "4" },
+ { id: 4, hospitalNo: "H1004", hospitalName: "姝︽眽鍚屾祹鍖婚櫌", type: "4" },
+ { id: 5, hospitalNo: "H1005", hospitalName: "鎴愰兘鍗庤タ鍖婚櫌", type: "4" }
+];
+
+// 妯℃嫙鍗忚皟鍛樻暟鎹�
+const mockCoordinatorData = [
+ { reportNo: "C001", reportName: "寮犲崗璋冨憳" },
+ { reportNo: "C002", reportName: "鏉庡崗璋冨憳" },
+ { reportNo: "C003", reportName: "鐜嬪崗璋冨憳" },
+ { reportNo: "C004", reportName: "璧靛崗璋冨憳" }
+];
+
+// 妯℃嫙API鍝嶅簲寤惰繜
+const delay = (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));
+
+// 鏌ヨ鍣ㄥ畼鑾峰彇鍒楄〃
+export const listOrganProcurement = async (queryParams = {}) => {
+ await delay();
+
+ const {
+ pageNum = 1,
+ pageSize = 10,
+ hospitalNo,
+ donorName,
+ procurementStatus
+ } = queryParams;
+
+ // 杩囨护鏁版嵁
+ let filteredData = mockOrganProcurementData.filter(item => {
+ let match = true;
+
+ if (hospitalNo && !item.hospitalNo.includes(hospitalNo)) {
+ match = false;
+ }
+
+ if (donorName && !item.donorName.includes(donorName)) {
+ match = false;
+ }
+
+ if (procurementStatus && item.procurementStatus !== procurementStatus) {
+ match = false;
+ }
+
+ return match;
+ });
+
+ // 鍒嗛〉
+ const startIndex = (pageNum - 1) * pageSize;
+ const endIndex = startIndex + parseInt(pageSize);
+ const paginatedData = filteredData.slice(startIndex, endIndex);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ rows: paginatedData,
+ total: filteredData.length,
+ pageNum: parseInt(pageNum),
+ pageSize: parseInt(pageSize)
+ }
+ };
+};
+
+// 鑾峰彇鍣ㄥ畼鑾峰彇璇︾粏淇℃伅
+export const getOrganProcurementDetail = async (id) => {
+ await delay();
+
+ const detail = mockOrganProcurementData.find(item => item.id == id);
+
+ if (detail) {
+ // 鑾峰彇鑾峰彇璁板綍
+ const procurementRecords = mockProcurementRecordData.filter(item => item.procurementId == id);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ ...detail,
+ procurementRecords
+ }
+ };
+ } else {
+ return {
+ code: 404,
+ message: "鍣ㄥ畼鑾峰彇璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鏂板鍣ㄥ畼鑾峰彇
+export const addOrganProcurement = async (data) => {
+ await delay();
+
+ const newId = Math.max(...mockOrganProcurementData.map(item => item.id), 0) + 1;
+ const hospitalNo = `D${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+ const caseNo = `C${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+
+ const newRecord = {
+ ...data,
+ id: newId,
+ hospitalNo,
+ caseNo,
+ registrationTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ createTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ mockOrganProcurementData.unshift(newRecord);
+
+ return {
+ code: 200,
+ message: "鏂板鎴愬姛",
+ data: newRecord
+ };
+};
+
+// 淇敼鍣ㄥ畼鑾峰彇
+export const updateOrganProcurement = async (data) => {
+ await delay();
+
+ const index = mockOrganProcurementData.findIndex(item => item.id == data.id);
+
+ if (index !== -1) {
+ mockOrganProcurementData[index] = {
+ ...mockOrganProcurementData[index],
+ ...data,
+ updateTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ return {
+ code: 200,
+ message: "淇敼鎴愬姛",
+ data: mockOrganProcurementData[index]
+ };
+ } else {
+ return {
+ code: 404,
+ message: "鍣ㄥ畼鑾峰彇璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鍒犻櫎鍣ㄥ畼鑾峰彇
+export const delOrganProcurement = async (ids) => {
+ await delay();
+
+ const idArray = Array.isArray(ids) ? ids : [ids];
+
+ idArray.forEach(id => {
+ const index = mockOrganProcurementData.findIndex(item => item.id == id);
+ if (index !== -1) {
+ mockOrganProcurementData.splice(index, 1);
+ }
+ });
+
+ return {
+ code: 200,
+ message: "鍒犻櫎鎴愬姛"
+ };
+};
+
+// 淇濆瓨鍣ㄥ畼鑾峰彇璁板綍
+export const saveProcurementRecords = async (procurementId, records) => {
+ await delay();
+
+ // 鍒犻櫎璇ヨ幏鍙朓D鐨勬墍鏈夎褰�
+ const existingIndexes = [];
+ mockProcurementRecordData.forEach((item, index) => {
+ if (item.procurementId == procurementId) {
+ existingIndexes.push(index);
+ }
+ });
+
+ // 浠庡悗寰�鍓嶅垹闄ら伩鍏嶇储寮曢棶棰�
+ existingIndexes.reverse().forEach(index => {
+ mockProcurementRecordData.splice(index, 1);
+ });
+
+ // 娣诲姞鏂拌褰�
+ records.forEach(record => {
+ const newId = Math.max(...mockProcurementRecordData.map(item => item.id), 0) + 1;
+ mockProcurementRecordData.push({
+ ...record,
+ id: newId,
+ procurementId: procurementId
+ });
+ });
+
+ return {
+ code: 200,
+ message: "淇濆瓨鎴愬姛",
+ data: records
+ };
+};
+
+// 鑾峰彇鍖婚櫌鍒楄〃
+export const getHospitalList = async () => {
+ await delay();
+
+ return {
+ code: 200,
+ message: "success",
+ data: mockHospitalData
+ };
+};
+
+// 鑾峰彇鍗忚皟鍛樺垪琛�
+export const getCoordinatorList = async () => {
+ await delay();
+
+ return {
+ code: 200,
+ message: "success",
+ data: mockCoordinatorData
+ };
+};
+
+export default {
+ listOrganProcurement,
+ getOrganProcurementDetail,
+ addOrganProcurement,
+ updateOrganProcurement,
+ delOrganProcurement,
+ saveProcurementRecords,
+ getHospitalList,
+ getCoordinatorList
+};
diff --git a/src/views/business/OrganUtilization/OrganUtilizationInfo.vue b/src/views/business/OrganUtilization/OrganUtilizationInfo.vue
new file mode 100644
index 0000000..920f752
--- /dev/null
+++ b/src/views/business/OrganUtilization/OrganUtilizationInfo.vue
@@ -0,0 +1,1656 @@
+<template>
+ <div class="organ-utilization-detail">
+ <!-- 鍩烘湰淇℃伅 -->
+ <el-card class="detail-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍣ㄥ畼鍒╃敤鍩烘湰淇℃伅</span>
+ <div style="float: right;">
+ <el-button type="primary" @click="handleSave" :loading="saveLoading">
+ 淇濆瓨
+ </el-button>
+ <el-button
+ type="success"
+ @click="handleComplete"
+ :disabled="form.utilizationStatus === 'completed'"
+ >
+ 瀹屾垚鍒╃敤
+ </el-button>
+ </div>
+ </div>
+
+ <el-form :model="form" ref="form" :rules="rules" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input v-model="form.hospitalNo" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="妗堜緥缂栧彿" prop="caseNo">
+ <el-input v-model="form.caseNo" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎹愮尞鑰呭鍚�" prop="donorName">
+ <el-input v-model="form.donorName" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鎬у埆" prop="gender">
+ <el-select v-model="form.gender" style="width: 100%">
+ <el-option label="鐢�" value="0" />
+ <el-option label="濂�" value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="骞撮緞" prop="age">
+ <el-input v-model="form.age" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鍑虹敓鏃ユ湡" prop="birthDate">
+ <el-date-picker
+ v-model="form.birthDate"
+ type="date"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐤剧梾璇婃柇" prop="diagnosis">
+ <el-input
+ type="textarea"
+ :rows="2"
+ v-model="form.diagnosis"
+ placeholder="璇疯緭鍏ョ柧鐥呰瘖鏂俊鎭�"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍒嗛厤鏃堕棿" prop="allocationTime">
+ <el-date-picker
+ v-model="form.allocationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <el-form-item align="left" label="閬椾綋鎹愮尞" prop="isBodyDonation">
+ <el-radio-group v-model="form.isBodyDonation">
+ <el-radio
+ v-for="dict in dict.type.sys_0_1 || []"
+ :key="dict.value"
+ :label="dict.value"
+ >{{ dict.label }}</el-radio
+ >
+ </el-radio-group>
+ </el-form-item>
+ </el-col>
+ <el-col :span="18">
+ <el-form-item align="left" label="鎺ユ敹鍗曚綅" prop="receivingUnit">
+ <el-input
+ v-model="form.receivingUnit"
+ placeholder="璇疯緭鍏ユ帴鏀跺崟浣�"
+ :disabled="form.isBodyDonation !== '1'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <el-form-item label="璐熻矗浜�" prop="responsibleUserId">
+ <el-select
+ v-model="form.responsibleUserId"
+ placeholder="璇烽�夋嫨璐熻矗浜�"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in leaderList"
+ :key="item.reportNo"
+ :label="item.reportName"
+ :value="item.reportNo"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="鍗忚皟鍛樹竴" prop="coordinatedUserId1">
+ <el-select
+ v-model="form.coordinatedUserId1"
+ placeholder="璇烽�夋嫨鍗忚皟鍛�"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in coordinatorList"
+ :key="item.reportNo"
+ :label="item.reportName"
+ :value="item.reportNo"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="鍗忚皟鍛樹簩" prop="coordinatedUserId2">
+ <el-select
+ v-model="form.coordinatedUserId2"
+ placeholder="璇烽�夋嫨鍗忚皟鍛�"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in coordinatorList"
+ :key="item.reportNo"
+ :label="item.reportName"
+ :value="item.reportNo"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="瀹屾垚鏃堕棿" prop="completionTime">
+ <el-date-picker
+ v-model="form.completionTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ :disabled="form.utilizationStatus !== 'completed'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐧昏浜�" prop="registrant">
+ <el-input v-model="form.registrant" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐧昏鏃堕棿" prop="registrationTime">
+ <el-date-picker
+ v-model="form.registrationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ readonly
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </el-card>
+
+ <!-- 鍣ㄥ畼鍒╃敤璁板綍閮ㄥ垎 -->
+ <el-card class="utilization-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍣ㄥ畼鍒╃敤璁板綍</span>
+ <div style="float: right;">
+ <el-tag :type="getStatusTagType(form.utilizationStatus)">
+ {{ getStatusText(form.utilizationStatus) }}
+ </el-tag>
+ </div>
+ </div>
+
+ <el-form
+ ref="utilizationForm"
+ :rules="utilizationRules"
+ :model="utilizationData"
+ label-position="right"
+ >
+ <el-row>
+ <el-col>
+ <el-form-item label-width="100px" label="绉绘鍣ㄥ畼">
+ <el-checkbox-group v-model="selectedOrgans" @change="handleOrganSelectionChange">
+ <el-checkbox
+ v-for="dict in dict.type.sys_Organ || []"
+ :key="dict.value"
+ :label="dict.value"
+ :disabled="form.utilizationStatus === 'completed'"
+ >
+ {{ dict.label }}
+ </el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row>
+ <el-col>
+ <el-form-item>
+ <el-table
+ :data="utilizationData.records"
+ v-loading="loading"
+ border
+ style="width: 100%"
+ :row-class-name="getOrganRowClassName"
+ >
+ <el-table-column
+ label="鍣ㄥ畼鍚嶇О"
+ align="center"
+ width="120"
+ prop="organName"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.organName"
+ placeholder="鍣ㄥ畼鍚嶇О"
+ :disabled="true"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="绯荤粺缂栧彿"
+ align="center"
+ width="120"
+ prop="caseNo"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.caseNo"
+ placeholder="绯荤粺缂栧彿"
+ :disabled="form.utilizationStatus === 'completed'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="绉绘鍖婚櫌"
+ align="center"
+ width="200"
+ prop="hospitalNo"
+ >
+ <template slot-scope="scope">
+ <el-select
+ v-model="scope.row.hospitalNo"
+ placeholder="璇烽�夋嫨绉绘鍖婚櫌"
+ style="width: 100%"
+ :disabled="form.utilizationStatus === 'completed'"
+ @change="handleHospitalChange(scope.row, $event)"
+ >
+ <el-option
+ v-for="hospital in hospitalList"
+ :key="hospital.hospitalNo"
+ :label="hospital.hospitalName"
+ :value="hospital.hospitalNo"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鍙椾綋濮撴皬"
+ align="center"
+ width="120"
+ prop="recipientName"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.recipientName"
+ placeholder="鍙椾綋濮撴皬"
+ :disabled="form.utilizationStatus === 'completed'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="绉绘璐熻矗浜�"
+ align="center"
+ width="120"
+ prop="transplantDoctor"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.transplantDoctor"
+ placeholder="鍖诲笀濮撳悕"
+ :disabled="form.utilizationStatus === 'completed'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="绉绘鏃堕棿"
+ align="center"
+ width="150"
+ prop="transplantTime"
+ >
+ <template slot-scope="scope">
+ <el-date-picker
+ clearable
+ size="small"
+ style="width: 100%"
+ v-model="scope.row.transplantTime"
+ type="date"
+ value-format="yyyy-MM-dd"
+ placeholder="閫夋嫨绉绘鏃堕棿"
+ :disabled="form.utilizationStatus === 'completed'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="绉绘鐘舵��"
+ align="center"
+ width="120"
+ prop="transplantStatus"
+ >
+ <template slot-scope="scope">
+ <el-select
+ v-model="scope.row.transplantStatus"
+ placeholder="璇烽�夋嫨绉绘鐘舵��"
+ style="width: 100%"
+ :disabled="form.utilizationStatus === 'completed'"
+ >
+ <el-option
+ v-for="dict in transplantStatusList"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="璇存槑"
+ align="center"
+ prop="abandonReason"
+ min-width="200"
+ >
+ <template slot-scope="scope">
+ <el-input
+ type="textarea"
+ clearable
+ v-model="scope.row.abandonReason"
+ placeholder="璇疯緭鍏ュ純鐢ㄨ鏄�"
+ :disabled="form.utilizationStatus === 'completed'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="120"
+ class-name="small-padding fixed-width"
+ v-if="form.utilizationStatus !== 'completed'"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-edit"
+ @click="handleEditUtilization(scope.row)"
+ >
+ 缂栬緫
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 鍒╃敤缁熻淇℃伅 -->
+ <div class="utilization-stats" v-if="utilizationData.records.length > 0">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">宸插埄鐢ㄥ櫒瀹�:</span>
+ <span class="stat-value">{{ utilizationData.records.length }} 涓�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">寰呭畬鍠勪俊鎭�:</span>
+ <span class="stat-value">{{ incompleteRecords }} 涓�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">娑夊強鍖婚櫌:</span>
+ <span class="stat-value">{{ uniqueHospitals }} 瀹�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">鍒╃敤鐘舵��:</span>
+ <span class="stat-value">
+ <el-tag :type="getStatusTagType(form.utilizationStatus)">
+ {{ getStatusText(form.utilizationStatus) }}
+ </el-tag>
+ </span>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+
+ <div v-else class="empty-utilization">
+ <el-empty description="鏆傛棤鍒╃敤璁板綍" :image-size="80">
+ <span>璇峰厛閫夋嫨瑕佸埄鐢ㄧ殑鍣ㄥ畼</span>
+ </el-empty>
+ </div>
+ </el-form>
+
+ <div class="dialog-footer" v-if="form.utilizationStatus !== 'completed'">
+ <el-button
+ type="primary"
+ @click="handleSaveUtilization"
+ :loading="saveLoading"
+ :disabled="utilizationData.records.length === 0"
+ >
+ 淇濆瓨鍒╃敤璁板綍
+ </el-button>
+ <el-button
+ type="success"
+ @click="handleConfirmUtilization"
+ :loading="confirmLoading"
+ :disabled="incompleteRecords > 0"
+ >
+ 纭瀹屾垚鍒╃敤
+ </el-button>
+ </div>
+ </el-card>
+
+ <!-- 鍙楄�呰缁嗕俊鎭儴鍒� -->
+ <el-card class="recipient-card" v-if="utilizationData.records.length > 0">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍙楄�呰缁嗕俊鎭�</span>
+ </div>
+
+ <el-tabs v-model="activeRecipientTab" type="card">
+ <el-tab-pane
+ v-for="record in utilizationData.records"
+ :key="record.organNo"
+ :label="record.organName"
+ :name="record.organNo"
+ >
+ <el-form :model="record" label-width="140px">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鍙楄�呭鍚�">
+ <el-input v-model="record.recipientName" placeholder="璇疯緭鍏ュ彈鑰呭鍚�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鍑虹敓骞存湀">
+ <el-date-picker
+ v-model="record.recipientBirthDate"
+ type="month"
+ value-format="yyyy-MM"
+ placeholder="閫夋嫨鍑虹敓骞存湀"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎬у埆">
+ <el-select v-model="record.recipientGender" placeholder="璇烽�夋嫨鎬у埆" style="width: 100%">
+ <el-option label="鐢�" value="0" />
+ <el-option label="濂�" value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绉绘涓績鍚嶇О">
+ <el-input v-model="record.transplantCenter" placeholder="璇疯緭鍏ョЩ妞嶄腑蹇冨悕绉�" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎵�鍦ㄥ湴">
+ <el-input v-model="record.location" placeholder="璇疯緭鍏ユ墍鍦ㄥ湴" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="绉绘鏃ユ湡">
+ <el-date-picker
+ v-model="record.transplantTime"
+ type="date"
+ value-format="yyyy-MM-dd"
+ placeholder="閫夋嫨绉绘鏃ユ湡"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍘熷彂鐥�">
+ <el-input v-model="record.originalDisease" placeholder="璇疯緭鍏ュ師鍙戠梾" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="妫�娴嬫寚鏍�">
+ <el-input
+ type="textarea"
+ :rows="3"
+ v-model="record.testIndicators"
+ placeholder="璇疯緭鍏ュ悇绫诲繀瑕佺殑妫�娴嬫寚鏍�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </el-tab-pane>
+ </el-tabs>
+ </el-card>
+
+ <!-- 闅忚璁板綍閮ㄥ垎 -->
+ <el-card class="followup-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">闅忚璁板綍</span>
+ <el-button
+ type="primary"
+ size="mini"
+ icon="el-icon-plus"
+ @click="handleAddFollowup"
+ style="float: right;"
+ >
+ 鏂板闅忚
+ </el-button>
+ </div>
+
+ <el-table :data="followupData.records" v-loading="loading" border>
+ <el-table-column label="鍣ㄥ畼鍚嶇О" align="center" width="120" prop="organName" />
+ <el-table-column label="闅忚鏃堕棿" align="center" width="160" prop="followupTime">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.followupTime) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="闅忚绫诲瀷" align="center" width="100" prop="followupType">
+ <template slot-scope="scope">
+ <el-tag :type="getFollowupTypeTag(scope.row.followupType)">
+ {{ getFollowupTypeText(scope.row.followupType) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙楄�呮儏鍐�" align="center" prop="recipientCondition" min-width="200" show-overflow-tooltip />
+ <el-table-column label="闅忚鍖荤敓" align="center" width="120" prop="followupDoctor" />
+ <el-table-column label="涓嬫闅忚鏃堕棿" align="center" width="160" prop="nextFollowupTime">
+ <template slot-scope="scope">
+ <span>{{ scope.row.nextFollowupTime || '-' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" width="150">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleViewFollowup(scope.row)"
+ >鏌ョ湅</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-edit"
+ @click="handleEditFollowup(scope.row)"
+ >缂栬緫</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C;"
+ @click="handleDeleteFollowup(scope.row)"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 闄勪欢绠$悊閮ㄥ垎 -->
+ <el-card class="attachment-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鐩稿叧闄勪欢</span>
+ <el-button
+ type="primary"
+ size="mini"
+ icon="el-icon-upload"
+ @click="handleUploadAttachment"
+ >
+ 涓婁紶闄勪欢
+ </el-button>
+ </div>
+
+ <div class="attachment-list">
+ <el-table :data="attachments" style="width: 100%">
+ <el-table-column label="鏂囦欢鍚嶇О" min-width="200">
+ <template slot-scope="scope">
+ <div class="file-info">
+ <i :class="getFileIcon(scope.row.fileName)" style="margin-right: 8px; color: #409EFF;"></i>
+ <span>{{ scope.row.fileName }}</span>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂囦欢绫诲瀷" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag size="small">{{ getFileType(scope.row.fileName) }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏂囦欢澶у皬" width="100" align="center">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="涓婁紶鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.uploadTime) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150" align="center">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handlePreviewAttachment(scope.row)"
+ >棰勮</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-download"
+ @click="handleDownloadAttachment(scope.row)"
+ >涓嬭浇</el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C;"
+ @click="handleRemoveAttachment(scope.row)"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </el-card>
+
+ <!-- 缂栬緫鍒╃敤璁板綍瀵硅瘽妗� -->
+ <el-dialog
+ title="缂栬緫鍣ㄥ畼鍒╃敤璁板綍"
+ :visible.sync="editDialogVisible"
+ width="600px"
+ >
+ <el-form :model="currentRecord" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍣ㄥ畼鍚嶇О">
+ <el-input v-model="currentRecord.organName" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绉绘鐘舵��">
+ <el-select v-model="currentRecord.transplantStatus" style="width: 100%">
+ <el-option
+ v-for="dict in transplantStatusList"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="寮冪敤璇存槑" v-if="currentRecord.transplantStatus === '0'">
+ <el-input
+ type="textarea"
+ :rows="3"
+ v-model="currentRecord.abandonReason"
+ placeholder="璇疯緭鍏ュ純鐢ㄧ殑鍘熷洜璇存槑"
+ />
+ </el-form-item>
+ </el-form>
+ <div slot="footer">
+ <el-button @click="editDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleEditConfirm">纭</el-button>
+ </div>
+ </el-dialog>
+
+ <!-- 闅忚璁板綍瀵硅瘽妗� -->
+ <el-dialog
+ :title="followupDialogTitle"
+ :visible.sync="followupDialogVisible"
+ width="700px"
+ >
+ <el-form :model="currentFollowup" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍣ㄥ畼鍚嶇О">
+ <el-select v-model="currentFollowup.organNo" style="width: 100%">
+ <el-option
+ v-for="organ in utilizationData.records"
+ :key="organ.organNo"
+ :label="organ.organName"
+ :value="organ.organNo"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="闅忚绫诲瀷">
+ <el-select v-model="currentFollowup.followupType" style="width: 100%">
+ <el-option label="甯歌闅忚" value="routine" />
+ <el-option label="绱ф�ラ殢璁�" value="emergency" />
+ <el-option label="鐗规畩闅忚" value="special" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="闅忚鏃堕棿">
+ <el-date-picker
+ v-model="currentFollowup.followupTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="闅忚鍖荤敓">
+ <el-input v-model="currentFollowup.followupDoctor" placeholder="璇疯緭鍏ラ殢璁垮尰鐢�" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="鍙楄�呮儏鍐�">
+ <el-input
+ type="textarea"
+ :rows="3"
+ v-model="currentFollowup.recipientCondition"
+ placeholder="璇疯緭鍏ュ彈鑰呭綋鍓嶆儏鍐�"
+ />
+ </el-form-item>
+ <el-form-item label="鐢ㄨ嵂鎯呭喌">
+ <el-input
+ type="textarea"
+ :rows="2"
+ v-model="currentFollowup.medicationSituation"
+ placeholder="璇疯緭鍏ョ敤鑽儏鍐�"
+ />
+ </el-form-item>
+ <el-form-item label="妫�鏌ョ粨鏋�">
+ <el-input
+ type="textarea"
+ :rows="2"
+ v-model="currentFollowup.testResults"
+ placeholder="璇疯緭鍏ユ鏌ョ粨鏋�"
+ />
+ </el-form-item>
+ <el-form-item label="涓嬫闅忚鏃堕棿">
+ <el-date-picker
+ v-model="currentFollowup.nextFollowupTime"
+ type="date"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ <div slot="footer">
+ <el-button @click="followupDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSaveFollowup">淇濆瓨</el-button>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import {
+ getOrganUtilizationDetail,
+ updateOrganUtilization,
+ addOrganUtilization,
+ saveUtilizationRecords,
+ saveFollowupRecord,
+ getHospitalList,
+ getLeaderList,
+ getCoordinatorList
+} from "./organUtilization";
+
+export default {
+ name: "OrganUtilizationDetail",
+ dicts: ["sys_user_sex", "sys_Organ", "sys_0_1"],
+ data() {
+ return {
+ // 琛ㄥ崟鏁版嵁
+ form: {
+ id: undefined,
+ hospitalNo: "",
+ caseNo: "",
+ donorName: "",
+ gender: "",
+ age: "",
+ birthDate: "",
+ diagnosis: "",
+ utilizationStatus: "pending",
+ allocationTime: "",
+ registrant: "",
+ registrationTime: "",
+ isBodyDonation: "0",
+ receivingUnit: "",
+ responsibleUserId: "",
+ coordinatedUserId1: "",
+ coordinatedUserId2: "",
+ completionTime: ""
+ },
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ rules: {
+ donorName: [
+ { required: true, message: "鎹愮尞鑰呭鍚嶄笉鑳戒负绌�", trigger: "blur" }
+ ],
+ diagnosis: [
+ { required: true, message: "鐤剧梾璇婃柇涓嶈兘涓虹┖", trigger: "blur" }
+ ]
+ },
+ // 鍒╃敤璁板綍楠岃瘉瑙勫垯
+ utilizationRules: {},
+ // 淇濆瓨鍔犺浇鐘舵��
+ saveLoading: false,
+ confirmLoading: false,
+ // 鍔犺浇鐘舵��
+ loading: false,
+ // 閫変腑鐨勫櫒瀹�
+ selectedOrgans: [],
+ // 鍖婚櫌鍒楄〃
+ hospitalList: [],
+ // 璐熻矗浜哄垪琛�
+ leaderList: [],
+ // 鍗忚皟鍛樺垪琛�
+ coordinatorList: [],
+ // 绉绘鐘舵�佸垪琛�
+ transplantStatusList: [
+ { value: "1", label: "宸茬Щ妞�" },
+ { value: "0", label: "鏈Щ妞�" },
+ { value: "2", label: "绉绘涓�" }
+ ],
+ // 鍒╃敤璁板綍鏁版嵁
+ utilizationData: {
+ records: []
+ },
+ // 闅忚璁板綍鏁版嵁
+ followupData: {
+ records: []
+ },
+ // 闄勪欢鏁版嵁
+ attachments: [],
+ // 褰撳墠婵�娲荤殑鍙楄�呮爣绛�
+ activeRecipientTab: "",
+ // 缂栬緫瀵硅瘽妗�
+ editDialogVisible: false,
+ currentRecord: {},
+ currentEditIndex: -1,
+ // 闅忚瀵硅瘽妗�
+ followupDialogVisible: false,
+ followupDialogTitle: "鏂板闅忚璁板綍",
+ currentFollowup: {},
+ isEditingFollowup: false
+ };
+ },
+ computed: {
+ // 褰撳墠鐢ㄦ埛淇℃伅
+ currentUser() {
+ return JSON.parse(sessionStorage.getItem("user") || "{}");
+ },
+ // 涓嶅畬鏁寸殑璁板綍鏁伴噺
+ incompleteRecords() {
+ return this.utilizationData.records.filter(
+ record =>
+ !record.caseNo ||
+ !record.hospitalNo ||
+ !record.recipientName ||
+ !record.transplantTime
+ ).length;
+ },
+ // 鍞竴鍖婚櫌鏁伴噺
+ uniqueHospitals() {
+ const hospitals = this.utilizationData.records
+ .map(record => record.hospitalNo)
+ .filter(Boolean);
+ return new Set(hospitals).size;
+ }
+ },
+ created() {
+ const id = this.$route.query.id;
+ if (id) {
+ this.getDetail(id);
+ } else {
+ this.generateCaseNo();
+ this.form.registrant = this.currentUser.username || "褰撳墠鐢ㄦ埛";
+ this.form.registrationTime = new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19);
+ }
+ this.getHospitalData();
+ this.getLeaderData();
+ this.getCoordinatorData();
+ },
+ methods: {
+ // 鐢熸垚妗堜緥缂栧彿
+ generateCaseNo() {
+ const timestamp = Date.now().toString();
+ this.form.hospitalNo = "D" + timestamp.slice(-6);
+ this.form.caseNo = "C" + timestamp.slice(-6);
+ },
+ // 鑾峰彇璇︽儏
+ getDetail(id) {
+ this.loading = true;
+ getOrganUtilizationDetail(id)
+ .then(response => {
+ if (response.code === 200) {
+ this.form = response.data;
+ if (response.data.utilizationRecords) {
+ this.utilizationData.records = response.data.utilizationRecords;
+ this.selectedOrgans = response.data.utilizationRecords.map(
+ item => item.organNo
+ );
+ if (this.utilizationData.records.length > 0) {
+ this.activeRecipientTab = this.utilizationData.records[0].organNo;
+ }
+ }
+ if (response.data.followupRecords) {
+ this.followupData.records = response.data.followupRecords;
+ }
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇鍣ㄥ畼鍒╃敤璇︽儏澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇璇︽儏澶辫触");
+ });
+ },
+ // 鑾峰彇鍖婚櫌鏁版嵁
+ getHospitalData() {
+ getHospitalList().then(response => {
+ if (response.code === 200) {
+ this.hospitalList = response.data;
+ }
+ });
+ },
+ // 鑾峰彇璐熻矗浜烘暟鎹�
+ getLeaderData() {
+ getLeaderList().then(response => {
+ if (response.code === 200) {
+ this.leaderList = response.data;
+ }
+ });
+ },
+ // 鑾峰彇鍗忚皟鍛樻暟鎹�
+ getCoordinatorData() {
+ getCoordinatorList().then(response => {
+ if (response.code === 200) {
+ this.coordinatorList = response.data;
+ }
+ });
+ },
+ // 鍣ㄥ畼閫夋嫨鐘舵�佸彉鍖�
+ handleOrganSelectionChange(selectedValues) {
+ const currentOrganNos = this.utilizationData.records.map(
+ item => item.organNo
+ );
+
+ // 鏂板閫夋嫨鐨勫櫒瀹�
+ selectedValues.forEach(organValue => {
+ if (!currentOrganNos.includes(organValue)) {
+ const organInfo = this.dict.type.sys_Organ.find(
+ item => item.value === organValue
+ );
+ if (organInfo) {
+ this.utilizationData.records.push({
+ organName: organInfo.label,
+ organNo: organValue,
+ id: null,
+ utilizationId: this.form.id,
+ caseNo: "",
+ hospitalNo: "",
+ hospitalName: "",
+ recipientName: "",
+ transplantDoctor: "",
+ transplantTime: "",
+ transplantStatus: "1",
+ abandonReason: "",
+ recipientBirthDate: "",
+ recipientGender: "",
+ transplantCenter: "",
+ location: "",
+ originalDisease: "",
+ testIndicators: ""
+ });
+ }
+ }
+ });
+
+ // 绉婚櫎鍙栨秷閫夋嫨鐨勫櫒瀹�
+ this.utilizationData.records = this.utilizationData.records.filter(
+ record => {
+ if (selectedValues.includes(record.organNo)) {
+ return true;
+ } else {
+ if (record.id) {
+ this.$confirm(
+ "鍒犻櫎鍣ㄥ畼鍒╃敤鏁版嵁鍚庡皢鏃犳硶鎭㈠锛屾偍纭鍒犻櫎璇ユ潯璁板綍鍚楋紵",
+ "鎻愮ず",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ )
+ .then(() => {
+ this.utilizationData.records = this.utilizationData.records.filter(
+ r => r.organNo !== record.organNo
+ );
+ this.$message.success("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {
+ this.selectedOrgans.push(record.organNo);
+ });
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ );
+ },
+ // 鍖婚櫌閫夋嫨鍙樺寲
+ handleHospitalChange(row, hospitalNo) {
+ const hospital = this.hospitalList.find(
+ item => item.hospitalNo === hospitalNo
+ );
+ if (hospital) {
+ row.hospitalName = hospital.hospitalName;
+ }
+ },
+ // 缂栬緫鍒╃敤璁板綍
+ handleEditUtilization(row) {
+ const index = this.utilizationData.records.findIndex(
+ item => item.organNo === row.organNo
+ );
+ if (index !== -1) {
+ this.currentRecord = { ...row };
+ this.currentEditIndex = index;
+ this.editDialogVisible = true;
+ }
+ },
+ // 纭缂栬緫
+ handleEditConfirm() {
+ if (this.currentEditIndex !== -1) {
+ this.utilizationData.records[this.currentEditIndex] = {
+ ...this.currentRecord
+ };
+ this.$message.success("鍒╃敤璁板綍鏇存柊鎴愬姛");
+ this.editDialogVisible = false;
+ }
+ },
+ // 鍣ㄥ畼琛屾牱寮�
+ getOrganRowClassName({ row }) {
+ if (
+ !row.caseNo ||
+ !row.hospitalNo ||
+ !row.recipientName ||
+ !row.transplantTime
+ ) {
+ return "warning-row";
+ }
+ return "";
+ },
+ // 鑾峰彇鐘舵�佹爣绛剧被鍨�
+ getStatusTagType(status) {
+ const typeMap = {
+ completed: "success",
+ in_progress: "warning",
+ pending: "info"
+ };
+ return typeMap[status] || "info";
+ },
+ // 鑾峰彇鐘舵�佹枃鏈�
+ getStatusText(status) {
+ const textMap = {
+ completed: "宸插畬鎴�",
+ in_progress: "杩涜涓�",
+ pending: "寰呭鐞�"
+ };
+ return textMap[status] || "鏈煡";
+ },
+ // 鑾峰彇闅忚绫诲瀷鏍囩
+ getFollowupTypeTag(type) {
+ const typeMap = {
+ routine: "success",
+ emergency: "danger",
+ special: "warning"
+ };
+ return typeMap[type] || "info";
+ },
+ // 鑾峰彇闅忚绫诲瀷鏂囨湰
+ getFollowupTypeText(type) {
+ const textMap = {
+ routine: "甯歌闅忚",
+ emergency: "绱ф�ラ殢璁�",
+ special: "鐗规畩闅忚"
+ };
+ return textMap[type] || "鏈煡";
+ },
+ // 淇濆瓨鍩烘湰淇℃伅
+ handleSave() {
+ this.$refs.form.validate(valid => {
+ if (valid) {
+ this.saveLoading = true;
+ const apiMethod = this.form.id
+ ? updateOrganUtilization
+ : addOrganUtilization;
+
+ apiMethod(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("淇濆瓨鎴愬姛");
+ if (!this.form.id) {
+ this.form.id = response.data.id;
+ this.$router.replace({
+ query: { ...this.$route.query, id: this.form.id }
+ });
+ }
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨澶辫触:", error);
+ this.$message.error("淇濆瓨澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ }
+ });
+ },
+ // 淇濆瓨鍒╃敤璁板綍
+ handleSaveUtilization() {
+ if (!this.form.id) {
+ this.$message.warning("璇峰厛淇濆瓨鍩烘湰淇℃伅");
+ return;
+ }
+
+ this.saveLoading = true;
+ saveUtilizationRecords(this.form.id, this.utilizationData.records)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒╃敤璁板綍淇濆瓨鎴愬姛");
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨鍒╃敤璁板綍澶辫触:", error);
+ this.$message.error("淇濆瓨鍒╃敤璁板綍澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ },
+ // 纭瀹屾垚鍒╃敤
+ handleConfirmUtilization() {
+ if (this.incompleteRecords > 0) {
+ this.$message.warning("璇峰厛瀹屽杽鎵�鏈夊埄鐢ㄨ褰曠殑淇℃伅");
+ return;
+ }
+
+ this.$confirm("纭瀹屾垚鍣ㄥ畼鍒╃敤鍚楋紵瀹屾垚鍚庡皢鏃犳硶淇敼鍒╃敤淇℃伅銆�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.confirmLoading = true;
+ this.form.utilizationStatus = "completed";
+ this.form.completionTime = new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19);
+
+ updateOrganUtilization(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍣ㄥ畼鍒╃敤宸插畬鎴�");
+ }
+ })
+ .catch(error => {
+ console.error("纭鍒╃敤澶辫触:", error);
+ this.$message.error("纭鍒╃敤澶辫触");
+ })
+ .finally(() => {
+ this.confirmLoading = false;
+ });
+ })
+ .catch(() => {});
+ },
+ // 瀹屾垚鍒╃敤
+ handleComplete() {
+ this.handleConfirmUtilization();
+ },
+ // 鏂板闅忚璁板綍
+ handleAddFollowup() {
+ this.followupDialogTitle = "鏂板闅忚璁板綍";
+ this.isEditingFollowup = false;
+ this.currentFollowup = {
+ organNo: this.utilizationData.records.length > 0 ? this.utilizationData.records[0].organNo : "",
+ followupTime: new Date().toISOString().replace("T", " ").substring(0, 19),
+ followupType: "routine",
+ recipientCondition: "",
+ medicationSituation: "",
+ testResults: "",
+ nextFollowupTime: "",
+ followupDoctor: ""
+ };
+ this.followupDialogVisible = true;
+ },
+ // 鏌ョ湅闅忚璁板綍
+ handleViewFollowup(record) {
+ this.currentFollowup = { ...record };
+ this.followupDialogTitle = "鏌ョ湅闅忚璁板綍";
+ this.followupDialogVisible = true;
+ },
+ // 缂栬緫闅忚璁板綍
+ handleEditFollowup(record) {
+ this.followupDialogTitle = "缂栬緫闅忚璁板綍";
+ this.isEditingFollowup = true;
+ this.currentFollowup = { ...record };
+ this.followupDialogVisible = true;
+ },
+ // 鍒犻櫎闅忚璁板綍
+ handleDeleteFollowup(record) {
+ this.$confirm("纭畾瑕佸垹闄よ繖鏉¢殢璁胯褰曞悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ const index = this.followupData.records.findIndex(
+ item => item.id === record.id
+ );
+ if (index !== -1) {
+ this.followupData.records.splice(index, 1);
+ this.$message.success("闅忚璁板綍鍒犻櫎鎴愬姛");
+ }
+ })
+ .catch(() => {});
+ },
+ // 淇濆瓨闅忚璁板綍
+ handleSaveFollowup() {
+ if (!this.currentFollowup.organNo) {
+ this.$message.warning("璇烽�夋嫨鍣ㄥ畼");
+ return;
+ }
+
+ if (!this.currentFollowup.followupTime) {
+ this.$message.warning("璇烽�夋嫨闅忚鏃堕棿");
+ return;
+ }
+
+ this.saveLoading = true;
+
+ // 鑾峰彇鍣ㄥ畼鍚嶇О
+ const organRecord = this.utilizationData.records.find(
+ item => item.organNo === this.currentFollowup.organNo
+ );
+ const organName = organRecord ? organRecord.organName : "";
+
+ const followupData = {
+ ...this.currentFollowup,
+ organName: organName,
+ utilizationId: this.form.id
+ };
+
+ saveFollowupRecord(followupData)
+ .then(response => {
+ if (response.code === 200) {
+ if (this.isEditingFollowup) {
+ // 鏇存柊鐜版湁璁板綍
+ const index = this.followupData.records.findIndex(
+ item => item.id === this.currentFollowup.id
+ );
+ if (index !== -1) {
+ this.followupData.records[index] = response.data;
+ }
+ } else {
+ // 娣诲姞鏂拌褰�
+ this.followupData.records.push(response.data);
+ }
+ this.$message.success("闅忚璁板綍淇濆瓨鎴愬姛");
+ this.followupDialogVisible = false;
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨闅忚璁板綍澶辫触:", error);
+ this.$message.error("淇濆瓨闅忚璁板綍澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ },
+ // 涓婁紶闄勪欢
+ handleUploadAttachment() {
+ this.$message.info("闄勪欢涓婁紶鍔熻兘");
+ },
+ // 棰勮闄勪欢
+ handlePreviewAttachment(attachment) {
+ this.$message.info("闄勪欢棰勮鍔熻兘");
+ },
+ // 涓嬭浇闄勪欢
+ handleDownloadAttachment(attachment) {
+ this.$message.info("闄勪欢涓嬭浇鍔熻兘");
+ },
+ // 鍒犻櫎闄勪欢
+ handleRemoveAttachment(attachment) {
+ this.$confirm("纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.$message.success("闄勪欢鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+ },
+ // 鑾峰彇鏂囦欢鍥炬爣
+ getFileIcon(fileName) {
+ const ext = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const iconMap = {
+ pdf: "el-icon-document",
+ doc: "el-icon-document",
+ docx: "el-icon-document",
+ xls: "el-icon-document",
+ xlsx: "el-icon-document",
+ jpg: "el-icon-picture",
+ jpeg: "el-icon-picture",
+ png: "el-icon-picture"
+ };
+ return iconMap[ext] || "el-icon-document";
+ },
+ // 鑾峰彇鏂囦欢绫诲瀷
+ getFileType(fileName) {
+ const ext = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const typeMap = {
+ pdf: "PDF",
+ doc: "DOC",
+ docx: "DOCX",
+ xls: "XLS",
+ xlsx: "XLSX",
+ jpg: "JPG",
+ jpeg: "JPEG",
+ png: "PNG"
+ };
+ return typeMap[ext] || ext.toUpperCase();
+ },
+ // 鏂囦欢澶у皬鏍煎紡鍖�
+ formatFileSize(size) {
+ if (size === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(size) / Math.log(k));
+ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time) {
+ if (!time) return "";
+ const date = new Date(time);
+ return `${date.getFullYear()}-${(date.getMonth() + 1)
+ .toString()
+ .padStart(2, "0")}-${date
+ .getDate()
+ .toString()
+ .padStart(2, "0")} ${date
+ .getHours()
+ .toString()
+ .padStart(2, "0")}:${date
+ .getMinutes()
+ .toString()
+ .padStart(2, "0")}`;
+ },
+ // 鎻愪氦褰掓。
+ handleSubmitArchive() {
+ this.$confirm("纭鎻愪氦褰掓。鍚楋紵褰掓。鍚庡皢鏃犳硶淇敼鏁版嵁銆�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.$message.success("鎻愪氦褰掓。鎴愬姛");
+ })
+ .catch(() => {});
+ },
+ // 鎾ら攢褰掓。
+ handleRevokeArchive() {
+ this.$confirm("纭鎾ら攢褰掓。鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.$message.success("鎾ら攢褰掓。鎴愬姛");
+ })
+ .catch(() => {});
+ },
+ // 缁堟妗堜緥
+ handleTerminateCase() {
+ this.$confirm("纭缁堟妗堜緥鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.$message.success("妗堜緥宸茬粓姝�");
+ })
+ .catch(() => {});
+ },
+ // 鎭㈠妗堜緥
+ handleRestoreCase() {
+ this.$confirm("纭鎭㈠妗堜緥鍚楋紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.$message.success("妗堜緥宸叉仮澶�");
+ })
+ .catch(() => {});
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+.organ-utilization-detail {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.detail-card, .utilization-card, .recipient-card, .followup-card, .attachment-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ border: 1px solid #e4e7ed;
+}
+
+.detail-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 1.4;
+}
+/* 琛ㄦ牸鏁翠綋鏍峰紡 */
+:deep(.el-table) {
+ border-radius: 8px;
+ overflow: hidden;
+}
+:deep(.el-table th) {
+ background-color: #f5f7fa;
+ color: #606266;
+ font-weight: 600;
+}
+:deep(.el-table .cell) {
+ padding: 12px 8px;
+ line-height: 1.5;
+}
+
+/* 鏂戦┈绾硅〃鏍艰 */
+:deep(.el-table__row.warning-row) {
+ background-color: #fdf6ec;
+}
+:deep(.el-table__row.default-row) {
+ background-color: #f0f9ff;
+}
+
+/* 榧犳爣鎮仠鏁堟灉 */
+:deep(.el-table--enable-row-hover .el-table__body tr:hover > td) {
+ background-color: #f5f7fa !important;
+}
+
+/* 缁熻淇℃伅鏍峰紡 */
+.utilization-stats {
+ margin-top: 20px;
+ padding: 15px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 8px;
+ color: white;
+}
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 10px;
+ text-align: center;
+}
+.stat-label {
+ font-size: 18px;
+ opacity: 0.9;
+ margin-bottom: 5px;
+}
+.stat-value {
+ font-size: 20px;
+ font-weight: bold;
+}
+/* 琛ㄥ崟鏍囩鍜岃緭鍏ユ鏍峰紡 */
+:deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #606266;
+}
+:deep(.el-input__inner) {
+ border-radius: 4px;
+ transition: border-color 0.3s ease;
+}
+:deep(.el-input__inner:focus) {
+ border-color: #409EFF;
+ box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+}
+
+/* 鎸夐挳鏍峰紡浼樺寲 */
+:deep(.el-button--primary) {
+ background: linear-gradient(135deg, #409EFF 0%, #3375e0 100%);
+ border: none;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+}
+:deep(.el-button--primary:hover) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+}
+
+/* 鏍囩椤垫牱寮� */
+:deep(.el-tabs__item) {
+ font-weight: 500;
+}
+:deep(.el-tabs__active-bar) {
+ background: linear-gradient(135deg, #409EFF 0%, #3375e0 100%);
+}
+/* 骞虫澘璁惧閫傞厤 */
+@media (max-width: 1024px) {
+ .organ-utilization-detail {
+ padding: 15px;
+ }
+ :deep(.el-col) {
+ margin-bottom: 10px;
+ }
+}
+
+/* 鎵嬫満璁惧閫傞厤 */
+@media (max-width: 768px) {
+ .organ-utilization-detail {
+ padding: 10px;
+ }
+ .detail-title {
+ font-size: 16px;
+ }
+ :deep(.el-table .cell) {
+ padding: 8px 4px;
+ font-size: 12px;
+ }
+ :deep(.el-form-item__label) {
+ font-size: 12px;
+ }
+}
+
+/* 瓒呭皬灞忓箷璁惧 */
+@media (max-width: 480px) {
+ .organ-utilization-detail {
+ padding: 5px;
+ }
+ :deep(.el-card__header) {
+ padding: 10px 15px;
+ }
+}
+/* 绌虹姸鎬佹牱寮� */
+.empty-utilization {
+ text-align: center;
+ padding: 40px 0;
+ color: #909399;
+ background: #fafafa;
+ border-radius: 4px;
+ margin: 20px 0;
+}
+
+/* 鍔犺浇鐘舵�� */
+:deep(.el-loading-mask) {
+ border-radius: 4px;
+}
+
+/* 鏂囦欢淇℃伅鏍峰紡 */
+.file-info {
+ display: flex;
+ align-items: center;
+ padding: 5px 0;
+}
+.file-info i {
+ font-size: 18px;
+ margin-right: 10px;
+}
+
+/* 鍔ㄧ敾鏁堟灉 */
+.fade-enter-active, .fade-leave-active {
+ transition: opacity 0.3s ease;
+}
+.fade-enter, .fade-leave-to {
+ opacity: 0;
+}
+</style>
diff --git a/src/views/business/OrganUtilization/index.vue b/src/views/business/OrganUtilization/index.vue
new file mode 100644
index 0000000..6b1941b
--- /dev/null
+++ b/src/views/business/OrganUtilization/index.vue
@@ -0,0 +1,377 @@
+<template>
+ <div class="organ-utilization-list">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-card class="search-card">
+ <el-form
+ :model="queryParams"
+ ref="queryForm"
+ :inline="true"
+ label-width="100px"
+ >
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input
+ v-model="queryParams.hospitalNo"
+ 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="utilizationStatus">
+ <el-select
+ v-model="queryParams.utilizationStatus"
+ placeholder="璇烽�夋嫨鍒╃敤鐘舵��"
+ clearable
+ style="width: 200px"
+ >
+ <el-option label="宸插畬鎴�" value="completed" />
+ <el-option label="杩涜涓�" value="in_progress" />
+ <el-option label="寰呭鐞�" value="pending" />
+ </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-card class="tool-card">
+ <el-row :gutter="10">
+ <el-col :span="16">
+ <el-button type="primary" icon="el-icon-plus" @click="handleCreate"
+ >鏂板缓鍒╃敤</el-button
+ >
+ <el-button
+ type="success"
+ icon="el-icon-edit"
+ :disabled="single"
+ @click="handleUpdate"
+ >淇敼</el-button
+ >
+ <el-button
+ type="danger"
+ icon="el-icon-delete"
+ :disabled="multiple"
+ @click="handleDelete"
+ >鍒犻櫎</el-button
+ >
+ </el-col>
+ <el-col :span="8" style="text-align: right">
+ <el-tooltip content="鍒锋柊" placement="top">
+ <el-button icon="el-icon-refresh" circle @click="getList" />
+ </el-tooltip>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-card>
+ <el-table
+ v-loading="loading"
+ :data="organUtilizationList"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column
+ label="浣忛櫌鍙�"
+ align="center"
+ prop="hospitalNo"
+ width="120"
+ />
+ <el-table-column
+ label="鎹愮尞鑰呭鍚�"
+ align="center"
+ prop="donorName"
+ width="120"
+ />
+ <el-table-column label="鎬у埆" align="center" prop="gender" width="80">
+ <template slot-scope="scope">
+ <dict-tag
+ :options="dict.type.sys_user_sex"
+ :value="parseInt(scope.row.gender)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="骞撮緞" align="center" prop="age" width="80" />
+ <el-table-column
+ label="鐤剧梾璇婃柇"
+ align="center"
+ prop="diagnosis"
+ min-width="180"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍒╃敤鐘舵��"
+ align="center"
+ prop="utilizationStatus"
+ width="100"
+ >
+ <template slot-scope="scope">
+ <el-tag :type="getStatusTagType(scope.row.utilizationStatus)">
+ {{ getStatusText(scope.row.utilizationStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鐧昏浜�"
+ align="center"
+ prop="registrant"
+ width="100"
+ />
+ <el-table-column
+ label="鐧昏鏃堕棿"
+ align="center"
+ prop="registrationTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.registrationTime
+ ? parseTime(scope.row.registrationTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="150"
+ class-name="small-padding fixed-width"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleView(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-delete"
+ style="color: #F56C6C"
+ @click="handleDelete(scope.row)"
+ >鍒犻櫎</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-card>
+ </div>
+</template>
+
+<script>
+import { listOrganUtilization, delOrganUtilization } from "./organUtilization";
+import Pagination from "@/components/Pagination";
+
+export default {
+ name: "OrganUtilizationList",
+ components: { Pagination },
+ dicts: ["sys_user_sex"],
+ data() {
+ return {
+ // 閬僵灞�
+ loading: true,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 鍣ㄥ畼鍒╃敤琛ㄦ牸鏁版嵁
+ organUtilizationList: [],
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ hospitalNo: undefined,
+ donorName: undefined,
+ utilizationStatus: undefined
+ }
+ };
+ },
+ created() {
+ this.getList();
+ },
+ methods: {
+ // 鏌ヨ鍣ㄥ畼鍒╃敤鍒楄〃
+ getList() {
+ this.loading = true;
+ listOrganUtilization(this.queryParams)
+ .then(response => {
+ if (response.code === 200) {
+ this.organUtilizationList = response.data.rows;
+ this.total = response.data.total;
+ } else {
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇鍣ㄥ畼鍒╃敤鍒楄〃澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ });
+ },
+ // 鑾峰彇鐘舵�佹爣绛剧被鍨�
+ getStatusTagType(status) {
+ const typeMap = {
+ completed: "success",
+ in_progress: "warning",
+ pending: "info"
+ };
+ return typeMap[status] || "info";
+ },
+ // 鑾峰彇鐘舵�佹枃鏈�
+ getStatusText(status) {
+ const textMap = {
+ completed: "宸插畬鎴�",
+ in_progress: "杩涜涓�",
+ pending: "寰呭鐞�"
+ };
+ return textMap[status] || "鏈煡";
+ },
+ // 鎼滅储鎸夐挳鎿嶄綔
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ // 閲嶇疆鎸夐挳鎿嶄綔
+ resetQuery() {
+ this.$refs.queryForm.resetFields();
+ this.handleQuery();
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.id);
+ this.single = selection.length !== 1;
+ this.multiple = !selection.length;
+ },
+ // 鏌ョ湅璇︽儏
+ handleView(row) {
+ this.$router.push({
+ path: "/case/organUtilizationInfo",
+ query: { id: row.id }
+ });
+ },
+ // 鏂板鎸夐挳鎿嶄綔
+ handleCreate() {
+ this.$router.push("/case/organUtilizationInfo");
+ },
+ // 淇敼鎸夐挳鎿嶄綔
+ handleUpdate(row) {
+ const id = row.id || this.ids[0];
+ this.$router.push({
+ path: "/case/organUtilizationInfo",
+ query: { id: id }
+ });
+ },
+ // 鍒犻櫎鎸夐挳鎿嶄綔
+ handleDelete(row) {
+ const ids = row.id ? [row.id] : this.ids;
+ this.$confirm("鏄惁纭鍒犻櫎閫変腑鐨勬暟鎹」锛�", "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return delOrganUtilization(ids);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒犻櫎鎴愬姛");
+ this.getList();
+ }
+ })
+ .catch(() => {});
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time, pattern) {
+ if (!time) return "";
+ const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
+ let date;
+ if (typeof time === "object") {
+ date = time;
+ } else {
+ if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+ time = parseInt(time);
+ }
+ if (typeof time === "number" && time.toString().length === 10) {
+ time = time * 1000;
+ }
+ date = new Date(time);
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ };
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key];
+ if (key === "a") {
+ return ["鏃�", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�"][value];
+ }
+ if (result.length > 0 && value < 10) {
+ value = "0" + value;
+ }
+ return value || 0;
+ });
+ return time_str;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.organ-utilization-list {
+ padding: 20px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.tool-card {
+ margin-bottom: 20px;
+}
+
+.fixed-width .el-button {
+ margin: 0 5px;
+}
+</style>
diff --git a/src/views/business/OrganUtilization/organUtilization.js b/src/views/business/OrganUtilization/organUtilization.js
new file mode 100644
index 0000000..5308a5e
--- /dev/null
+++ b/src/views/business/OrganUtilization/organUtilization.js
@@ -0,0 +1,439 @@
+// 妯℃嫙鍣ㄥ畼鍒╃敤鏁版嵁
+const mockOrganUtilizationData = [
+ {
+ id: 1,
+ hospitalNo: "D202312001",
+ caseNo: "C202312001",
+ donorName: "寮犱笁",
+ gender: "0",
+ age: 45,
+ diagnosis: "鑴戝浼�",
+ registrant: "鏉庡崗璋冨憳",
+ registrationTime: "2023-12-01 15:00:00",
+ createTime: "2023-12-01 10:00:00",
+ utilizationStatus: "completed",
+ completionTime: "2023-12-01 16:30:00",
+ isBodyDonation: "1",
+ receivingUnit: "鍖荤澶у瑙e墫鏁欑爺瀹�",
+ responsibleUserId: "L001",
+ coordinatedUserId1: "C001",
+ coordinatedUserId2: "C002"
+ },
+ {
+ id: 2,
+ hospitalNo: "D202312002",
+ caseNo: "C202312002",
+ donorName: "鏉庡洓",
+ gender: "1",
+ age: 38,
+ diagnosis: "蹇冭剰楠ゅ仠",
+ registrant: "寮犲崗璋冨憳",
+ registrationTime: "2023-12-02 10:00:00",
+ createTime: "2023-12-02 08:30:00",
+ utilizationStatus: "in_progress",
+ completionTime: "",
+ isBodyDonation: "0",
+ receivingUnit: "",
+ responsibleUserId: "",
+ coordinatedUserId1: "",
+ coordinatedUserId2: ""
+ },
+ {
+ id: 3,
+ hospitalNo: "D202312003",
+ caseNo: "C202312003",
+ donorName: "鐜嬩簲",
+ gender: "0",
+ age: 52,
+ diagnosis: "鑴戞姝�",
+ registrant: "璧靛崗璋冨憳",
+ registrationTime: "2023-12-03 17:20:00",
+ createTime: "2023-12-03 14:00:00",
+ utilizationStatus: "pending",
+ completionTime: "",
+ isBodyDonation: "0",
+ receivingUnit: "",
+ responsibleUserId: "",
+ coordinatedUserId1: "",
+ coordinatedUserId2: ""
+ }
+];
+
+// 妯℃嫙鍣ㄥ畼鍒╃敤璁板綍鏁版嵁
+const mockUtilizationRecordData = [
+ {
+ id: 1,
+ utilizationId: 1,
+ organName: "鑲濊剰",
+ organNo: "L001",
+ caseNo: "C202312001",
+ hospitalNo: "H1001",
+ hospitalName: "鍖椾含鍗忓拰鍖婚櫌",
+ recipientName: "鐜�",
+ transplantDoctor: "寮犲尰鐢�",
+ transplantTime: "2023-12-01 16:00:00",
+ transplantStatus: "1",
+ abandonReason: "",
+ recipientBirthDate: "1980-05-15",
+ recipientGender: "0",
+ transplantCenter: "鍖椾含鍗忓拰鍖婚櫌绉绘涓績",
+ location: "鍖椾含甯�",
+ originalDisease: "鑲濈‖鍖�",
+ testIndicators: "鑲濆姛鑳芥甯革紝琛�鍨嬪尮閰�"
+ },
+ {
+ id: 2,
+ utilizationId: 1,
+ organName: "鑲捐剰",
+ organNo: "K001",
+ caseNo: "C202312001",
+ hospitalNo: "H1002",
+ hospitalName: "涓婃捣鐟為噾鍖婚櫌",
+ recipientName: "鏉�",
+ transplantDoctor: "鐜嬪尰鐢�",
+ transplantTime: "2023-12-01 16:30:00",
+ transplantStatus: "1",
+ abandonReason: "",
+ recipientBirthDate: "1975-08-20",
+ recipientGender: "1",
+ transplantCenter: "涓婃捣鐟為噾鍖婚櫌绉绘涓績",
+ location: "涓婃捣甯�",
+ originalDisease: "灏挎瘨鐥�",
+ testIndicators: "鑲惧姛鑳芥甯革紝鍏嶇柅鍖归厤"
+ },
+ {
+ id: 3,
+ utilizationId: 1,
+ organName: "蹇冭剰",
+ organNo: "H001",
+ caseNo: "C202312001",
+ hospitalNo: "H1003",
+ hospitalName: "骞垮窞涓北鍖婚櫌",
+ recipientName: "闄�",
+ transplantDoctor: "鍒樺尰鐢�",
+ transplantTime: "2023-12-01 17:00:00",
+ transplantStatus: "1",
+ abandonReason: "",
+ recipientBirthDate: "1982-03-10",
+ recipientGender: "0",
+ transplantCenter: "骞垮窞涓北鍖婚櫌蹇冭剰涓績",
+ location: "骞垮窞甯�",
+ originalDisease: "蹇冭倢鐥�",
+ testIndicators: "蹇冨姛鑳芥甯革紝琛�鍨嬪尮閰�"
+ }
+];
+
+// 妯℃嫙闅忚璁板綍鏁版嵁
+const mockFollowupRecordData = [
+ {
+ id: 1,
+ utilizationId: 1,
+ organNo: "L001",
+ followupTime: "2024-01-01 10:00:00",
+ followupType: "routine",
+ recipientCondition: "鎭㈠鑹ソ锛岃倽鍔熻兘姝e父",
+ medicationSituation: "鍏嶇柅鎶戝埗鍓傝寰嬫湇鐢�",
+ testResults: "鑲濆姛鑳芥寚鏍囨甯革紝琛�鑽祿搴﹁揪鏍�",
+ nextFollowupTime: "2024-04-01",
+ followupDoctor: "寮犲尰鐢�",
+ attachment: "鑲濆姛鑳芥鏌ユ姤鍛�.pdf"
+ },
+ {
+ id: 2,
+ utilizationId: 1,
+ organNo: "K001",
+ followupTime: "2024-01-02 09:30:00",
+ followupType: "routine",
+ recipientCondition: "鑲惧姛鑳界ǔ瀹氾紝鏃犳帓鏂ュ弽搴�",
+ medicationSituation: "鎶楁帓鏂ヨ嵂鐗╂寜鏃舵湇鐢�",
+ testResults: "鑲岄厫姘村钩姝e父锛屽翱甯歌鏃犲紓甯�",
+ nextFollowupTime: "2024-04-02",
+ followupDoctor: "鐜嬪尰鐢�",
+ attachment: "鑲惧姛鑳介殢璁挎姤鍛�.pdf"
+ },
+ {
+ id: 3,
+ utilizationId: 1,
+ organNo: "H001",
+ followupTime: "2024-01-03 11:00:00",
+ followupType: "emergency",
+ recipientCondition: "鍑虹幇杞诲井鎺掓枼鍙嶅簲锛屽凡澶勭悊",
+ medicationSituation: "璋冩暣鍏嶇柅鎶戝埗鍓傚墏閲�",
+ testResults: "蹇冨姛鑳芥寚鏍囧熀鏈甯革紝闇�瀵嗗垏瑙傚療",
+ nextFollowupTime: "2024-01-10",
+ followupDoctor: "鍒樺尰鐢�",
+ attachment: "蹇冭剰绉绘闅忚璁板綍.pdf"
+ }
+];
+
+// 妯℃嫙鍖婚櫌鏁版嵁
+const mockHospitalData = [
+ { id: 1, hospitalNo: "H1001", hospitalName: "鍖椾含鍗忓拰鍖婚櫌", type: "4" },
+ { id: 2, hospitalNo: "H1002", hospitalName: "涓婃捣鐟為噾鍖婚櫌", type: "4" },
+ { id: 3, hospitalNo: "H1003", hospitalName: "骞垮窞涓北鍖婚櫌", type: "4" }
+];
+
+// 妯℃嫙璐熻矗浜烘暟鎹�
+const mockLeaderData = [
+ { reportNo: "L001", reportName: "寮犱富浠�" },
+ { reportNo: "L002", reportName: "鐜嬩富浠�" },
+ { reportNo: "L003", reportName: "鏉庝富浠�" }
+];
+
+// 妯℃嫙鍗忚皟鍛樻暟鎹�
+const mockCoordinatorData = [
+ { reportNo: "C001", reportName: "寮犲崗璋冨憳" },
+ { reportNo: "C002", reportName: "鏉庡崗璋冨憳" },
+ { reportNo: "C003", reportName: "鐜嬪崗璋冨憳" },
+ { reportNo: "C004", reportName: "璧靛崗璋冨憳" }
+];
+
+// 妯℃嫙API鍝嶅簲寤惰繜
+const delay = (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));
+
+// 鏌ヨ鍣ㄥ畼鍒╃敤鍒楄〃
+export const listOrganUtilization = async (queryParams = {}) => {
+ await delay();
+
+ const {
+ pageNum = 1,
+ pageSize = 10,
+ hospitalNo,
+ donorName,
+ utilizationStatus
+ } = queryParams;
+
+ // 杩囨护鏁版嵁
+ let filteredData = mockOrganUtilizationData.filter(item => {
+ let match = true;
+
+ if (hospitalNo && !item.hospitalNo.includes(hospitalNo)) {
+ match = false;
+ }
+
+ if (donorName && !item.donorName.includes(donorName)) {
+ match = false;
+ }
+
+ if (utilizationStatus && item.utilizationStatus !== utilizationStatus) {
+ match = false;
+ }
+
+ return match;
+ });
+
+ // 鍒嗛〉
+ const startIndex = (pageNum - 1) * pageSize;
+ const endIndex = startIndex + parseInt(pageSize);
+ const paginatedData = filteredData.slice(startIndex, endIndex);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ rows: paginatedData,
+ total: filteredData.length,
+ pageNum: parseInt(pageNum),
+ pageSize: parseInt(pageSize)
+ }
+ };
+};
+
+// 鑾峰彇鍣ㄥ畼鍒╃敤璇︾粏淇℃伅
+export const getOrganUtilizationDetail = async (id) => {
+ await delay();
+
+ const detail = mockOrganUtilizationData.find(item => item.id == id);
+
+ if (detail) {
+ // 鑾峰彇鍒╃敤璁板綍
+ const utilizationRecords = mockUtilizationRecordData.filter(item => item.utilizationId == id);
+ // 鑾峰彇闅忚璁板綍
+ const followupRecords = mockFollowupRecordData.filter(item => item.utilizationId == id);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ ...detail,
+ utilizationRecords,
+ followupRecords
+ }
+ };
+ } else {
+ return {
+ code: 404,
+ message: "鍣ㄥ畼鍒╃敤璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鏂板鍣ㄥ畼鍒╃敤
+export const addOrganUtilization = async (data) => {
+ await delay();
+
+ const newId = Math.max(...mockOrganUtilizationData.map(item => item.id), 0) + 1;
+ const hospitalNo = `D${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+ const caseNo = `C${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+
+ const newRecord = {
+ ...data,
+ id: newId,
+ hospitalNo,
+ caseNo,
+ registrationTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ createTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ mockOrganUtilizationData.unshift(newRecord);
+
+ return {
+ code: 200,
+ message: "鏂板鎴愬姛",
+ data: newRecord
+ };
+};
+
+// 淇敼鍣ㄥ畼鍒╃敤
+export const updateOrganUtilization = async (data) => {
+ await delay();
+
+ const index = mockOrganUtilizationData.findIndex(item => item.id == data.id);
+
+ if (index !== -1) {
+ mockOrganUtilizationData[index] = {
+ ...mockOrganUtilizationData[index],
+ ...data,
+ updateTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ return {
+ code: 200,
+ message: "淇敼鎴愬姛",
+ data: mockOrganUtilizationData[index]
+ };
+ } else {
+ return {
+ code: 404,
+ message: "鍣ㄥ畼鍒╃敤璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鍒犻櫎鍣ㄥ畼鍒╃敤
+export const delOrganUtilization = async (ids) => {
+ await delay();
+
+ const idArray = Array.isArray(ids) ? ids : [ids];
+
+ idArray.forEach(id => {
+ const index = mockOrganUtilizationData.findIndex(item => item.id == id);
+ if (index !== -1) {
+ mockOrganUtilizationData.splice(index, 1);
+ }
+ });
+
+ return {
+ code: 200,
+ message: "鍒犻櫎鎴愬姛"
+ };
+};
+
+// 淇濆瓨鍣ㄥ畼鍒╃敤璁板綍
+export const saveUtilizationRecords = async (utilizationId, records) => {
+ await delay();
+
+ // 鍒犻櫎璇ュ埄鐢↖D鐨勬墍鏈夎褰�
+ const existingIndexes = [];
+ mockUtilizationRecordData.forEach((item, index) => {
+ if (item.utilizationId == utilizationId) {
+ existingIndexes.push(index);
+ }
+ });
+
+ // 浠庡悗寰�鍓嶅垹闄ら伩鍏嶇储寮曢棶棰�
+ existingIndexes.reverse().forEach(index => {
+ mockUtilizationRecordData.splice(index, 1);
+ });
+
+ // 娣诲姞鏂拌褰�
+ records.forEach(record => {
+ const newId = Math.max(...mockUtilizationRecordData.map(item => item.id), 0) + 1;
+ mockUtilizationRecordData.push({
+ ...record,
+ id: newId,
+ utilizationId: utilizationId
+ });
+ });
+
+ return {
+ code: 200,
+ message: "淇濆瓨鎴愬姛",
+ data: records
+ };
+};
+
+// 淇濆瓨闅忚璁板綍
+export const saveFollowupRecord = async (record) => {
+ await delay();
+
+ const newId = Math.max(...mockFollowupRecordData.map(item => item.id), 0) + 1;
+ const newRecord = {
+ ...record,
+ id: newId
+ };
+
+ mockFollowupRecordData.push(newRecord);
+
+ return {
+ code: 200,
+ message: "闅忚璁板綍淇濆瓨鎴愬姛",
+ data: newRecord
+ };
+};
+
+// 鑾峰彇鍖婚櫌鍒楄〃
+export const getHospitalList = async () => {
+ await delay();
+
+ return {
+ code: 200,
+ message: "success",
+ data: mockHospitalData
+ };
+};
+
+// 鑾峰彇璐熻矗浜哄垪琛�
+export const getLeaderList = async () => {
+ await delay();
+
+ return {
+ code: 200,
+ message: "success",
+ data: mockLeaderData
+ };
+};
+
+// 鑾峰彇鍗忚皟鍛樺垪琛�
+export const getCoordinatorList = async () => {
+ await delay();
+
+ return {
+ code: 200,
+ message: "success",
+ data: mockCoordinatorData
+ };
+};
+
+export default {
+ listOrganUtilization,
+ getOrganUtilizationDetail,
+ addOrganUtilization,
+ updateOrganUtilization,
+ delOrganUtilization,
+ saveUtilizationRecords,
+ saveFollowupRecord,
+ getHospitalList,
+ getLeaderList,
+ getCoordinatorList
+};
diff --git a/src/views/business/affirm/mockConfirmationApi.js b/src/views/business/affirm/mockConfirmationApi.js
index d0d7626..e2057f1 100644
--- a/src/views/business/affirm/mockConfirmationApi.js
+++ b/src/views/business/affirm/mockConfirmationApi.js
@@ -116,7 +116,18 @@
}, 300);
});
}
-
+// 鏇存柊鎹愮尞纭淇℃伅
+export function updateDeathJudgment(data) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ const index = confirmationData.rows.findIndex(item => item.id === data.id);
+ if (index !== -1) {
+ confirmationData.rows[index] = { ...confirmationData.rows[index], ...data };
+ }
+ resolve({ code: 200, message: '鏇存柊鎴愬姛' });
+ }, 300);
+ });
+}
// 鍒犻櫎鎹愮尞纭
export function delConfirmation(ids) {
return new Promise((resolve) => {
diff --git a/src/views/business/allocation/allocationInfo.vue b/src/views/business/allocation/allocationInfo.vue
new file mode 100644
index 0000000..fcda788
--- /dev/null
+++ b/src/views/business/allocation/allocationInfo.vue
@@ -0,0 +1,1014 @@
+<template>
+ <div class="organ-allocation-detail">
+ <!-- 鍩烘湰淇℃伅閮ㄥ垎 -->
+ <el-card class="detail-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍣ㄥ畼鍒嗛厤鍩烘湰淇℃伅</span>
+ <div style="float: right;">
+ <el-button type="primary" @click="handleSave" :loading="saveLoading">
+ 淇濆瓨
+ </el-button>
+ <el-button
+ type="success"
+ @click="handleAllocate"
+ :disabled="form.allocationStatus === 'allocated'"
+ >
+ 纭鍒嗛厤
+ </el-button>
+ </div>
+ </div>
+
+ <el-form :model="form" ref="form" :rules="rules" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input v-model="form.hospitalNo" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="妗堜緥缂栧彿" prop="caseNo">
+ <el-input v-model="form.caseNo" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎹愮尞鑰呭鍚�" prop="donorName">
+ <el-input v-model="form.donorName" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="鎬у埆" prop="gender">
+ <el-select v-model="form.gender" style="width: 100%">
+ <el-option label="鐢�" value="0" />
+ <el-option label="濂�" value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="骞撮緞" prop="age">
+ <el-input v-model="form.age" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鍑虹敓鏃ユ湡" prop="birthDate">
+ <el-date-picker
+ v-model="form.birthDate"
+ type="date"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐤剧梾璇婃柇" prop="diagnosis">
+ <el-input
+ type="textarea"
+ :rows="2"
+ v-model="form.diagnosis"
+ placeholder="璇疯緭鍏ョ柧鐥呰瘖鏂俊鎭�"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍒嗛厤鏃堕棿" prop="allocationTime">
+ <el-date-picker
+ v-model="form.allocationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ :disabled="form.allocationStatus !== 'allocated'"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐧昏浜�" prop="registrant">
+ <el-input v-model="form.registrant" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐧昏鏃堕棿" prop="registrationTime">
+ <el-date-picker
+ v-model="form.registrationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ readonly
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </el-card>
+
+ <!-- 鍣ㄥ畼鍒嗛厤璁板綍閮ㄥ垎 -->
+ <el-card class="allocation-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍣ㄥ畼鍒嗛厤璁板綍</span>
+ <div style="float: right;">
+ <el-tag
+ :type="
+ form.allocationStatus === 'allocated' ? 'success' : 'warning'
+ "
+ >
+ {{ form.allocationStatus === "allocated" ? "宸插垎閰�" : "寰呭垎閰�" }}
+ </el-tag>
+ </div>
+ </div>
+
+ <el-form
+ ref="allocationForm"
+ :rules="allocationRules"
+ :model="allocationData"
+ label-position="right"
+ >
+ <el-row>
+ <el-col>
+ <el-form-item label-width="100px" label="鍒嗛厤鍣ㄥ畼">
+ <el-checkbox-group
+ v-model="selectedOrgans"
+ @change="handleOrganSelectionChange"
+ >
+ <el-checkbox
+ v-for="organ in organDict"
+ :key="organ.value"
+ :label="organ.value"
+ :disabled="form.allocationStatus === 'allocated'"
+ >
+ {{ organ.label }}
+ </el-checkbox>
+ </el-checkbox-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row>
+ <el-col>
+ <el-form-item>
+ <el-table
+ :data="allocationData.records"
+ v-loading="loading"
+ border
+ style="width: 100%"
+ :row-class-name="getOrganRowClassName"
+ >
+ <el-table-column
+ label="鍣ㄥ畼鍚嶇О"
+ align="center"
+ width="120"
+ prop="organName"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.organName"
+ placeholder="鍣ㄥ畼鍚嶇О"
+ :disabled="true"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鍒嗛厤绯荤粺缂栧彿"
+ align="center"
+ width="150"
+ prop="systemNo"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.systemNo"
+ placeholder="鍒嗛厤绯荤粺缂栧彿"
+ :disabled="form.allocationStatus === 'allocated'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鍒嗛厤鎺ユ敹鏃堕棿"
+ align="center"
+ width="180"
+ prop="applicantTime"
+ >
+ <template slot-scope="scope">
+ <el-date-picker
+ clearable
+ size="small"
+ style="width: 100%"
+ v-model="scope.row.applicantTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ placeholder="閫夋嫨鍒嗛厤鎺ユ敹鏃堕棿"
+ :disabled="form.allocationStatus === 'allocated'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鍙椾綋濮撴皬"
+ align="center"
+ width="120"
+ prop="recipientName"
+ >
+ <template slot-scope="scope">
+ <el-input
+ v-model="scope.row.recipientName"
+ placeholder="鍙椾綋濮撴皬"
+ :disabled="form.allocationStatus === 'allocated'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="绉绘鍖婚櫌"
+ align="center"
+ width="200"
+ prop="transplantHospitalNo"
+ >
+ <template slot-scope="scope">
+ <el-select
+ v-model="scope.row.transplantHospitalNo"
+ placeholder="璇烽�夋嫨绉绘鍖婚櫌"
+ style="width: 100%"
+ :disabled="form.allocationStatus === 'allocated'"
+ @change="handleHospitalChange(scope.row, $event)"
+ >
+ <el-option
+ v-for="hospital in hospitalList"
+ :key="hospital.hospitalNo"
+ :label="hospital.hospitalName"
+ :value="hospital.hospitalNo"
+ />
+ </el-select>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="璇存槑"
+ align="center"
+ prop="reallocationReason"
+ min-width="200"
+ >
+ <template slot-scope="scope">
+ <el-input
+ type="textarea"
+ clearable
+ v-model="scope.row.reallocationReason"
+ placeholder="璇疯緭鍏ヨ鏄�"
+ :disabled="form.allocationStatus === 'allocated'"
+ />
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="120"
+ class-name="small-padding fixed-width"
+ v-if="form.allocationStatus !== 'allocated'"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-copy-document"
+ @click="handleRedistribution(scope.row)"
+ :disabled="!scope.row.systemNo"
+ >
+ 閲嶅垎閰�
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <!-- 鍒嗛厤缁熻淇℃伅 -->
+ <div class="allocation-stats" v-if="allocationData.records.length > 0">
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">宸插垎閰嶅櫒瀹�:</span>
+ <span class="stat-value"
+ >{{ allocationData.records.length }} 涓�</span
+ >
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">寰呭畬鍠勪俊鎭�:</span>
+ <span class="stat-value">{{ incompleteRecords }} 涓�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">娑夊強鍖婚櫌:</span>
+ <span class="stat-value">{{ uniqueHospitals }} 瀹�</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">鍒嗛厤鐘舵��:</span>
+ <span class="stat-value">
+ <el-tag
+ :type="
+ form.allocationStatus === 'allocated'
+ ? 'success'
+ : 'warning'
+ "
+ >
+ {{
+ form.allocationStatus === "allocated"
+ ? "宸插畬鎴�"
+ : "杩涜涓�"
+ }}
+ </el-tag>
+ </span>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+
+ <div v-else class="empty-allocation">
+ <el-empty description="鏆傛棤鍒嗛厤璁板綍" :image-size="80">
+ <span>璇峰厛閫夋嫨瑕佸垎閰嶇殑鍣ㄥ畼</span>
+ </el-empty>
+ </div>
+ </el-form>
+
+ <div class="dialog-footer" v-if="form.allocationStatus !== 'allocated'">
+ <el-button
+ type="primary"
+ @click="handleSaveAllocation"
+ :loading="saveLoading"
+ :disabled="allocationData.records.length === 0"
+ >
+ 淇濆瓨鍒嗛厤璁板綍
+ </el-button>
+ <el-button
+ type="success"
+ @click="handleConfirmAllocation"
+ :loading="confirmLoading"
+ :disabled="incompleteRecords > 0"
+ >
+ 纭瀹屾垚鍒嗛厤
+ </el-button>
+ </div>
+ </el-card>
+
+ <!-- 闄勪欢绠$悊閮ㄥ垎 -->
+ <el-card class="attachment-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鐩稿叧闄勪欢</span>
+ <upload-attachment
+ :file-list="attachments"
+ @change="handleAttachmentChange"
+ :limit="10"
+ :accept="'.pdf,.jpg,.jpeg,.png,.doc,.docx'"
+ />
+ </div>
+
+ <div class="attachment-list">
+ <el-table :data="attachments" style="width: 100%">
+ <el-table-column label="鏂囦欢鍚嶇О" min-width="200">
+ <template slot-scope="scope">
+ <div class="file-info">
+ <i
+ :class="getFileIcon(scope.row.fileName)"
+ style="margin-right: 8px; color: #409EFF;"
+ ></i>
+ <span>{{ scope.row.fileName }}</span>
+ </div>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢绫诲瀷" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag size="small">{{
+ getFileType(scope.row.fileName)
+ }}</el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢澶у皬" width="100" align="center">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓婁紶鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.uploadTime) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鎿嶄綔" width="150" align="center">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handlePreviewAttachment(scope.row)"
+ >棰勮</el-button
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-download"
+ @click="handleDownloadAttachment(scope.row)"
+ >涓嬭浇</el-button
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C;"
+ @click="handleRemoveAttachment(scope.row)"
+ >鍒犻櫎</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </el-card>
+ <!-- 闄勪欢棰勮瀵硅瘽妗� -->
+ <attachment-preview
+ :visible="attachmentPreviewVisible"
+ :file-list="currentAttachmentList"
+ :title="attachmentPreviewTitle"
+ @close="attachmentPreviewVisible = false"
+ />
+ <!-- 閲嶅垎閰嶅璇濇 -->
+ <el-dialog
+ title="鍣ㄥ畼閲嶅垎閰�"
+ :visible.sync="redistributionDialogVisible"
+ width="500px"
+ >
+ <el-form :model="redistributionForm" label-width="100px">
+ <el-form-item label="鍘熷櫒瀹樹俊鎭�">
+ <el-input v-model="redistributionForm.organName" readonly />
+ </el-form-item>
+ <el-form-item label="閲嶅垎閰嶅師鍥�" prop="reason">
+ <el-input
+ type="textarea"
+ :rows="4"
+ v-model="redistributionForm.reason"
+ placeholder="璇疯緭鍏ラ噸鍒嗛厤鍘熷洜"
+ />
+ </el-form-item>
+ </el-form>
+ <div slot="footer">
+ <el-button @click="redistributionDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleRedistributionConfirm"
+ >纭閲嶅垎閰�</el-button
+ >
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import {
+ getOrganAllocationDetail,
+ updateOrganAllocation,
+ saveAllocationRecords,
+ getHospitalList,
+ getOrganDict
+} from "./organAllocation";
+import UploadAttachment from "@/components/UploadAttachment";
+import AttachmentPreview from "@/components/AttachmentPreview";
+export default {
+ name: "OrganAllocationDetail",
+ components: {
+ UploadAttachment,
+ AttachmentPreview,
+ },
+ data() {
+ return {
+ // 琛ㄥ崟鏁版嵁
+ form: {
+ id: undefined,
+ hospitalNo: "",
+ caseNo: "",
+ donorName: "",
+ gender: "",
+ age: "",
+ birthDate: "",
+ diagnosis: "",
+ allocationStatus: "pending",
+ allocationTime: "",
+ registrant: "",
+ registrationTime: ""
+ },
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ rules: {
+ donorName: [
+ { required: true, message: "鎹愮尞鑰呭鍚嶄笉鑳戒负绌�", trigger: "blur" }
+ ],
+ diagnosis: [
+ { required: true, message: "鐤剧梾璇婃柇涓嶈兘涓虹┖", trigger: "blur" }
+ ]
+ },
+ // 鍒嗛厤璁板綍楠岃瘉瑙勫垯
+ allocationRules: {},
+ // 淇濆瓨鍔犺浇鐘舵��
+ saveLoading: false,
+ confirmLoading: false,
+ // 鍔犺浇鐘舵��
+ loading: false,
+ // 閫変腑鐨勫櫒瀹�
+ selectedOrgans: [],
+ // 鍣ㄥ畼瀛楀吀
+ organDict: [],
+ // 鍖婚櫌鍒楄〃
+ hospitalList: [],
+ // 鍒嗛厤璁板綍鏁版嵁
+ allocationData: {
+ records: []
+ },
+ // 闄勪欢鏁版嵁
+ attachments: [],
+ // 閲嶅垎閰嶅璇濇
+ redistributionDialogVisible: false,
+ redistributionForm: {
+ organName: "",
+ reason: ""
+ },
+ currentRedistributeRecord: null
+ };
+ },
+ computed: {
+ // 褰撳墠鐢ㄦ埛淇℃伅
+ currentUser() {
+ return JSON.parse(sessionStorage.getItem("user") || "{}");
+ },
+ // 涓嶅畬鏁寸殑璁板綍鏁伴噺
+ incompleteRecords() {
+ return this.allocationData.records.filter(
+ record =>
+ !record.systemNo ||
+ !record.applicantTime ||
+ !record.recipientName ||
+ !record.transplantHospitalNo
+ ).length;
+ },
+ // 鍞竴鍖婚櫌鏁伴噺
+ uniqueHospitals() {
+ const hospitals = this.allocationData.records
+ .map(record => record.transplantHospitalNo)
+ .filter(Boolean);
+ return new Set(hospitals).size;
+ }
+ },
+ created() {
+ const id = this.$route.query.id;
+ if (id) {
+ this.getDetail(id);
+ } else {
+ this.generateCaseNo();
+ this.form.registrant = this.currentUser.username || "褰撳墠鐢ㄦ埛";
+ this.form.registrationTime = new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19);
+ }
+ this.getOrganDictionary();
+ this.getHospitalData();
+ },
+ methods: {
+ // 鐢熸垚妗堜緥缂栧彿
+ generateCaseNo() {
+ const timestamp = Date.now().toString();
+ this.form.hospitalNo = "D" + timestamp.slice(-6);
+ this.form.caseNo = "C" + timestamp.slice(-6);
+ },
+ // 鑾峰彇璇︽儏
+ getDetail(id) {
+ this.loading = true;
+ getOrganAllocationDetail(id)
+ .then(response => {
+ if (response.code === 200) {
+ this.form = response.data;
+ if (response.data.allocationRecords) {
+ this.allocationData.records = response.data.allocationRecords;
+ this.selectedOrgans = response.data.allocationRecords.map(
+ item => item.organNo
+ );
+ }
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇鍣ㄥ畼鍒嗛厤璇︽儏澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇璇︽儏澶辫触");
+ });
+ },
+ // 鑾峰彇鍣ㄥ畼瀛楀吀
+ getOrganDictionary() {
+ getOrganDict().then(response => {
+ if (response.code === 200) {
+ this.organDict = response.data;
+ }
+ });
+ },
+ // 鑾峰彇鍖婚櫌鏁版嵁
+ getHospitalData() {
+ getHospitalList().then(response => {
+ if (response.code === 200) {
+ this.hospitalList = response.data;
+ }
+ });
+ },
+ handleAttachmentChange(fileList) {
+console.log(fileList,'娴嬭瘯');
+
+ },
+ // 鍣ㄥ畼閫夋嫨鐘舵�佸彉鍖�
+ handleOrganSelectionChange(selectedValues) {
+ const currentOrganNos = this.allocationData.records.map(
+ item => item.organNo
+ );
+
+ // 鏂板閫夋嫨鐨勫櫒瀹�
+ selectedValues.forEach(organValue => {
+ if (!currentOrganNos.includes(organValue)) {
+ const organInfo = this.organDict.find(
+ item => item.value === organValue
+ );
+ if (organInfo) {
+ this.allocationData.records.push({
+ organName: organInfo.label,
+ organNo: organValue,
+ id: null,
+ allocationId: this.form.id,
+ systemNo: "",
+ applicantTime: "",
+ recipientName: "",
+ transplantHospitalNo: "",
+ transplantHospitalName: "",
+ reallocationReason: "",
+ organState: 1
+ });
+ }
+ }
+ });
+
+ // 绉婚櫎鍙栨秷閫夋嫨鐨勫櫒瀹�
+ this.allocationData.records = this.allocationData.records.filter(
+ record => {
+ if (selectedValues.includes(record.organNo)) {
+ return true;
+ } else {
+ if (record.id) {
+ this.$confirm(
+ "鍒犻櫎鍣ㄥ畼鍒嗛厤鏁版嵁鍚庡皢鏃犳硶鎭㈠锛屾偍纭鍒犻櫎璇ユ潯璁板綍鍚楋紵",
+ "鎻愮ず",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ )
+ .then(() => {
+ // 瀹為檯椤圭洰涓繖閲屽簲璇ヨ皟鐢ㄥ垹闄PI
+ this.allocationData.records = this.allocationData.records.filter(
+ r => r.organNo !== record.organNo
+ );
+ this.$message.success("鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {
+ this.selectedOrgans.push(record.organNo);
+ });
+ return true; // 绛夊緟鐢ㄦ埛纭
+ } else {
+ return false; // 鐩存帴鍒犻櫎鏂拌褰�
+ }
+ }
+ }
+ );
+ },
+ // 鍖婚櫌閫夋嫨鍙樺寲
+ handleHospitalChange(row, hospitalNo) {
+ const hospital = this.hospitalList.find(
+ item => item.hospitalNo === hospitalNo
+ );
+ if (hospital) {
+ row.transplantHospitalName = hospital.hospitalName;
+ }
+ },
+ // 閲嶅垎閰嶆搷浣�
+ handleRedistribution(row) {
+ this.currentRedistributeRecord = row;
+ this.redistributionForm.organName = row.organName;
+ this.redistributionForm.reason = row.reallocationReason || "";
+ this.redistributionDialogVisible = true;
+ },
+ // 纭閲嶅垎閰�
+ handleRedistributionConfirm() {
+ if (!this.redistributionForm.reason) {
+ this.$message.warning("璇疯緭鍏ラ噸鍒嗛厤鍘熷洜");
+ return;
+ }
+
+ if (this.currentRedistributeRecord) {
+ this.currentRedistributeRecord.reallocationReason = this.redistributionForm.reason;
+ this.$message.success("閲嶅垎閰嶅師鍥犲凡鏇存柊");
+ this.redistributionDialogVisible = false;
+ }
+ },
+ // 鍣ㄥ畼琛屾牱寮�
+ getOrganRowClassName({ row }) {
+ if (
+ !row.systemNo ||
+ !row.applicantTime ||
+ !row.recipientName ||
+ !row.transplantHospitalNo
+ ) {
+ return "warning-row";
+ }
+ return "";
+ },
+ // 淇濆瓨鍩烘湰淇℃伅
+ handleSave() {
+ this.$refs.form.validate(valid => {
+ if (valid) {
+ this.saveLoading = true;
+ const apiMethod = this.form.id
+ ? updateOrganAllocation
+ : addOrganAllocation;
+
+ apiMethod(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("淇濆瓨鎴愬姛");
+ if (!this.form.id) {
+ this.form.id = response.data.id;
+ this.$router.replace({
+ query: { ...this.$route.query, id: this.form.id }
+ });
+ }
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨澶辫触:", error);
+ this.$message.error("淇濆瓨澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ }
+ });
+ },
+ // 淇濆瓨鍒嗛厤璁板綍
+ handleSaveAllocation() {
+ if (!this.form.id) {
+ this.$message.warning("璇峰厛淇濆瓨鍩烘湰淇℃伅");
+ return;
+ }
+
+ this.saveLoading = true;
+ saveAllocationRecords(this.form.id, this.allocationData.records)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒嗛厤璁板綍淇濆瓨鎴愬姛");
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨鍒嗛厤璁板綍澶辫触:", error);
+ this.$message.error("淇濆瓨鍒嗛厤璁板綍澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ },
+ // 纭瀹屾垚鍒嗛厤
+ handleConfirmAllocation() {
+ if (this.incompleteRecords > 0) {
+ this.$message.warning("璇峰厛瀹屽杽鎵�鏈夊垎閰嶈褰曠殑淇℃伅");
+ return;
+ }
+
+ this.$confirm("纭瀹屾垚鍣ㄥ畼鍒嗛厤鍚楋紵瀹屾垚鍚庡皢鏃犳硶淇敼鍒嗛厤淇℃伅銆�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.confirmLoading = true;
+ this.form.allocationStatus = "allocated";
+ this.form.allocationTime = new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19);
+
+ updateOrganAllocation(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍣ㄥ畼鍒嗛厤宸插畬鎴�");
+ }
+ })
+ .catch(error => {
+ console.error("纭鍒嗛厤澶辫触:", error);
+ this.$message.error("纭鍒嗛厤澶辫触");
+ })
+ .finally(() => {
+ this.confirmLoading = false;
+ });
+ })
+ .catch(() => {});
+ },
+ // 涓婁紶闄勪欢
+ handleUploadAttachment() {
+ // 闄勪欢涓婁紶閫昏緫
+ this.$message.info("闄勪欢涓婁紶鍔熻兘");
+ },
+ // 棰勮闄勪欢
+ handlePreviewAttachment(attachment) {
+ // 闄勪欢棰勮閫昏緫
+ this.$message.info("闄勪欢棰勮鍔熻兘");
+ },
+ // 涓嬭浇闄勪欢
+ handleDownloadAttachment(attachment) {
+ // 闄勪欢涓嬭浇閫昏緫
+ this.$message.info("闄勪欢涓嬭浇鍔熻兘");
+ },
+ // 鍒犻櫎闄勪欢
+ handleRemoveAttachment(attachment) {
+ this.$confirm("纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.$message.success("闄勪欢鍒犻櫎鎴愬姛");
+ })
+ .catch(() => {});
+ },
+ // 鑾峰彇鏂囦欢鍥炬爣
+ getFileIcon(fileName) {
+ const ext = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const iconMap = {
+ pdf: "el-icon-document",
+ doc: "el-icon-document",
+ docx: "el-icon-document",
+ xls: "el-icon-document",
+ xlsx: "el-icon-document",
+ jpg: "el-icon-picture",
+ jpeg: "el-icon-picture",
+ png: "el-icon-picture"
+ };
+ return iconMap[ext] || "el-icon-document";
+ },
+ // 鑾峰彇鏂囦欢绫诲瀷
+ getFileType(fileName) {
+ const ext = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const typeMap = {
+ pdf: "PDF",
+ doc: "DOC",
+ docx: "DOCX",
+ xls: "XLS",
+ xlsx: "XLSX",
+ jpg: "JPG",
+ jpeg: "JPEG",
+ png: "PNG"
+ };
+ return typeMap[ext] || ext.toUpperCase();
+ },
+ // 鏂囦欢澶у皬鏍煎紡鍖�
+ formatFileSize(size) {
+ if (size === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(size) / Math.log(k));
+ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time) {
+ if (!time) return "";
+ const date = new Date(time);
+ return `${date.getFullYear()}-${(date.getMonth() + 1)
+ .toString()
+ .padStart(2, "0")}-${date
+ .getDate()
+ .toString()
+ .padStart(2, "0")} ${date
+ .getHours()
+ .toString()
+ .padStart(2, "0")}:${date
+ .getMinutes()
+ .toString()
+ .padStart(2, "0")}`;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.organ-allocation-detail {
+ padding: 20px;
+ background-color: #f5f7fa;
+}
+
+.detail-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.allocation-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.attachment-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.detail-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #303133;
+}
+
+/* 缁熻淇℃伅鏍峰紡 */
+.allocation-stats {
+ margin-top: 20px;
+ padding: 15px;
+ background: linear-gradient(135deg, #9eb7e5 0%, #53519c 100%);
+ border-radius: 8px;
+ color: white;
+ font-size: 18px;
+}
+
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 10px;
+}
+
+.stat-label {
+ /* font-size: 12px; */
+ opacity: 0.9;
+
+ /* color: #606266; */
+ margin-bottom: 5px;
+}
+
+.stat-value {
+ font-size: 20px;
+ font-weight: bold;
+ /* color: #303133; */
+}
+
+/* 绌虹姸鎬佹牱寮� */
+.empty-allocation {
+ text-align: center;
+ padding: 40px 0;
+ color: #909399;
+}
+
+/* 瀵硅瘽妗嗗簳閮ㄦ寜閽� */
+.dialog-footer {
+ margin-top: 20px;
+ text-align: center;
+ padding-top: 20px;
+ border-top: 1px solid #e4e7ed;
+}
+
+/* 琛ㄦ牸琛屾牱寮� */
+:deep(.warning-row) {
+ background-color: #fff7e6;
+}
+
+:deep(.warning-row:hover) {
+ background-color: #ffecc2;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .organ-allocation-detail {
+ padding: 10px;
+ }
+
+ .allocation-stats .el-col {
+ margin-bottom: 10px;
+ }
+}
+</style>
diff --git a/src/views/business/allocation/index.vue b/src/views/business/allocation/index.vue
new file mode 100644
index 0000000..72a80e7
--- /dev/null
+++ b/src/views/business/allocation/index.vue
@@ -0,0 +1,372 @@
+<template>
+ <div class="organ-allocation-list">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-card class="search-card">
+ <el-form
+ :model="queryParams"
+ ref="queryForm"
+ :inline="true"
+ label-width="100px"
+ >
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input
+ v-model="queryParams.hospitalNo"
+ 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="allocationStatus">
+ <el-select
+ v-model="queryParams.allocationStatus"
+ placeholder="璇烽�夋嫨鍒嗛厤鐘舵��"
+ clearable
+ style="width: 200px"
+ >
+ <el-option label="宸插垎閰�" value="allocated" />
+ <el-option label="寰呭垎閰�" value="pending" />
+ </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-card class="tool-card">
+ <el-row :gutter="10">
+ <el-col :span="16">
+ <el-button type="primary" icon="el-icon-plus" @click="handleCreate"
+ >鏂板缓鍒嗛厤</el-button
+ >
+ <el-button
+ type="success"
+ icon="el-icon-edit"
+ :disabled="single"
+ @click="handleUpdate"
+ >淇敼</el-button
+ >
+ <el-button
+ type="danger"
+ icon="el-icon-delete"
+ :disabled="multiple"
+ @click="handleDelete"
+ >鍒犻櫎</el-button
+ >
+ </el-col>
+ <el-col :span="8" style="text-align: right">
+ <el-tooltip content="鍒锋柊" placement="top">
+ <el-button icon="el-icon-refresh" circle @click="getList" />
+ </el-tooltip>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-card>
+ <el-table
+ v-loading="loading"
+ :data="organAllocationList"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column
+ label="浣忛櫌鍙�"
+ align="center"
+ prop="hospitalNo"
+ width="120"
+ />
+ <el-table-column
+ label="鎹愮尞鑰呭鍚�"
+ align="center"
+ prop="donorName"
+ width="120"
+ />
+ <el-table-column label="鎬у埆" align="center" prop="gender" width="80">
+ <template slot-scope="scope">
+ <dict-tag
+ :options="dict.type.sys_user_sex"
+ :value="parseInt(scope.row.gender)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="骞撮緞" align="center" prop="age" width="80" />
+ <el-table-column
+ label="鐤剧梾璇婃柇"
+ align="center"
+ prop="diagnosis"
+ min-width="180"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="鍒嗛厤鐘舵��"
+ align="center"
+ prop="allocationStatus"
+ width="100"
+ >
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.allocationStatus === 'allocated' ? 'success' : 'warning'">
+ {{ scope.row.allocationStatus === 'allocated' ? '宸插垎閰�' : '寰呭垎閰�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鍒嗛厤鏃堕棿"
+ align="center"
+ prop="allocationTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.allocationTime
+ ? parseTime(scope.row.allocationTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鐧昏浜�"
+ align="center"
+ prop="registrant"
+ width="100"
+ />
+ <el-table-column
+ label="鐧昏鏃堕棿"
+ align="center"
+ prop="registrationTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.registrationTime
+ ? parseTime(scope.row.registrationTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="150"
+ class-name="small-padding fixed-width"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleView(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-delete"
+ style="color: #F56C6C"
+ @click="handleDelete(scope.row)"
+ >鍒犻櫎</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-card>
+ </div>
+</template>
+
+<script>
+import { listOrganAllocation, delOrganAllocation } from "./organAllocation";
+import Pagination from "@/components/Pagination";
+
+export default {
+ name: "OrganAllocationList",
+ components: { Pagination },
+ dicts: ["sys_user_sex"],
+ data() {
+ return {
+ // 閬僵灞�
+ loading: true,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 鍣ㄥ畼鍒嗛厤琛ㄦ牸鏁版嵁
+ organAllocationList: [],
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ hospitalNo: undefined,
+ donorName: undefined,
+ allocationStatus: undefined
+ }
+ };
+ },
+ created() {
+ this.getList();
+ },
+ methods: {
+ // 鏌ヨ鍣ㄥ畼鍒嗛厤鍒楄〃
+ getList() {
+ this.loading = true;
+ listOrganAllocation(this.queryParams)
+ .then(response => {
+ if (response.code === 200) {
+ this.organAllocationList = response.data.rows;
+ this.total = response.data.total;
+ } else {
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇鍣ㄥ畼鍒嗛厤鍒楄〃澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ });
+ },
+ // 鎼滅储鎸夐挳鎿嶄綔
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ // 閲嶇疆鎸夐挳鎿嶄綔
+ resetQuery() {
+ this.$refs.queryForm.resetFields();
+ this.handleQuery();
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.id);
+ this.single = selection.length !== 1;
+ this.multiple = !selection.length;
+ },
+ // 鏌ョ湅璇︽儏
+ handleView(row) {
+ this.$router.push({
+ path: "/case/allocationInfo",
+ query: { id: row.id }
+ });
+ },
+ // 鏂板鎸夐挳鎿嶄綔
+ handleCreate() {
+ this.$router.push("/case/allocationInfo");
+ },
+ // 淇敼鎸夐挳鎿嶄綔
+ handleUpdate(row) {
+ const id = row.id || this.ids[0];
+ this.$router.push({
+ path: "/case/allocationInfo",
+ query: { id: id }
+ });
+ },
+ // 鍒犻櫎鎸夐挳鎿嶄綔
+ handleDelete(row) {
+ const ids = row.id ? [row.id] : this.ids;
+ this.$confirm("鏄惁纭鍒犻櫎閫変腑鐨勬暟鎹」锛�", "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return delOrganAllocation(ids);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒犻櫎鎴愬姛");
+ this.getList();
+ }
+ })
+ .catch(() => {});
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time, pattern) {
+ if (!time) return "";
+ const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
+ let date;
+ if (typeof time === "object") {
+ date = time;
+ } else {
+ if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+ time = parseInt(time);
+ }
+ if (typeof time === "number" && time.toString().length === 10) {
+ time = time * 1000;
+ }
+ date = new Date(time);
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ };
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key];
+ if (key === "a") {
+ return ["鏃�", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�"][value];
+ }
+ if (result.length > 0 && value < 10) {
+ value = "0" + value;
+ }
+ return value || 0;
+ });
+ return time_str;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.organ-allocation-list {
+ padding: 20px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.tool-card {
+ margin-bottom: 20px;
+}
+
+.fixed-width .el-button {
+ margin: 0 5px;
+}
+</style>
diff --git a/src/views/business/allocation/organAllocation.js b/src/views/business/allocation/organAllocation.js
new file mode 100644
index 0000000..dd879c1
--- /dev/null
+++ b/src/views/business/allocation/organAllocation.js
@@ -0,0 +1,329 @@
+// 妯℃嫙鍣ㄥ畼鍒嗛厤鏁版嵁
+const mockOrganAllocationData = [
+ {
+ id: 1,
+ hospitalNo: "D202312001",
+ caseNo: "C202312001",
+ donorName: "寮犱笁",
+ gender: "0",
+ age: 45,
+ birthDate: "1978-05-15",
+ diagnosis: "鑴戝浼�",
+ allocationStatus: "allocated",
+ allocationTime: "2023-12-01 16:30:00",
+ registrant: "鏉庡崗璋冨憳",
+ registrationTime: "2023-12-01 15:00:00",
+ createTime: "2023-12-01 10:00:00"
+ },
+ {
+ id: 2,
+ hospitalNo: "D202312002",
+ caseNo: "C202312002",
+ donorName: "鏉庡洓",
+ gender: "1",
+ age: 38,
+ birthDate: "1985-08-22",
+ diagnosis: "蹇冭剰楠ゅ仠",
+ allocationStatus: "allocated",
+ allocationTime: "2023-12-02 11:20:00",
+ registrant: "寮犲崗璋冨憳",
+ registrationTime: "2023-12-02 10:00:00",
+ createTime: "2023-12-02 08:30:00"
+ },
+ {
+ id: 3,
+ hospitalNo: "D202312003",
+ caseNo: "C202312003",
+ donorName: "鐜嬩簲",
+ gender: "0",
+ age: 52,
+ birthDate: "1971-03-10",
+ diagnosis: "鑴戞姝�",
+ allocationStatus: "pending",
+ allocationTime: "",
+ registrant: "璧靛崗璋冨憳",
+ registrationTime: "2023-12-03 17:20:00",
+ createTime: "2023-12-03 14:00:00"
+ }
+];
+
+// 妯℃嫙鍣ㄥ畼鍒嗛厤璁板綍鏁版嵁
+const mockAllocationRecordData = [
+ {
+ id: 1,
+ allocationId: 1,
+ organName: "鑲濊剰",
+ organNo: "L001",
+ caseNo: "C202312001",
+ systemNo: "AL202312001",
+ applicantTime: "2023-12-01 17:00:00",
+ recipientName: "鐜�",
+ transplantHospitalNo: "H1001",
+ transplantHospitalName: "鍖椾含鍗忓拰鍖婚櫌",
+ reallocationReason: "",
+ organState: 1
+ },
+ {
+ id: 2,
+ allocationId: 1,
+ organName: "鑲捐剰",
+ organNo: "K001",
+ caseNo: "C202312001",
+ systemNo: "AL202312002",
+ applicantTime: "2023-12-01 17:30:00",
+ recipientName: "鏉�",
+ transplantHospitalNo: "H1002",
+ transplantHospitalName: "涓婃捣鐟為噾鍖婚櫌",
+ reallocationReason: "",
+ organState: 1
+ },
+ {
+ id: 3,
+ allocationId: 1,
+ organName: "蹇冭剰",
+ organNo: "H001",
+ caseNo: "C202312001",
+ systemNo: "AL202312003",
+ applicantTime: "2023-12-01 18:00:00",
+ recipientName: "寮�",
+ transplantHospitalNo: "H1003",
+ transplantHospitalName: "骞垮窞涓北鍖婚櫌",
+ reallocationReason: "",
+ organState: 1
+ }
+];
+
+// 妯℃嫙鍖婚櫌鏁版嵁
+const mockHospitalData = [
+ { id: 1, hospitalNo: "H1001", hospitalName: "鍖椾含鍗忓拰鍖婚櫌", type: "4" },
+ { id: 2, hospitalNo: "H1002", hospitalName: "涓婃捣鐟為噾鍖婚櫌", type: "4" },
+ { id: 3, hospitalNo: "H1003", hospitalName: "骞垮窞涓北鍖婚櫌", type: "4" },
+ { id: 4, hospitalNo: "H1004", hospitalName: "姝︽眽鍚屾祹鍖婚櫌", type: "4" },
+ { id: 5, hospitalNo: "H1005", hospitalName: "鎴愰兘鍗庤タ鍖婚櫌", type: "4" }
+];
+
+// 妯℃嫙鍣ㄥ畼绫诲瀷瀛楀吀
+const mockOrganDict = [
+ { value: "L001", label: "鑲濊剰" },
+ { value: "K001", label: "鑲捐剰" },
+ { value: "H001", label: "蹇冭剰" },
+ { value: "L002", label: "鑲鸿剰" },
+ { value: "P001", label: "鑳拌吅" },
+ { value: "I001", label: "灏忚偁" },
+ { value: "C001", label: "瑙掕啘" }
+];
+
+// 妯℃嫙API鍝嶅簲寤惰繜
+const delay = (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));
+
+// 鏌ヨ鍣ㄥ畼鍒嗛厤鍒楄〃
+export const listOrganAllocation = async (queryParams = {}) => {
+ await delay();
+
+ const {
+ pageNum = 1,
+ pageSize = 10,
+ hospitalNo,
+ donorName,
+ allocationStatus
+ } = queryParams;
+
+ // 杩囨护鏁版嵁
+ let filteredData = mockOrganAllocationData.filter(item => {
+ let match = true;
+
+ if (hospitalNo && !item.hospitalNo.includes(hospitalNo)) {
+ match = false;
+ }
+
+ if (donorName && !item.donorName.includes(donorName)) {
+ match = false;
+ }
+
+ if (allocationStatus && item.allocationStatus !== allocationStatus) {
+ match = false;
+ }
+
+ return match;
+ });
+
+ // 鍒嗛〉
+ const startIndex = (pageNum - 1) * pageSize;
+ const endIndex = startIndex + parseInt(pageSize);
+ const paginatedData = filteredData.slice(startIndex, endIndex);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ rows: paginatedData,
+ total: filteredData.length,
+ pageNum: parseInt(pageNum),
+ pageSize: parseInt(pageSize)
+ }
+ };
+};
+
+// 鑾峰彇鍣ㄥ畼鍒嗛厤璇︾粏淇℃伅
+export const getOrganAllocationDetail = async (id) => {
+ await delay();
+
+ const detail = mockOrganAllocationData.find(item => item.id == id);
+
+ if (detail) {
+ // 鑾峰彇鍒嗛厤璁板綍
+ const allocationRecords = mockAllocationRecordData.filter(item => item.allocationId == id);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ ...detail,
+ allocationRecords
+ }
+ };
+ } else {
+ return {
+ code: 404,
+ message: "鍣ㄥ畼鍒嗛厤璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鏂板鍣ㄥ畼鍒嗛厤
+export const addOrganAllocation = async (data) => {
+ await delay();
+
+ const newId = Math.max(...mockOrganAllocationData.map(item => item.id), 0) + 1;
+ const hospitalNo = `D${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+ const caseNo = `C${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+
+ const newRecord = {
+ ...data,
+ id: newId,
+ hospitalNo,
+ caseNo,
+ registrationTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ createTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ mockOrganAllocationData.unshift(newRecord);
+
+ return {
+ code: 200,
+ message: "鏂板鎴愬姛",
+ data: newRecord
+ };
+};
+
+// 淇敼鍣ㄥ畼鍒嗛厤
+export const updateOrganAllocation = async (data) => {
+ await delay();
+
+ const index = mockOrganAllocationData.findIndex(item => item.id == data.id);
+
+ if (index !== -1) {
+ mockOrganAllocationData[index] = {
+ ...mockOrganAllocationData[index],
+ ...data,
+ updateTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ return {
+ code: 200,
+ message: "淇敼鎴愬姛",
+ data: mockOrganAllocationData[index]
+ };
+ } else {
+ return {
+ code: 404,
+ message: "鍣ㄥ畼鍒嗛厤璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鍒犻櫎鍣ㄥ畼鍒嗛厤
+export const delOrganAllocation = async (ids) => {
+ await delay();
+
+ const idArray = Array.isArray(ids) ? ids : [ids];
+
+ idArray.forEach(id => {
+ const index = mockOrganAllocationData.findIndex(item => item.id == id);
+ if (index !== -1) {
+ mockOrganAllocationData.splice(index, 1);
+ }
+ });
+
+ return {
+ code: 200,
+ message: "鍒犻櫎鎴愬姛"
+ };
+};
+
+// 淇濆瓨鍣ㄥ畼鍒嗛厤璁板綍
+export const saveAllocationRecords = async (allocationId, records) => {
+ await delay();
+
+ // 鍒犻櫎璇ュ垎閰岻D鐨勬墍鏈夎褰�
+ const existingIndexes = [];
+ mockAllocationRecordData.forEach((item, index) => {
+ if (item.allocationId == allocationId) {
+ existingIndexes.push(index);
+ }
+ });
+
+ // 浠庡悗寰�鍓嶅垹闄ら伩鍏嶇储寮曢棶棰�
+ existingIndexes.reverse().forEach(index => {
+ mockAllocationRecordData.splice(index, 1);
+ });
+
+ // 娣诲姞鏂拌褰�
+ records.forEach(record => {
+ const newId = Math.max(...mockAllocationRecordData.map(item => item.id), 0) + 1;
+ mockAllocationRecordData.push({
+ ...record,
+ id: newId,
+ allocationId: allocationId
+ });
+ });
+
+ return {
+ code: 200,
+ message: "淇濆瓨鎴愬姛",
+ data: records
+ };
+};
+
+// 鑾峰彇鍖婚櫌鍒楄〃
+export const getHospitalList = async () => {
+ await delay();
+
+ return {
+ code: 200,
+ message: "success",
+ data: mockHospitalData
+ };
+};
+
+// 鑾峰彇鍣ㄥ畼瀛楀吀
+export const getOrganDict = async () => {
+ await delay();
+
+ return {
+ code: 200,
+ message: "success",
+ data: mockOrganDict
+ };
+};
+
+export default {
+ listOrganAllocation,
+ getOrganAllocationDetail,
+ addOrganAllocation,
+ updateOrganAllocation,
+ delOrganAllocation,
+ saveAllocationRecords,
+ getHospitalList,
+ getOrganDict
+};
diff --git a/src/views/business/appear/caseDetail.vue b/src/views/business/appear/caseDetail.vue
index 05826f7..fb7c54c 100644
--- a/src/views/business/appear/caseDetail.vue
+++ b/src/views/business/appear/caseDetail.vue
@@ -3,40 +3,76 @@
<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="鎹愮尞缂栧彿">{{
+ 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"/>
+ <dict-tag :options="genderOptions" :value="caseData.gender" />
</el-descriptions-item>
- <el-descriptions-item label="骞撮緞">{{ caseData.age }}宀�</el-descriptions-item>
+ <el-descriptions-item label="骞撮緞"
+ >{{ caseData.age }}宀�</el-descriptions-item
+ >
<el-descriptions-item label="琛�鍨�">
- <dict-tag :options="bloodTypeOptions" :value="caseData.bloodType"/>
+ <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-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-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-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>
@@ -99,16 +135,26 @@
</el-card>
</el-tab-pane>
- <el-tab-pane label="瀹℃壒淇℃伅" name="approval" v-if="caseData.status !== '0'">
+ <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-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>
@@ -252,24 +298,24 @@
filters: {
statusFilter(status) {
const statusMap = {
- '0': 'warning',
- '1': 'success',
- '2': 'danger'
+ "0": "warning",
+ "1": "success",
+ "2": "danger"
};
return statusMap[status];
},
statusTextFilter(status) {
const statusMap = {
- '0': '寰呭鎵�',
- '1': '宸查�氳繃',
- '2': '宸查┏鍥�'
+ "0": "寰呭鎵�",
+ "1": "宸查�氳繃",
+ "2": "宸查┏鍥�"
};
return statusMap[status];
}
},
data() {
return {
- activeTab: 'basic',
+ activeTab: "basic",
genderOptions: [
{ value: "0", label: "鐢�" },
{ value: "1", label: "濂�" }
@@ -333,12 +379,15 @@
},
methods: {
handleClose() {
- this.$emit('close');
+ this.$emit("close");
},
// 鑾峰彇鏂囦欢绫诲瀷
getFileType(fileName) {
- const extension = fileName.split('.').pop().toLowerCase();
+ const extension = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
const pdfTypes = ["pdf"];
diff --git a/src/views/business/course/components/BaseStage.vue b/src/views/business/course/components/BaseStage.vue
new file mode 100644
index 0000000..a0457c7
--- /dev/null
+++ b/src/views/business/course/components/BaseStage.vue
@@ -0,0 +1,51 @@
+<template>
+ <div class="base-stage">
+ <slot name="header"></slot>
+ <div class="stage-content">
+ <slot></slot>
+ </div>
+ <slot name="footer"></slot>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'BaseStage',
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ methods: {
+ // 鏍煎紡鍖栨椂闂�
+ formatTime(time) {
+ if (!time) return '-';
+ return this.$dayjs(time).format('YYYY-MM-DD HH:mm');
+ },
+ // 鑾峰彇鐘舵�佹爣绛剧被鍨�
+ getStatusTag(status) {
+ const map = {
+ 'completed': 'success',
+ 'in_progress': 'warning',
+ 'pending': 'info'
+ };
+ return map[status] || 'info';
+ }
+ }
+};
+</script>
+
+<style scoped>
+.base-stage {
+ padding: 0;
+}
+
+.stage-content {
+ min-height: 200px;
+}
+</style>
diff --git a/src/views/business/course/components/DeathJudgmentStage.vue b/src/views/business/course/components/DeathJudgmentStage.vue
new file mode 100644
index 0000000..f4d0e4b
--- /dev/null
+++ b/src/views/business/course/components/DeathJudgmentStage.vue
@@ -0,0 +1,206 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <template #header>
+ <el-alert
+ title="姝讳骸鍒ゅ畾闃舵"
+ :type="stageData.status === 'completed' ? 'success' : 'warning'"
+ :description="getAlertDescription()"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍒ゅ畾鍩烘湰淇℃伅</span>
+ </div>
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="鍒ゅ畾绫诲瀷">
+ <el-tag type="primary">鑴戞浜″垽瀹�</el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒ゅ畾鏃堕棿">
+ {{ formatTime(judgmentDetails.judgmentTime) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒ゅ畾鍖荤敓涓�">
+ {{ judgmentDetails.doctor1 }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒ゅ畾鍖荤敓浜�">
+ {{ judgmentDetails.doctor2 }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒ゅ畾缁撴灉">
+ <el-tag :type="judgmentDetails.result ? 'success' : 'warning'">
+ {{ judgmentDetails.result ? '鑴戞浜$‘璁�' : '鍒ゅ畾涓�' }}
+ </el-tag>
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+ </el-col>
+
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍒ゅ畾娴佺▼璁板綍</span>
+ </div>
+ <el-steps direction="vertical" :active="judgmentSteps.active" space="100px">
+ <el-step
+ v-for="step in judgmentSteps.steps"
+ :key="step.title"
+ :title="step.title"
+ :description="step.description"
+ :status="step.status"
+ />
+ </el-steps>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>鍒ゅ畾璇︾粏璁板綍</span>
+ </div>
+ <el-table :data="judgmentRecords" border>
+ <el-table-column label="妫�鏌ラ」鐩�" prop="item" width="180" />
+ <el-table-column label="妫�鏌ユ柟娉�" prop="method" width="150" />
+ <el-table-column label="妫�鏌ョ粨鏋�" prop="result" width="120">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.result === '绗﹀悎' ? 'success' : 'warning'">
+ {{ scope.row.result }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="妫�鏌ユ椂闂�" width="160">
+ <template slot-scope="scope">
+ {{ formatTime(scope.row.time) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="妫�鏌ュ尰鐢�" prop="doctor" width="120" />
+ <el-table-column label="澶囨敞" prop="remark" min-width="200" />
+ </el-table>
+ </el-card>
+
+ <template #footer>
+ <div class="action-buttons" style="margin-top: 20px; text-align: center;">
+ <el-button type="primary" @click="handleViewCertificate">
+ 鏌ョ湅姝讳骸璇佹槑
+ </el-button>
+ <el-button type="success" @click="handleConfirmJudgment">
+ 纭鍒ゅ畾缁撴灉
+ </el-button>
+ </div>
+ </template>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+
+export default {
+ name: 'DeathJudgmentStage',
+ components: { BaseStage },
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data() {
+ return {
+ judgmentDetails: {
+ judgmentTime: '2023-12-03 09:15:00',
+ doctor1: '寮犱富浠�',
+ doctor2: '鐜嬪尰鐢�',
+ result: true,
+ certificateNo: 'SW20231203001'
+ },
+ judgmentSteps: {
+ active: 4,
+ steps: [
+ {
+ title: '鍒濇涓村簥妫�鏌�',
+ description: '瀹屾垚鑷富鍛煎惛娴嬭瘯',
+ status: 'finish'
+ },
+ {
+ title: '鑴戝共鍙嶅皠娴嬭瘯',
+ description: '鍚勯」鍙嶅皠娴嬭瘯瀹屾垚',
+ status: 'finish'
+ },
+ {
+ title: '纭鎬ф鏌�',
+ description: '鑴戠數鍥炬鏌ュ畬鎴�',
+ status: 'finish'
+ },
+ {
+ title: '鏈�缁堝垽瀹�',
+ description: '涓や綅鍖荤敓鐙珛鍒ゅ畾',
+ status: 'finish'
+ }
+ ]
+ },
+ judgmentRecords: [
+ {
+ item: '鑷富鍛煎惛娴嬭瘯',
+ method: '鍛煎惛鏆傚仠璇曢獙',
+ result: '绗﹀悎',
+ time: '2023-12-03 08:30:00',
+ doctor: '寮犱富浠�',
+ remark: '鏃犺嚜涓诲懠鍚�'
+ },
+ {
+ item: '鐬冲瓟瀵瑰厜鍙嶅皠',
+ method: '鍏夊埡婵�娴嬭瘯',
+ result: '绗﹀悎',
+ time: '2023-12-03 08:45:00',
+ doctor: '鐜嬪尰鐢�',
+ remark: '鐬冲瓟鍥哄畾锛屽鍏夊弽灏勬秷澶�'
+ },
+ {
+ item: '鑴戝共鍚璇卞彂鐢典綅',
+ method: 'BAEP妫�鏌�',
+ result: '绗﹀悎',
+ time: '2023-12-03 09:00:00',
+ doctor: '鏉庡尰鐢�',
+ remark: '鑴戝共鍔熻兘涓уけ'
+ }
+ ]
+ };
+ },
+ methods: {
+ getAlertDescription() {
+ if (this.stageData.status === 'completed') {
+ return '鑴戞浜″垽瀹氬凡瀹屾垚锛岀鍚堝櫒瀹樻崘鐚潯浠�';
+ } else if (this.stageData.status === 'in_progress') {
+ return '姝讳骸鍒ゅ畾娴佺▼姝e湪杩涜涓�';
+ }
+ return '绛夊緟寮�濮嬫浜″垽瀹氭祦绋�';
+ },
+ handleViewCertificate() {
+ this.$message.info('鏌ョ湅姝讳骸璇佹槑鍔熻兘');
+ },
+ handleConfirmJudgment() {
+ this.$confirm('纭姝讳骸鍒ゅ畾缁撴灉鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.$message.success('姝讳骸鍒ゅ畾缁撴灉宸茬‘璁�');
+ });
+ }
+ }
+};
+</script>
+
+<style scoped>
+.action-buttons {
+ display: flex;
+ justify-content: center;
+ gap: 15px;
+ margin-top: 20px;
+}
+</style>
diff --git a/src/views/business/course/components/DonationConfirmStage.vue b/src/views/business/course/components/DonationConfirmStage.vue
new file mode 100644
index 0000000..d608417
--- /dev/null
+++ b/src/views/business/course/components/DonationConfirmStage.vue
@@ -0,0 +1,217 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <template #header>
+ <el-alert
+ :title="`鎹愮尞纭 - ${getStatusText()}`"
+ :type="getAlertType()"
+ :description="getAlertDescription()"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>纭淇℃伅姒傝</span>
+ </div>
+ <div class="confirm-overview">
+ <div class="overview-item">
+ <span class="label">纭鐘舵��:</span>
+ <el-tag :type="getStatusTag(confirmationDetails.status)">
+ {{ getStatusText(confirmationDetails.status) }}
+ </el-tag>
+ </div>
+ <div class="overview-item">
+ <span class="label">纭鏃堕棿:</span>
+ <span>{{ formatTime(confirmationDetails.confirmTime) }}</span>
+ </div>
+ <div class="overview-item">
+ <span class="label">纭鏂瑰紡:</span>
+ <span>{{ confirmationDetails.method }}</span>
+ </div>
+ <div class="overview-item">
+ <span class="label">鍗忚皟鍛�:</span>
+ <span>{{ confirmationDetails.coordinator }}</span>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>瀹跺睘鍚屾剰鎯呭喌</span>
+ </div>
+ <el-descriptions :column="1" size="small">
+ <el-descriptions-item label="涓昏瀹跺睘">
+ {{ familyConsent.mainRelative }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍚屾剰鐘舵��">
+ <el-tag :type="familyConsent.consented ? 'success' : 'warning'">
+ {{ familyConsent.consented ? '宸插悓鎰�' : '寰呯‘璁�' }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="绛惧瓧鏃堕棿">
+ {{ formatTime(familyConsent.signTime) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍏崇郴璇佹槑">
+ {{ familyConsent.relationshipProof }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>娉曞緥鏂囦欢</span>
+ </div>
+ <div class="document-list">
+ <div v-for="doc in legalDocuments" :key="doc.name" class="document-item">
+ <div class="doc-info">
+ <i :class="doc.icon" style="color: #409EFF; margin-right: 8px;"></i>
+ <span>{{ doc.name }}</span>
+ </div>
+ <el-tag :type="doc.status === 'completed' ? 'success' : 'warning'" size="small">
+ {{ doc.status === 'completed' ? '宸茬缃�' : '寰呯缃�' }}
+ </el-tag>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>鎹愮尞鎰忔効纭涔�</span>
+ </div>
+ <div class="consent-content">
+ <p>鏈汉/瀹跺睘纭锛屽湪鍏呭垎浜嗚В鍣ㄥ畼鎹愮尞鐨勭浉鍏充俊鎭悗锛岃嚜鎰垮悓鎰忚繘琛屽櫒瀹樻崘鐚��</p>
+ <el-divider />
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鎹愮尞鑰呭鍚�">{{ caseInfo.donorName }}</el-descriptions-item>
+ <el-descriptions-item label="鎹愮尞鑰呰韩浠借瘉鍙�">{{ confirmationDetails.idNumber }}</el-descriptions-item>
+ <el-descriptions-item label="鎹愮尞鍣ㄥ畼绫诲瀷">{{ confirmationDetails.organs }}</el-descriptions-item>
+ <el-descriptions-item label="鎹愮尞鐢ㄩ��">{{ confirmationDetails.purpose }}</el-descriptions-item>
+ <el-descriptions-item label="绛惧瓧浜�">{{ familyConsent.mainRelative }}</el-descriptions-item>
+ <el-descriptions-item label="绛惧瓧鏃ユ湡">{{ formatTime(familyConsent.signTime) }}</el-descriptions-item>
+ </el-descriptions>
+ </div>
+ </el-card>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+
+export default {
+ name: 'DonationConfirmStage',
+ components: { BaseStage },
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data() {
+ return {
+ confirmationDetails: {
+ status: 'completed',
+ confirmTime: '2023-12-03 11:00:00',
+ method: '瀹跺睘涔﹂潰鍚屾剰',
+ coordinator: '璧靛崗璋冨憳',
+ idNumber: '110101199001011234',
+ organs: '蹇冭剰銆佽倽鑴忋�佽偩鑴忋�佽鑶�',
+ purpose: '涓村簥绉绘'
+ },
+ familyConsent: {
+ mainRelative: '寮犱笁鐖朵翰',
+ consented: true,
+ signTime: '2023-12-03 10:45:00',
+ relationshipProof: '鎴峰彛鏈叧绯昏瘉鏄�'
+ },
+ legalDocuments: [
+ { name: '鍣ㄥ畼鎹愮尞鍚屾剰涔�', icon: 'el-icon-document', status: 'completed' },
+ { name: '瀹跺睘鍏崇郴璇佹槑', icon: 'el-icon-document', status: 'completed' },
+ { name: '鍖荤枟鍏嶈矗澹版槑', icon: 'el-icon-document', status: 'completed' },
+ { name: '闅愮淇濇姢鍗忚', icon: 'el-icon-document', status: 'completed' }
+ ]
+ };
+ },
+ methods: {
+ getStatusText() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '宸插畬鎴�' :
+ status === 'in_progress' ? '杩涜涓�' : '鏈紑濮�';
+ },
+ getAlertType() {
+ const status = this.stageData.status;
+ return status === 'completed' ? 'success' :
+ status === 'in_progress' ? 'warning' : 'info';
+ },
+ getAlertDescription() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '鎹愮尞纭娴佺▼宸插畬鎴愶紝鎵�鏈夋硶寰嬫枃浠跺凡绛剧讲' :
+ status === 'in_progress' ? '鎹愮尞纭娴佺▼姝e湪杩涜涓�' : '绛夊緟寮�濮嬫崘鐚‘璁ゆ祦绋�';
+ },
+ getStatusTag(status) {
+ const map = {
+ 'completed': 'success',
+ 'in_progress': 'warning',
+ 'pending': 'info'
+ };
+ return map[status] || 'info';
+ }
+ }
+};
+</script>
+
+<style scoped>
+.confirm-overview {
+ padding: 10px 0;
+}
+
+.overview-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.overview-item .label {
+ color: #606266;
+ font-weight: 500;
+}
+
+.document-list {
+ padding: 10px 0;
+}
+
+.document-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+ padding: 8px 12px;
+ border: 1px solid #e4e7ed;
+ border-radius: 4px;
+}
+
+.doc-info {
+ display: flex;
+ align-items: center;
+}
+
+.consent-content {
+ padding: 20px;
+ line-height: 1.6;
+}
+</style>
diff --git a/src/views/business/course/components/DonorMaintenanceStage.vue b/src/views/business/course/components/DonorMaintenanceStage.vue
new file mode 100644
index 0000000..a7e82a9
--- /dev/null
+++ b/src/views/business/course/components/DonorMaintenanceStage.vue
@@ -0,0 +1,156 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <template #header>
+ <el-alert
+ title="渚涜�呯淮鎶ら樁娈�"
+ type="success"
+ description="渚涜�呬俊鎭淮鎶ゅ凡瀹屾垚锛屾墍鏈夊熀鏈俊鎭凡纭鏃犺"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="12">
+ <el-card class="info-card">
+ <div slot="header" class="card-header">
+ <span>渚涜�呭熀鏈俊鎭�</span>
+ </div>
+ <el-descriptions :column="1" border size="small">
+ <el-descriptions-item label="浣忛櫌鍙�">
+ {{ caseInfo.hospitalNo }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎹愮尞鑰呭鍚�">
+ {{ caseInfo.donorName }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎬у埆">
+ <dict-tag :options="dict.type.sys_user_sex" :value="parseInt(caseInfo.gender)" />
+ </el-descriptions-item>
+ <el-descriptions-item label="骞撮緞">
+ {{ caseInfo.age }} 宀�
+ </el-descriptions-item>
+ <el-descriptions-item label="鐤剧梾璇婃柇">
+ {{ caseInfo.diagnosis }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+ </el-col>
+
+ <el-col :span="12">
+ <el-card class="timeline-card">
+ <div slot="header" class="card-header">
+ <span>缁存姢鏃堕棿绾�</span>
+ </div>
+ <el-timeline>
+ <el-timeline-item
+ v-for="event in maintenanceEvents"
+ :key="event.time"
+ :timestamp="formatTime(event.time)"
+ :type="event.type"
+ >
+ {{ event.content }}
+ </el-timeline-item>
+ </el-timeline>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>缁存姢璁板綍璇︽儏</span>
+ </div>
+ <el-table :data="maintenanceRecords" border>
+ <el-table-column label="缁存姢椤圭洰" prop="item" width="150" />
+ <el-table-column label="缁存姢鍐呭" prop="content" min-width="200" />
+ <el-table-column label="缁存姢浜�" prop="operator" width="120" />
+ <el-table-column label="缁存姢鏃堕棿" width="160">
+ <template slot-scope="scope">
+ {{ formatTime(scope.row.time) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" width="100">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.status === 'completed' ? 'success' : 'warning'">
+ {{ scope.row.status === 'completed' ? '宸插畬鎴�' : '杩涜涓�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+
+export default {
+ name: 'DonorMaintenanceStage',
+ components: { BaseStage },
+ dicts: ['sys_user_sex'],
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data() {
+ return {
+ maintenanceEvents: [
+ {
+ time: '2023-12-01 08:30:00',
+ content: '渚涜�呭熀鏈俊鎭綍鍏�',
+ type: 'primary'
+ },
+ {
+ time: '2023-12-01 09:15:00',
+ content: '鍖荤枟妗f寤虹珛',
+ type: 'success'
+ },
+ {
+ time: '2023-12-01 10:00:00',
+ content: '鍒濇璇勪及瀹屾垚',
+ type: 'success'
+ }
+ ],
+ maintenanceRecords: [
+ {
+ item: '鍩烘湰淇℃伅',
+ content: '渚涜�呰韩浠戒俊鎭‘璁や笌褰曞叆',
+ operator: '寮犲尰鐢�',
+ time: '2023-12-01 08:30:00',
+ status: 'completed'
+ },
+ {
+ item: '鍖荤枟妗f',
+ content: '鐥呭彶璧勬枡鏀堕泦涓庢暣鐞�',
+ operator: '鏉庢姢澹�',
+ time: '2023-12-01 09:15:00',
+ status: 'completed'
+ },
+ {
+ item: '鍒濇璇勪及',
+ content: '鎹愮尞閫傚疁鎬у垵姝ヨ瘎浼�',
+ operator: '鐜嬩富浠�',
+ time: '2023-12-01 10:00:00',
+ status: 'completed'
+ }
+ ]
+ };
+ }
+};
+</script>
+
+<style scoped>
+.card-header {
+ font-weight: 600;
+ color: #303133;
+}
+
+.info-card, .timeline-card {
+ height: 100%;
+}
+</style>
diff --git a/src/views/business/course/components/EthicalReviewStage.vue b/src/views/business/course/components/EthicalReviewStage.vue
new file mode 100644
index 0000000..47e4877
--- /dev/null
+++ b/src/views/business/course/components/EthicalReviewStage.vue
@@ -0,0 +1,206 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <template #header>
+ <el-alert
+ title="浼︾悊瀹℃煡闃舵"
+ :type="getAlertType()"
+ :description="getAlertDescription()"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>瀹℃煡濮斿憳浼氫俊鎭�</span>
+ </div>
+ <el-descriptions :column="1" border>
+ <el-descriptions-item label="濮斿憳浼氬悕绉�">
+ {{ reviewCommittee.name }}
+ </el-descriptions-item>
+ <el-descriptions-item label="瀹℃煡浼氳鏃堕棿">
+ {{ formatTime(reviewCommittee.meetingTime) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍙備細濮斿憳">
+ {{ reviewCommittee.members.length }} 浜�
+ </el-descriptions-item>
+ <el-descriptions-item label="瀹℃煡缁撹">
+ <el-tag :type="reviewCommittee.conclusion ? 'success' : 'warning'">
+ {{ reviewCommittee.conclusion ? '瀹℃煡閫氳繃' : '瀹℃煡涓�' }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="涓诲腑绛惧瓧">
+ {{ reviewCommittee.chairman }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+ </el-col>
+
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>瀹℃煡娴佺▼杩涘害</span>
+ </div>
+ <el-steps direction="vertical" :active="reviewProgress.active" space="80px">
+ <el-step
+ v-for="step in reviewProgress.steps"
+ :key="step.title"
+ :title="step.title"
+ :description="step.description"
+ :status="step.status"
+ />
+ </el-steps>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>濮斿憳瀹℃煡鎰忚</span>
+ </div>
+ <el-table :data="reviewComments" border>
+ <el-table-column label="濮斿憳濮撳悕" prop="memberName" width="120" />
+ <el-table-column label="涓撲笟棰嗗煙" prop="specialty" width="120" />
+ <el-table-column label="瀹℃煡鎰忚" prop="comment" min-width="200" />
+ <el-table-column label="鎶曠エ缁撴灉" width="100">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.vote === '鍚屾剰' ? 'success' : 'danger'">
+ {{ scope.row.vote }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹℃煡鏃堕棿" width="160">
+ <template slot-scope="scope">
+ {{ formatTime(scope.row.reviewTime) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>瀹℃煡鍐宠鏂囦欢</span>
+ <el-button type="primary" size="small" @click="handleViewResolution">
+ 鏌ョ湅鍐宠鏂囦欢
+ </el-button>
+ </div>
+ <div class="resolution-content">
+ <p><strong>浼︾悊瀹℃煡鍐宠锛�</strong></p>
+ <p>{{ resolutionContent }}</p>
+ <el-divider />
+ <div class="signature-area">
+ <p>浼︾悊濮斿憳浼氫富甯細{{ reviewCommittee.chairman }}</p>
+ <p>鏃ユ湡锛歿{ formatTime(reviewCommittee.meetingTime) }}</p>
+ </div>
+ </div>
+ </el-card>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+
+export default {
+ name: 'EthicalReviewStage',
+ components: { BaseStage },
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data() {
+ return {
+ reviewCommittee: {
+ name: '鍖婚櫌浼︾悊瀹℃煡濮斿憳浼�',
+ meetingTime: '2023-12-03 15:20:00',
+ members: ['寮犳暀鎺�', '鏉庝富浠�', '鐜嬪尰鐢�', '璧靛鍛�', '閽变笓瀹�'],
+ conclusion: true,
+ chairman: '寮犳暀鎺�'
+ },
+ reviewProgress: {
+ active: 4,
+ steps: [
+ {
+ title: '鏉愭枡鍒濆',
+ description: '鐢宠鏉愭枡瀹屾暣鎬у鏌�',
+ status: 'finish'
+ },
+ {
+ title: '濮斿憳璇勫',
+ description: '鍚勫鍛樼嫭绔嬪鏌�',
+ status: 'finish'
+ },
+ {
+ title: '浼氳璁ㄨ',
+ description: '濮斿憳浼氶泦浣撹璁�',
+ status: 'finish'
+ },
+ {
+ title: '褰㈡垚鍐宠',
+ description: '鎶曠エ褰㈡垚鏈�缁堝喅璁�',
+ status: 'finish'
+ }
+ ]
+ },
+ reviewComments: [
+ {
+ memberName: '寮犳暀鎺�',
+ specialty: '鍖诲浼︾悊',
+ comment: '鎹愮尞绋嬪簭绗﹀悎浼︾悊瑙勮寖锛屽悓鎰忛�氳繃',
+ vote: '鍚屾剰',
+ reviewTime: '2023-12-03 14:30:00'
+ },
+ {
+ memberName: '鏉庝富浠�',
+ specialty: '涓村簥鍖诲',
+ comment: '鍖荤枟绋嬪簭瑙勮寖锛屾棤浼︾悊闂',
+ vote: '鍚屾剰',
+ reviewTime: '2023-12-03 14:45:00'
+ },
+ {
+ memberName: '鐜嬪尰鐢�',
+ specialty: '娉曞緥鍖诲',
+ comment: '娉曞緥鏂囦欢榻愬叏锛岀▼搴忓悎娉�',
+ vote: '鍚屾剰',
+ reviewTime: '2023-12-03 15:00:00'
+ }
+ ],
+ resolutionContent: '缁忎鸡鐞嗗鏌ュ鍛樹細瀹℃煡锛岃鍣ㄥ畼鎹愮尞妗堜緥绗﹀悎鍖诲浼︾悊瑕佹眰锛屾崘鐚▼搴忚鑼冿紝瀹跺睘鎰忔効鐪熷疄鏈夋晥锛屽悓鎰忚繘琛屽櫒瀹樻崘鐚��'
+ };
+ },
+ methods: {
+ getAlertType() {
+ const status = this.stageData.status;
+ return status === 'completed' ? 'success' :
+ status === 'in_progress' ? 'warning' : 'info';
+ },
+ getAlertDescription() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '浼︾悊瀹℃煡宸查�氳繃锛屽彲浠ヨ繘琛屽櫒瀹樺垎閰�' :
+ status === 'in_progress' ? '浼︾悊瀹℃煡娴佺▼姝e湪杩涜涓�' : '绛夊緟寮�濮嬩鸡鐞嗗鏌ユ祦绋�';
+ },
+ handleViewResolution() {
+ this.$message.info('鏌ョ湅浼︾悊瀹℃煡鍐宠鏂囦欢鍔熻兘');
+ }
+ }
+};
+</script>
+
+<style scoped>
+.resolution-content {
+ padding: 20px;
+ line-height: 1.8;
+}
+
+.signature-area {
+ text-align: right;
+ margin-top: 30px;
+}
+</style>
diff --git a/src/views/business/course/components/MedicalAssessmentStage.vue b/src/views/business/course/components/MedicalAssessmentStage.vue
new file mode 100644
index 0000000..ece6b8f
--- /dev/null
+++ b/src/views/business/course/components/MedicalAssessmentStage.vue
@@ -0,0 +1,208 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <template #header>
+ <el-alert
+ :title="alertTitle"
+ :type="alertType"
+ :description="alertDescription"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>璇勪及姒傚喌</span>
+ </div>
+ <div class="assessment-stats">
+ <div class="stat-item">
+ <span class="stat-label">璇勪及鐘舵��:</span>
+ <el-tag :type="getStatusTag(stageData.status)">
+ {{ getStatusText(stageData.status) }}
+ </el-tag>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">璇勪及鍖荤敓:</span>
+ <span>{{ stageData.operator || '寰呭垎閰�' }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">寮�濮嬫椂闂�:</span>
+ <span>{{ formatTime(stageData.updateTime) }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="stat-label">瀹屾垚鏃堕棿:</span>
+ <span>{{ formatTime(stageData.completeTime) || '-' }}</span>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="16">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>璇勪及椤圭洰杩涘害</span>
+ </div>
+ <div class="progress-list">
+ <div v-for="item in assessmentItems" :key="item.name" class="progress-item">
+ <div class="progress-info">
+ <span class="item-name">{{ item.name }}</span>
+ <span class="item-status">
+ <el-tag :type="item.status === 'completed' ? 'success' : 'warning'" size="small">
+ {{ item.status === 'completed' ? '宸插畬鎴�' : '寰呰瘎浼�' }}
+ </el-tag>
+ </span>
+ </div>
+ <el-progress
+ :percentage="item.status === 'completed' ? 100 : 0"
+ :show-text="false"
+ :status="item.status === 'completed' ? 'success' : 'exception'"
+ />
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>璇勪及璇︽儏璁板綍</span>
+ <el-button type="primary" size="small" @click="handleViewReport">
+ 鏌ョ湅璇勪及鎶ュ憡
+ </el-button>
+ </div>
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="鐢熺悊鎸囨爣璇勪及">
+ {{ assessmentDetails.physiological || '寰呰瘎浼�' }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍣ㄥ畼鍔熻兘璇勪及">
+ {{ assessmentDetails.organFunction || '寰呰瘎浼�' }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎰熸煋鎬х柧鐥呯瓫鏌�">
+ {{ assessmentDetails.infectionScreening || '寰呯瓫鏌�' }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎭舵�ц偪鐦ょ瓫鏌�">
+ {{ assessmentDetails.cancerScreening || '寰呯瓫鏌�' }}
+ </el-descriptions-item>
+ <el-descriptions-item label="璇勪及缁撹">
+ <el-tag :type="assessmentDetails.conclusion ? 'success' : 'warning'">
+ {{ assessmentDetails.conclusion || '璇勪及涓�' }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="璇勪及鍖荤敓鎰忚">
+ {{ assessmentDetails.doctorOpinion || '寰呭~鍐�' }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+
+export default {
+ name: 'MedicalAssessmentStage',
+ components: { BaseStage },
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ computed: {
+ alertTitle() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '鍖诲璇勪及瀹屾垚' :
+ status === 'in_progress' ? '鍖诲璇勪及杩涜涓�' : '寰呭紑濮嬪尰瀛﹁瘎浼�';
+ },
+ alertType() {
+ const status = this.stageData.status;
+ return status === 'completed' ? 'success' :
+ status === 'in_progress' ? 'warning' : 'info';
+ },
+ alertDescription() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '鎵�鏈夊尰瀛﹁瘎浼伴」鐩凡瀹屾垚锛屼緵鑰呯鍚堟崘鐚潯浠�' :
+ status === 'in_progress' ? '鍖诲璇勪及姝e湪杩涜涓紝璇峰叧娉ㄨ瘎浼拌繘搴�' : '绛夊緟寮�濮嬪尰瀛﹁瘎浼版祦绋�';
+ }
+ },
+ data() {
+ return {
+ assessmentItems: [
+ { name: '鐢熺悊鎸囨爣璇勪及', status: 'completed' },
+ { name: '鍣ㄥ畼鍔熻兘璇勪及', status: 'completed' },
+ { name: '鎰熸煋鎬х柧鐥呯瓫鏌�', status: 'completed' },
+ { name: '鎭舵�ц偪鐦ょ瓫鏌�', status: 'completed' },
+ { name: '閬椾紶鎬х柧鐥呯瓫鏌�', status: 'completed' },
+ { name: '蹇冪悊鐘舵�佽瘎浼�', status: 'completed' }
+ ],
+ assessmentDetails: {
+ physiological: '鍚勯」鐢熺悊鎸囨爣姝e父锛岀鍚堟崘鐚姹�',
+ organFunction: '涓昏鍣ㄥ畼鍔熻兘鑹ソ锛屾棤绂佸繉鐥�',
+ infectionScreening: '浼犳煋鐥呯瓫鏌ュ潎涓洪槾鎬�',
+ cancerScreening: '鏃犳伓鎬ц偪鐦よ抗璞�',
+ conclusion: '閫傚悎鍣ㄥ畼鎹愮尞',
+ doctorOpinion: '渚涜�呰韩浣撶姸鍐佃壇濂斤紝绗﹀悎鎹愮尞鍖诲鏍囧噯'
+ }
+ };
+ },
+ methods: {
+ getStatusText(status) {
+ const map = {
+ 'completed': '宸插畬鎴�',
+ 'in_progress': '杩涜涓�',
+ 'pending': '鏈紑濮�'
+ };
+ return map[status] || '鏈煡';
+ },
+ handleViewReport() {
+ this.$message.info('鏌ョ湅鍖诲璇勪及鎶ュ憡鍔熻兘');
+ }
+ }
+};
+</script>
+
+<style scoped>
+.assessment-stats {
+ padding: 10px 0;
+}
+
+.stat-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.stat-label {
+ color: #606266;
+ font-weight: 500;
+}
+
+.progress-list {
+ padding: 10px 0;
+}
+
+.progress-item {
+ margin-bottom: 15px;
+}
+
+.progress-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.item-name {
+ color: #606266;
+ font-size: 14px;
+}
+</style>
diff --git a/src/views/business/course/components/OrganAllocationStage.vue b/src/views/business/course/components/OrganAllocationStage.vue
new file mode 100644
index 0000000..8bc58b5
--- /dev/null
+++ b/src/views/business/course/components/OrganAllocationStage.vue
@@ -0,0 +1,447 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <template #header>
+ <el-alert
+ :title="`鍣ㄥ畼鍒嗛厤 - ${getStatusText()}`"
+ :type="getAlertType()"
+ :description="getAlertDescription()"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍒嗛厤姒傚喌</span>
+ </div>
+ <div class="allocation-stats">
+ <div class="stat-item">
+ <span class="label">寰呭垎閰嶅櫒瀹�:</span>
+ <span class="value">{{ allocationStats.totalOrgans }} 涓�</span>
+ </div>
+ <div class="stat-item">
+ <span class="label">宸插垎閰嶅櫒瀹�:</span>
+ <span class="value">{{ allocationStats.allocatedOrgans }} 涓�</span>
+ </div>
+ <div class="stat-item">
+ <span class="label">鍒嗛厤绯荤粺:</span>
+ <span class="value">{{ allocationStats.system }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="label">鍖归厤鎴愬姛鐜�:</span>
+ <el-progress
+ :percentage="allocationStats.matchRate"
+ :status="allocationStats.matchRate > 80 ? 'success' : 'warning'"
+ />
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍒嗛厤鏃堕棿绐楀彛</span>
+ </div>
+ <div class="time-window">
+ <div class="time-item">
+ <span class="label">鍒嗛厤寮�濮�:</span>
+ <span>{{ formatTime(allocationDetails.startTime) }}</span>
+ </div>
+ <div class="time-item">
+ <span class="label">棰勮瀹屾垚:</span>
+ <span>{{ formatTime(allocationDetails.estimatedEndTime) }}</span>
+ </div>
+ <div class="time-item">
+ <span class="label">鍣ㄥ畼鑰愬彈鏃堕棿:</span>
+ <span>{{ allocationDetails.toleranceTime }}</span>
+ </div>
+ <div class="time-item">
+ <span class="label">鍓╀綑鏃堕棿:</span>
+ <el-tag :type="getTimeRemainingType()">
+ {{ allocationDetails.timeRemaining }}
+ </el-tag>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍒嗛厤浼樺厛绾�</span>
+ </div>
+ <el-steps direction="vertical" :active="prioritySteps.active">
+ <el-step
+ v-for="step in prioritySteps.steps"
+ :key="step.title"
+ :title="step.title"
+ :description="step.description"
+ :status="step.status"
+ />
+ </el-steps>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>鍣ㄥ畼鍒嗛厤璇︽儏</span>
+ <el-button type="primary" size="small" @click="handleStartAllocation">
+ 鍚姩鑷姩鍒嗛厤
+ </el-button>
+ </div>
+ <el-table :data="organAllocationData" border>
+ <el-table-column label="鍣ㄥ畼鍚嶇О" prop="organName" width="120" align="center" />
+ <el-table-column label="鍣ㄥ畼鐘舵��" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="getOrganStatusTag(scope.row.status)" size="small">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍖归厤鍙椾綋" prop="recipient" width="150" />
+ <el-table-column label="琛�鍨嬪尮閰�" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.bloodMatch ? 'success' : 'warning'">
+ {{ scope.row.bloodMatch ? '鍖归厤' : '寰呭尮閰�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="缁勭粐閰嶅瀷" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.tissueMatch ? 'success' : 'warning'">
+ {{ scope.row.tissueMatch ? '鍚堟牸' : '寰呮娴�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="绉绘鍖婚櫌" prop="hospital" min-width="200" />
+ <el-table-column label="鍒嗛厤鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ {{ scope.row.allocationTime || '寰呭垎閰�' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="120" align="center">
+ <template slot-scope="scope">
+ <el-button type="text" size="small" @click="handleViewMatch(scope.row)">
+ 鏌ョ湅鍖归厤
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>鍒嗛厤绠楁硶缁撴灉</span>
+ </div>
+ <div class="algorithm-results">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="result-item">
+ <h4>鍖归厤璇勫垎鎺掑悕</h4>
+ <el-table :data="matchRanking" border size="small">
+ <el-table-column label="鎺掑悕" width="60" align="center">
+ <template slot-scope="scope">
+ {{ scope.$index + 1 }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙椾綋缂栧彿" prop="recipientNo" width="100" />
+ <el-table-column label="鍖归厤鍒嗘暟" prop="matchScore" width="100">
+ <template slot-scope="scope">
+ <el-rate
+ v-model="scope.row.matchScore"
+ disabled
+ show-score
+ text-color="#ff9900"
+ score-template="{value} 鍒�"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎺ㄨ崘鍣ㄥ畼" prop="recommendedOrgan" />
+ </el-table>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="result-item">
+ <h4>鍒嗛厤鍥犵礌鏉冮噸</h4>
+ <div class="weight-distribution">
+ <div v-for="factor in allocationFactors" :key="factor.name" class="factor-item">
+ <span class="factor-name">{{ factor.name }}</span>
+ <el-progress
+ :percentage="factor.weight"
+ :show-text="false"
+ :color="factor.color"
+ />
+ <span class="factor-percent">{{ factor.weight }}%</span>
+ </div>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ </el-card>
+
+ <template #footer>
+ <div class="action-buttons" style="margin-top: 20px; text-align: center;">
+ <el-button type="primary" @click="handleAutoAllocation">
+ 鏅鸿兘鍒嗛厤
+ </el-button>
+ <el-button type="success" @click="handleConfirmAllocation">
+ 纭鍒嗛厤缁撴灉
+ </el-button>
+ <el-button type="warning" @click="handleManualAdjust">
+ 鎵嬪姩璋冩暣
+ </el-button>
+ </div>
+ </template>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+
+export default {
+ name: 'OrganAllocationStage',
+ components: { BaseStage },
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data() {
+ return {
+ allocationStats: {
+ totalOrgans: 3,
+ allocatedOrgans: 0,
+ system: '涓浗浜轰綋鍣ㄥ畼鍒嗛厤涓庡叡浜绠楁満绯荤粺',
+ matchRate: 0
+ },
+ allocationDetails: {
+ startTime: '2023-12-04 10:00:00',
+ estimatedEndTime: '2023-12-04 12:00:00',
+ toleranceTime: '鑲濊剰:12灏忔椂,鑲捐剰:24灏忔椂,蹇冭剰:8灏忔椂,鑲鸿剰:12灏忔椂',
+ timeRemaining: '2灏忔椂'
+ },
+ prioritySteps: {
+ active: 3,
+ steps: [
+ {
+ title: '鐥呮儏鍗遍噸浼樺厛',
+ description: '鏍规嵁鎮h�呯梾鎯呭嵄閲嶇▼搴﹀垎閰峓1](@ref)',
+ status: 'finish'
+ },
+ {
+ title: '缁勭粐閰嶅瀷浼樺厛',
+ description: '缁勭粐閰嶅瀷鍖归厤搴﹂珮鐨勬偅鑰呬紭鍏�',
+ status: 'finish'
+ },
+ {
+ title: '琛�鍨嬬浉鍚屼紭鍏�',
+ description: '琛�鍨嬪尮閰嶇殑鎮h�呬紭鍏堣�冭檻',
+ status: 'finish'
+ },
+ {
+ title: '鍎跨鍖归厤浼樺厛',
+ description: '鍎跨鎮h�呬韩鏈変紭鍏堝垎閰嶆潈',
+ status: 'wait'
+ }
+ ]
+ },
+ organAllocationData: [
+ {
+ organName: '鑲濊剰',
+ status: '寰呭垎閰�',
+ recipient: '',
+ bloodMatch: false,
+ tissueMatch: false,
+ hospital: '',
+ allocationTime: ''
+ },
+ {
+ organName: '鑲捐剰',
+ status: '寰呭垎閰�',
+ recipient: '',
+ bloodMatch: false,
+ tissueMatch: false,
+ hospital: '',
+ allocationTime: ''
+ },
+ {
+ organName: '蹇冭剰',
+ status: '寰呭垎閰�',
+ recipient: '',
+ bloodMatch: false,
+ tissueMatch: false,
+ hospital: '',
+ allocationTime: ''
+ }
+ ],
+ matchRanking: [
+ {
+ recipientNo: 'R202312001',
+ matchScore: 4.8,
+ recommendedOrgan: '鑲濊剰'
+ },
+ {
+ recipientNo: 'R202312002',
+ matchScore: 4.5,
+ recommendedOrgan: '鑲捐剰'
+ },
+ {
+ recipientNo: 'R202312003',
+ matchScore: 4.3,
+ recommendedOrgan: '蹇冭剰'
+ }
+ ],
+ allocationFactors: [
+ { name: '鐥呮儏鍗遍噸绋嬪害', weight: 35, color: '#f56c6c' },
+ { name: '缁勭粐閰嶅瀷鍖归厤', weight: 25, color: '#e6a23c' },
+ { name: '绛夊緟鏃堕棿', weight: 15, color: '#5cb87a' },
+ { name: '鍦扮悊鍥犵礌', weight: 10, color: '#6f7ad3' },
+ { name: '骞撮緞鍥犵礌', weight: 15, color: '#8e44ad' }
+ ]
+ };
+ },
+ methods: {
+ getStatusText() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '宸插畬鎴�' :
+ status === 'in_progress' ? '杩涜涓�' : '鏈紑濮�';
+ },
+ getAlertType() {
+ const status = this.stageData.status;
+ return status === 'completed' ? 'success' :
+ status === 'in_progress' ? 'warning' : 'info';
+ },
+ getAlertDescription() {
+ const status = this.stageData.status;
+ if (status === 'completed') {
+ return '鍣ㄥ畼鍒嗛厤宸插畬鎴愶紝鎵�鏈夊櫒瀹樺潎宸叉垚鍔熷尮閰嶅彈浣�';
+ } else if (status === 'in_progress') {
+ return '鍣ㄥ畼鍒嗛厤杩涜涓紝绯荤粺姝e湪鑷姩鍖归厤鏈�浣冲彈浣�';
+ }
+ return '绛夊緟寮�濮嬪櫒瀹樺垎閰嶆祦绋�';
+ },
+ getOrganStatusTag(status) {
+ const map = {
+ '宸插垎閰�': 'success',
+ '鍒嗛厤涓�': 'warning',
+ '寰呭垎閰�': 'info'
+ };
+ return map[status] || 'info';
+ },
+ getTimeRemainingType() {
+ return this.allocationDetails.timeRemaining.includes('灏忔椂') ? 'success' : 'danger';
+ },
+ handleStartAllocation() {
+ this.$confirm('纭鍚姩鑷姩鍣ㄥ畼鍒嗛厤鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.$message.success('宸插惎鍔ㄨ嚜鍔ㄥ垎閰嶇畻娉�');
+ // 妯℃嫙鍒嗛厤杩囩▼
+ this.allocationStats.allocatedOrgans = 3;
+ this.allocationStats.matchRate = 95;
+ this.organAllocationData.forEach(organ => {
+ organ.status = '宸插垎閰�';
+ organ.allocationTime = new Date().toISOString().replace('T', ' ').substring(0, 19);
+ });
+ });
+ },
+ handleAutoAllocation() {
+ this.$message.info('鎵ц鏅鸿兘鍒嗛厤绠楁硶');
+ },
+ handleConfirmAllocation() {
+ this.$confirm('纭鏈�缁堝垎閰嶇粨鏋滃悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'success'
+ }).then(() => {
+ this.$message.success('鍣ㄥ畼鍒嗛厤缁撴灉宸茬‘璁�');
+ });
+ },
+ handleManualAdjust() {
+ this.$message.info('杩涘叆鎵嬪姩璋冩暣妯″紡');
+ },
+ handleViewMatch(row) {
+ this.$message.info(`鏌ョ湅${row.organName}鐨勫尮閰嶈鎯卄);
+ }
+ }
+};
+</script>
+
+<style scoped>
+.allocation-stats, .time-window {
+ padding: 10px 0;
+}
+
+.stat-item, .time-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.stat-item .label, .time-item .label {
+ color: #606266;
+ font-weight: 500;
+ min-width: 100px;
+}
+
+.stat-item .value {
+ font-weight: 600;
+ color: #409EFF;
+}
+
+.algorithm-results {
+ padding: 15px 0;
+}
+
+.result-item {
+ margin-bottom: 20px;
+}
+
+.result-item h4 {
+ margin-bottom: 15px;
+ color: #303133;
+}
+
+.weight-distribution {
+ padding: 10px 0;
+}
+
+.factor-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.factor-name {
+ width: 120px;
+ color: #606266;
+ font-size: 14px;
+}
+
+.factor-item .el-progress {
+ flex: 1;
+ margin: 0 15px;
+}
+
+.factor-percent {
+ width: 40px;
+ text-align: right;
+ color: #909399;
+ font-size: 12px;
+}
+</style>
diff --git a/src/views/business/course/components/OrganProcurementStage.vue b/src/views/business/course/components/OrganProcurementStage.vue
new file mode 100644
index 0000000..1b8a555
--- /dev/null
+++ b/src/views/business/course/components/OrganProcurementStage.vue
@@ -0,0 +1,499 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <template #header>
+ <el-alert
+ :title="`鍣ㄥ畼鑾峰彇 - ${getStatusText()}`"
+ :type="getAlertType()"
+ :description="getAlertDescription()"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鑾峰彇鎵嬫湳淇℃伅</span>
+ </div>
+ <el-descriptions :column="1" border size="small">
+ <el-descriptions-item label="鎵嬫湳鏃堕棿">
+ {{ formatTime(procurementDetails.surgeryTime) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎵嬫湳鍦扮偣">
+ {{ procurementDetails.location }}
+ </el-descriptions-item>
+ <el-descriptions-item label="涓诲垁鍖荤敓">
+ {{ procurementDetails.surgeon }}
+ </el-descriptions-item>
+ <el-descriptions-item label="楹婚唹鍖荤敓">
+ {{ procurementDetails.anesthesiologist }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎵嬫湳鐘舵��">
+ <el-tag :type="procurementDetails.status === 'completed' ? 'success' : 'warning'">
+ {{ procurementDetails.status === 'completed' ? '宸插畬鎴�' : '杩涜涓�' }}
+ </el-tag>
+ </el-descriptions-item>
+ </el-descriptions>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍣ㄥ畼鑾峰彇缁熻</span>
+ </div>
+ <div class="procurement-stats">
+ <div class="stat-item">
+ <span class="label">璁″垝鑾峰彇:</span>
+ <span class="value">{{ procurementStats.planned }} 涓�</span>
+ </div>
+ <div class="stat-item">
+ <span class="label">瀹為檯鑾峰彇:</span>
+ <span class="value">{{ procurementStats.actual }} 涓�</span>
+ </div>
+ <div class="stat-item">
+ <span class="label">鑾峰彇鎴愬姛鐜�:</span>
+ <el-progress
+ :percentage="procurementStats.successRate"
+ :status="procurementStats.successRate > 90 ? 'success' : 'warning'"
+ />
+ </div>
+ <div class="stat-item">
+ <span class="label">璐ㄩ噺璇勪及:</span>
+ <el-rate
+ v-model="procurementStats.qualityRating"
+ disabled
+ show-score
+ text-color="#ff9900"
+ score-template="{value} 鏄�"
+ />
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="8">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>淇濆瓨涓庤繍杈�</span>
+ </div>
+ <div class="preservation-info">
+ <div class="info-item">
+ <span class="label">淇濆瓨鏂瑰紡:</span>
+ <span>{{ preservationDetails.method }}</span>
+ </div>
+ <div class="info-item">
+ <span class="label">淇濆瓨娓╁害:</span>
+ <span>{{ preservationDetails.temperature }}</span>
+ </div>
+ <div class="info-item">
+ <span class="label">鐏屾敞娑�:</span>
+ <span>{{ preservationDetails.perfusionSolution }}</span>
+ </div>
+ <div class="info-item">
+ <span class="label">棰勮瀛樻椿鏃堕棿:</span>
+ <el-tag :type="getSurvivalTimeType()">
+ {{ preservationDetails.survivalTime }}
+ </el-tag>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>鍣ㄥ畼鑾峰彇璇︽儏</span>
+ <el-button type="primary" size="small" @click="handleStartProcurement">
+ 寮�濮嬭幏鍙栨墜鏈�
+ </el-button>
+ </div>
+ <el-table :data="organProcurementData" border>
+ <el-table-column label="鍣ㄥ畼鍚嶇О" prop="organName" width="120" align="center" />
+ <el-table-column label="鑾峰彇鐘舵��" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="getProcurementStatusTag(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鑾峰彇鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ {{ formatTime(scope.row.procurementTime) || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="閲嶉噺(g)" prop="weight" width="100" align="center" />
+ <el-table-column label="璐ㄩ噺璇勪及" width="120" align="center">
+ <template slot-scope="scope">
+ <el-rate
+ v-model="scope.row.qualityScore"
+ disabled
+ show-score
+ text-color="#ff9900"
+ score-template="{value}"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="鐏屾敞鎯呭喌" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.perfusionStatus ? 'success' : 'warning'">
+ {{ scope.row.perfusionStatus ? '宸插畬鎴�' : '寰呯亴娉�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="淇濆瓨鏂瑰紡" prop="preservationMethod" width="120" />
+ <el-table-column label="鎿嶄綔" width="150" align="center">
+ <template slot-scope="scope">
+ <el-button type="text" size="small" @click="handleViewOrgan(scope.row)">
+ 鏌ョ湅璇︽儏
+ </el-button>
+ <el-button
+ v-if="scope.row.status !== '宸茶幏鍙�'"
+ type="text"
+ size="small"
+ @click="handleRecordProcurement(scope.row)"
+ >
+ 璁板綍鑾峰彇
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>鎵嬫湳璁板綍涓庡奖鍍�</span>
+ </div>
+ <el-tabs v-model="activeMedicalTab">
+ <el-tab-pane label="鎵嬫湳璁板綍" name="surgeryRecord">
+ <div class="surgery-record">
+ <el-timeline>
+ <el-timeline-item
+ v-for="record in surgeryRecords"
+ :key="record.time"
+ :timestamp="formatTime(record.time)"
+ :type="record.type"
+ :icon="record.icon"
+ >
+ <p>{{ record.content }}</p>
+ <div v-if="record.images && record.images.length > 0" class="record-images">
+ <el-image
+ v-for="(img, index) in record.images"
+ :key="index"
+ :src="img"
+ :preview-src-list="record.images"
+ style="width: 100px; height: 100px; margin-right: 10px;"
+ />
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ </div>
+ </el-tab-pane>
+
+ <el-tab-pane label="鍣ㄥ畼褰卞儚" name="organImages">
+ <div class="organ-images">
+ <el-row :gutter="15">
+ <el-col v-for="organ in organImages" :key="organ.name" :span="8">
+ <el-card shadow="hover">
+ <div slot="header" class="image-header">
+ <span>{{ organ.name }}</span>
+ </div>
+ <el-image
+ :src="organ.image"
+ :preview-src-list="[organ.image]"
+ fit="cover"
+ style="width: 100%; height: 200px;"
+ />
+ <div style="padding: 10px;">
+ <p>{{ organ.description }}</p>
+ <el-tag size="small">{{ organ.status }}</el-tag>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ </el-tab-pane>
+
+ <el-tab-pane label="璐ㄩ噺妫�娴嬫姤鍛�" name="qualityReport">
+ <div class="quality-report">
+ <el-table :data="qualityReports" border>
+ <el-table-column label="妫�娴嬮」鐩�" prop="item" width="150" />
+ <el-table-column label="妫�娴嬬粨鏋�" prop="result" width="120">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.pass ? 'success' : 'danger'">
+ {{ scope.row.pass ? '鍚堟牸' : '涓嶅悎鏍�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙傝�冭寖鍥�" prop="reference" width="120" />
+ <el-table-column label="妫�娴嬪��" prop="value" width="100" />
+ <el-table-column label="妫�娴嬫椂闂�" width="160">
+ <template slot-scope="scope">
+ {{ formatTime(scope.row.testTime) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="妫�娴嬪尰鐢�" prop="doctor" width="120" />
+ </el-table>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
+ </el-card>
+
+ <template #footer>
+ <div class="action-buttons" style="margin-top: 20px; text-align: center;">
+ <el-button type="primary" @click="handleCompleteProcurement">
+ 瀹屾垚鍣ㄥ畼鑾峰彇
+ </el-button>
+ <el-button type="success" @click="handleGenerateReport">
+ 鐢熸垚鑾峰彇鎶ュ憡
+ </el-button>
+ <el-button type="warning" @click="handleUploadEvidence">
+ 涓婁紶鎵嬫湳璇佹嵁
+ </el-button>
+ </div>
+ </template>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+
+export default {
+ name: 'OrganProcurementStage',
+ components: { BaseStage },
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data() {
+ return {
+ activeMedicalTab: 'surgeryRecord',
+ procurementDetails: {
+ surgeryTime: '2023-12-04 14:00:00',
+ location: '鎵嬫湳瀹や竴鍙�',
+ surgeon: '寮犱富浠�',
+ anesthesiologist: '鐜嬪尰鐢�',
+ status: 'in_progress'
+ },
+ procurementStats: {
+ planned: 3,
+ actual: 0,
+ successRate: 0,
+ qualityRating: 0
+ },
+ preservationDetails: {
+ method: '浣庢俯鏈烘鐏屾敞[9](@ref)',
+ temperature: '4掳C',
+ perfusionSolution: 'UW淇濆瓨娑�',
+ survivalTime: '鑲濊剰12灏忔椂[1](@ref)'
+ },
+ organProcurementData: [
+ {
+ organName: '鑲濊剰',
+ status: '鑾峰彇涓�',
+ procurementTime: '',
+ weight: 1500,
+ qualityScore: 0,
+ perfusionStatus: false,
+ preservationMethod: '浣庢俯淇濆瓨'
+ },
+ {
+ organName: '鑲捐剰',
+ status: '寰呰幏鍙�',
+ procurementTime: '',
+ weight: 0,
+ qualityScore: 0,
+ perfusionStatus: false,
+ preservationMethod: ''
+ },
+ {
+ organName: '蹇冭剰',
+ status: '寰呰幏鍙�',
+ procurementTime: '',
+ weight: 0,
+ qualityScore: 0,
+ perfusionStatus: false,
+ preservationMethod: ''
+ }
+ ],
+ surgeryRecords: [
+ {
+ time: '2023-12-04 14:00:00',
+ content: '鎵嬫湳寮�濮嬶紝鎮h�呬綋浣嶆憜鏀撅紝娑堟瘨閾哄肪',
+ type: 'primary',
+ icon: 'el-icon-video-play'
+ },
+ {
+ time: '2023-12-04 14:30:00',
+ content: '寮�鑵规墜鏈紝鏆撮湶鑵硅厰鍣ㄥ畼',
+ type: 'success',
+ icon: 'el-icon-scissors'
+ },
+ {
+ time: '2023-12-04 15:00:00',
+ content: '鑲濊剰娓哥锛岃绠¤В鍓栧垎绂�',
+ type: 'warning',
+ icon: 'el-icon-medal'
+ }
+ ],
+ organImages: [
+ {
+ name: '鑲濊剰',
+ image: '/images/liver-procurement.jpg',
+ description: '鑾峰彇鐨勮倽鑴忓櫒瀹橈紝褰㈡�佸畬鏁�',
+ status: '璐ㄩ噺鑹ソ'
+ }
+ ],
+ qualityReports: [
+ {
+ item: '鑲濆姛鑳介叾瀛�',
+ result: true,
+ reference: '<40 U/L',
+ value: '35 U/L',
+ testTime: '2023-12-04 16:00:00',
+ doctor: '鏉庢楠屽笀'
+ },
+ {
+ item: '缁勭粐瀹屾暣鎬�',
+ result: true,
+ reference: '瀹屾暣',
+ value: '瀹屾暣',
+ testTime: '2023-12-04 16:15:00',
+ doctor: '寮犵梾鐞嗗笀'
+ }
+ ]
+ };
+ },
+ methods: {
+ getStatusText() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '宸插畬鎴�' :
+ status === 'in_progress' ? '杩涜涓�' : '鏈紑濮�';
+ },
+ getAlertType() {
+ const status = this.stageData.status;
+ return status === 'completed' ? 'success' :
+ status === 'in_progress' ? 'warning' : 'info';
+ },
+ getAlertDescription() {
+ const status = this.stageData.status;
+ if (status === 'completed') {
+ return '鍣ㄥ畼鑾峰彇鎵嬫湳宸插畬鎴愶紝鎵�鏈夊櫒瀹樺潎宸叉垚鍔熻幏鍙栧苟淇濆瓨';
+ } else if (status === 'in_progress') {
+ return '鍣ㄥ畼鑾峰彇鎵嬫湳杩涜涓紝璇峰瘑鍒囧叧娉ㄦ墜鏈繘灞�';
+ }
+ return '绛夊緟寮�濮嬪櫒瀹樿幏鍙栨墜鏈�';
+ },
+ getProcurementStatusTag(status) {
+ const map = {
+ '宸茶幏鍙�': 'success',
+ '鑾峰彇涓�': 'warning',
+ '寰呰幏鍙�': 'info'
+ };
+ return map[status] || 'info';
+ },
+ getSurvivalTimeType() {
+ return this.preservationDetails.survivalTime.includes('灏忔椂') ? 'success' : 'danger';
+ },
+ handleStartProcurement() {
+ this.$confirm('纭寮�濮嬪櫒瀹樿幏鍙栨墜鏈悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.$message.success('鍣ㄥ畼鑾峰彇鎵嬫湳宸插紑濮�');
+ });
+ },
+ handleRecordProcurement(row) {
+ this.$prompt('璇疯緭鍏ュ櫒瀹橀噸閲�(g)', '璁板綍鍣ㄥ畼鑾峰彇', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ inputPattern: /^\d+$/,
+ inputErrorMessage: '璇疯緭鍏ユ湁鏁堢殑閲嶉噺鏁板瓧'
+ }).then(({ value }) => {
+ row.status = '宸茶幏鍙�';
+ row.procurementTime = new Date().toISOString().replace('T', ' ').substring(0, 19);
+ row.weight = parseInt(value);
+ row.qualityScore = 4.5;
+ row.perfusionStatus = true;
+
+ this.procurementStats.actual++;
+ this.procurementStats.successRate = Math.round(
+ (this.procurementStats.actual / this.procurementStats.planned) * 100
+ );
+ this.procurementStats.qualityRating = 4.2;
+
+ this.$message.success(`${row.organName}鑾峰彇璁板綍宸蹭繚瀛榒);
+ });
+ },
+ handleViewOrgan(row) {
+ this.$message.info(`鏌ョ湅${row.organName}鐨勮缁嗕俊鎭痐);
+ },
+ handleCompleteProcurement() {
+ this.$confirm('纭瀹屾垚鎵�鏈夊櫒瀹樿幏鍙栧悧锛�', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'success'
+ }).then(() => {
+ this.$message.success('鍣ㄥ畼鑾峰彇闃舵宸插畬鎴�');
+ });
+ },
+ handleGenerateReport() {
+ this.$message.info('鐢熸垚鍣ㄥ畼鑾峰彇鎶ュ憡');
+ },
+ handleUploadEvidence() {
+ this.$message.info('涓婁紶鎵嬫湳璇佹嵁鏉愭枡');
+ }
+ }
+};
+</script>
+
+<style scoped>
+.procurement-stats, .preservation-info {
+ padding: 10px 0;
+}
+
+.stat-item, .info-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.stat-item .label, .info-item .label {
+ color: #606266;
+ font-weight: 500;
+ min-width: 100px;
+}
+
+.stat-item .value {
+ font-weight: 600;
+ color: #409EFF;
+}
+
+.surgery-record {
+ padding: 20px;
+}
+
+.record-images {
+ margin-top: 10px;
+}
+
+.image-header {
+ font-weight: 600;
+ color: #303133;
+}
+
+.quality-report {
+ padding: 10px 0;
+}
+</style>
diff --git a/src/views/business/course/components/OrganUtilizationStage.vue b/src/views/business/course/components/OrganUtilizationStage.vue
new file mode 100644
index 0000000..95fbb77
--- /dev/null
+++ b/src/views/business/course/components/OrganUtilizationStage.vue
@@ -0,0 +1,812 @@
+<template>
+ <base-stage :stage-data="stageData" :case-info="caseInfo">
+ <!-- 澶撮儴璀﹀憡淇℃伅 -->
+ <template #header>
+ <el-alert
+ :title="`鍣ㄥ畼鍒╃敤 - ${getStatusText()}`"
+ :type="getAlertType()"
+ :description="getAlertDescription()"
+ show-icon
+ :closable="false"
+ />
+ </template>
+
+ <!-- 缁熻姒傝琛� -->
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <el-col :span="6">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍒╃敤姒傚喌</span>
+ </div>
+ <div class="utilization-overview">
+ <div class="overview-item">
+ <div class="overview-icon" style="color: #67C23A;">
+ <i class="el-icon-success"></i>
+ </div>
+ <div class="overview-content">
+ <div class="value">{{ utilizationStats.transplanted }}</div>
+ <div class="label">宸茬Щ妞嶅櫒瀹�</div>
+ </div>
+ </div>
+ <div class="overview-item">
+ <div class="overview-icon" style="color: #E6A23C;">
+ <i class="el-icon-time"></i>
+ </div>
+ <div class="overview-content">
+ <div class="value">{{ utilizationStats.inProgress }}</div>
+ <div class="label">绉绘涓�</div>
+ </div>
+ </div>
+ <div class="overview-item">
+ <div class="overview-icon" style="color: #F56C6C;">
+ <i class="el-icon-warning"></i>
+ </div>
+ <div class="overview-content">
+ <div class="value">{{ utilizationStats.failed }}</div>
+ <div class="label">绉绘澶辫触</div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="6">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鎴愬姛鐜囩粺璁�</span>
+ </div>
+ <div class="success-stats">
+ <div class="success-item">
+ <span class="label">绉绘鎴愬姛鐜�:</span>
+ <el-progress
+ :percentage="utilizationStats.successRate"
+ :status="utilizationStats.successRate > 85 ? 'success' : 'warning'"
+ />
+ </div>
+ <div class="success-item">
+ <span class="label">鍣ㄥ畼鍒╃敤鐜�:</span>
+ <el-progress
+ :percentage="utilizationStats.utilizationRate"
+ status="success"
+ />
+ </div>
+ <div class="success-item">
+ <span class="label">鎮h�呭瓨娲荤巼:</span>
+ <el-progress
+ :percentage="utilizationStats.survivalRate"
+ status="success"
+ />
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="6">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鏃堕棿璺熻釜</span>
+ </div>
+ <div class="time-tracking">
+ <div class="time-item">
+ <span class="label">鑾峰彇鍒扮Щ妞�:</span>
+ <span class="value">{{ timeTracking.procurementToTransplant }}</span>
+ </div>
+ <div class="time-item">
+ <span class="label">鍐风己琛�鏃堕棿:</span>
+ <span class="value">{{ timeTracking.coldIschemiaTime }}</span>
+ </div>
+ <div class="time-item">
+ <span class="label">鎵嬫湳鏃堕暱:</span>
+ <span class="value">{{ timeTracking.surgeryDuration }}</span>
+ </div>
+ <div class="time-item">
+ <span class="label">ICU鍋滅暀:</span>
+ <span class="value">{{ timeTracking.icuStay }}</span>
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <el-col :span="6">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>璐ㄩ噺璇勪及</span>
+ </div>
+ <div class="quality-assessment">
+ <div class="quality-item">
+ <span class="label">鍣ㄥ畼璐ㄩ噺璇勫垎:</span>
+ <el-rate
+ v-model="qualityStats.organQuality"
+ disabled
+ show-score
+ text-color="#ff9900"
+ score-template="{value}"
+ />
+ </div>
+ <div class="quality-item">
+ <span class="label">鎵嬫湳璐ㄩ噺:</span>
+ <el-rate
+ v-model="qualityStats.surgeryQuality"
+ disabled
+ show-score
+ text-color="#ff9900"
+ score-template="{value}"
+ />
+ </div>
+ <div class="quality-item">
+ <span class="label">闅忚瀹屾垚鐜�:</span>
+ <el-progress
+ :percentage="qualityStats.followupCompletionRate"
+ status="success"
+ />
+ </div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 鏁版嵁鍙鍖栭儴鍒� -->
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <!-- 鍣ㄥ畼鍒╃敤鍒嗗竷鍥� -->
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>鍣ㄥ畼鍒╃敤鍒嗗竷</span>
+ <el-radio-group v-model="chartView" size="small" @change="updateCharts">
+ <el-radio-button label="bar">鏌辩姸鍥�</el-radio-button>
+ <el-radio-button label="pie">楗煎浘</el-radio-button>
+ </el-radio-group>
+ </div>
+ <div class="chart-container">
+ <div ref="organDistributionChart" style="width: 100%; height: 300px;"></div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <!-- 鎴愬姛鐜囪秼鍔垮浘 -->
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>绉绘鎴愬姛鐜囪秼鍔�</span>
+ </div>
+ <div class="chart-container">
+ <div ref="successTrendChart" style="width: 100%; height: 300px;"></div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" style="margin-top: 20px;">
+ <!-- 闅忚鏁版嵁缁熻 -->
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>闅忚鏁版嵁缁熻</span>
+ </div>
+ <div class="chart-container">
+ <div ref="followupStatsChart" style="width: 100%; height: 300px;"></div>
+ </div>
+ </el-card>
+ </el-col>
+
+ <!-- 骞跺彂鐥囧垎鏋� -->
+ <el-col :span="12">
+ <el-card>
+ <div slot="header" class="card-header">
+ <span>骞跺彂鐥囧垎鏋�</span>
+ </div>
+ <div class="chart-container">
+ <div ref="complicationChart" style="width: 100%; height: 300px;"></div>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <!-- 璇︾粏鏁版嵁琛ㄦ牸 -->
+ <el-card style="margin-top: 20px;">
+ <div slot="header" class="card-header">
+ <span>鍣ㄥ畼鍒╃敤璇︽儏</span>
+ <el-button type="primary" size="small" @click="exportData">
+ 瀵煎嚭鏁版嵁
+ </el-button>
+ </div>
+
+ <el-table :data="organUtilizationData" v-loading="loading" border>
+ <el-table-column label="鍣ㄥ畼鍚嶇О" prop="organName" width="120" align="center" />
+ <el-table-column label="绉绘鐘舵��" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="getTransplantStatusTag(scope.row.status)">
+ {{ scope.row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙椾綋淇℃伅" width="150">
+ <template slot-scope="scope">
+ <div>{{ scope.row.recipientName }}</div>
+ <div style="font-size: 12px; color: #909399;">{{ scope.row.recipientAge }}宀�/{{ scope.row.recipientGender }}</div>
+ </template>
+ </el-table-column>
+ <el-table-column label="绉绘鍖婚櫌" prop="hospital" min-width="180" />
+ <el-table-column label="绉绘鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ {{ formatTime(scope.row.transplantTime) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="闅忚娆℃暟" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="scope.row.followupCount > 0 ? 'success' : 'info'">
+ {{ scope.row.followupCount }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="褰撳墠鐘舵��" width="120" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="getRecipientStatusTag(scope.row.recipientStatus)">
+ {{ scope.row.recipientStatus }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="150" align="center">
+ <template slot-scope="scope">
+ <el-button type="text" size="small" @click="handleViewDetails(scope.row)">
+ 鏌ョ湅璇︽儏
+ </el-button>
+ <el-button type="text" size="small" @click="handleAddFollowup(scope.row)">
+ 娣诲姞闅忚
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+
+ <!-- 琛屽姩鎸夐挳 -->
+ <template #footer>
+ <div class="action-buttons" style="margin-top: 20px; text-align: center;">
+ <el-button type="primary" @click="handleGenerateReport">
+ 鐢熸垚鍒╃敤鎶ュ憡
+ </el-button>
+ <el-button type="success" @click="handleCompleteUtilization">
+ 瀹屾垚鍣ㄥ畼鍒╃敤
+ </el-button>
+ <el-button type="warning" @click="handleStatistics">
+ 缁熻鏁版嵁鍒嗘瀽
+ </el-button>
+ </div>
+ </template>
+ </base-stage>
+</template>
+
+<script>
+import BaseStage from './BaseStage.vue';
+import * as echarts from 'echarts';
+
+export default {
+ name: 'OrganUtilizationStage',
+ components: { BaseStage },
+ props: {
+ stageData: {
+ type: Object,
+ default: () => ({})
+ },
+ caseInfo: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ data() {
+ return {
+ chartView: 'bar',
+ loading: false,
+ utilizationStats: {
+ transplanted: 3,
+ inProgress: 0,
+ failed: 0,
+ successRate: 95,
+ utilizationRate: 100,
+ survivalRate: 92
+ },
+ timeTracking: {
+ procurementToTransplant: '4.5灏忔椂',
+ coldIschemiaTime: '鑲濊剰:6h,鑲捐剰:8h,蹇冭剰:3h',
+ surgeryDuration: '鑲濊剰:4h,鑲捐剰:3h,蹇冭剰:5h',
+ icuStay: '鑲濊剰:3澶�,鑲捐剰:2澶�,蹇冭剰:5澶�'
+ },
+ qualityStats: {
+ organQuality: 4.5,
+ surgeryQuality: 4.8,
+ followupCompletionRate: 85
+ },
+ organUtilizationData: [
+ {
+ organName: '鑲濊剰',
+ status: '绉绘鎴愬姛',
+ recipientName: '鐜嬪厛鐢�',
+ recipientAge: 45,
+ recipientGender: '鐢�',
+ hospital: '鍖椾含鍗忓拰鍖婚櫌绉绘涓績',
+ transplantTime: '2023-12-04 16:00:00',
+ followupCount: 3,
+ recipientStatus: '鎭㈠鑹ソ'
+ },
+ {
+ organName: '鑲捐剰',
+ status: '绉绘鎴愬姛',
+ recipientName: '鏉庡コ澹�',
+ recipientAge: 38,
+ recipientGender: '濂�',
+ hospital: '涓婃捣鐟為噾鍖婚櫌绉绘涓績',
+ transplantTime: '2023-12-04 17:30:00',
+ followupCount: 2,
+ recipientStatus: '绋冲畾鎭㈠'
+ },
+ {
+ organName: '蹇冭剰',
+ status: '绉绘鎴愬姛',
+ recipientName: '闄堝厛鐢�',
+ recipientAge: 52,
+ recipientGender: '鐢�',
+ hospital: '骞垮窞涓北鍖婚櫌蹇冭剰涓績',
+ transplantTime: '2023-12-04 18:15:00',
+ followupCount: 1,
+ recipientStatus: '瀵嗗垏瑙傚療'
+ }
+ ],
+ // 鍥捐〃瀹炰緥
+ organDistributionChart: null,
+ successTrendChart: null,
+ followupStatsChart: null,
+ complicationChart: null
+ };
+ },
+ mounted() {
+ this.$nextTick(() => {
+ this.initCharts();
+ });
+ },
+ beforeDestroy() {
+ // 閿�姣佸浘琛ㄥ疄渚�
+ if (this.organDistributionChart) {
+ this.organDistributionChart.dispose();
+ }
+ if (this.successTrendChart) {
+ this.successTrendChart.dispose();
+ }
+ if (this.followupStatsChart) {
+ this.followupStatsChart.dispose();
+ }
+ if (this.complicationChart) {
+ this.complicationChart.dispose();
+ }
+ },
+ methods: {
+ // 鍒濆鍖栧浘琛�
+ initCharts() {
+ this.initOrganDistributionChart();
+ this.initSuccessTrendChart();
+ this.initFollowupStatsChart();
+ this.initComplicationChart();
+ },
+
+ // 鍒濆鍖栧櫒瀹樺垎甯冨浘琛�
+ initOrganDistributionChart() {
+ this.organDistributionChart = echarts.init(this.$refs.organDistributionChart);
+ const option = {
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b}: {c} ({d}%)'
+ },
+ legend: {
+ orient: 'vertical',
+ right: 10,
+ top: 'center',
+ data: ['鑲濊剰', '鑲捐剰', '蹇冭剰', '鑲鸿剰', '瑙掕啘', '鍏朵粬']
+ },
+ series: [
+ {
+ name: '鍣ㄥ畼鍒╃敤',
+ type: 'pie',
+ radius: ['50%', '70%'],
+ avoidLabelOverlap: false,
+ label: {
+ show: false,
+ position: 'center'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: '18',
+ fontWeight: 'bold'
+ }
+ },
+ labelLine: {
+ show: false
+ },
+ data: [
+ { value: 35, name: '鑲濊剰' },
+ { value: 30, name: '鑲捐剰' },
+ { value: 15, name: '蹇冭剰' },
+ { value: 10, name: '鑲鸿剰' },
+ { value: 8, name: '瑙掕啘' },
+ { value: 2, name: '鍏朵粬' }
+ ]
+ }
+ ]
+ };
+ this.organDistributionChart.setOption(option);
+ },
+
+ // 鍒濆鍖栨垚鍔熺巼瓒嬪娍鍥捐〃
+ initSuccessTrendChart() {
+ this.successTrendChart = echarts.init(this.$refs.successTrendChart);
+ const option = {
+ tooltip: {
+ trigger: 'axis'
+ },
+ legend: {
+ data: ['鑲濊剰绉绘', '鑲捐剰绉绘', '蹇冭剰绉绘', '骞冲潎鎴愬姛鐜�']
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�']
+ },
+ yAxis: {
+ type: 'value',
+ min: 80,
+ max: 100
+ },
+ series: [
+ {
+ name: '鑲濊剰绉绘',
+ type: 'line',
+ smooth: true,
+ data: [92, 93, 94, 95, 96, 95, 96]
+ },
+ {
+ name: '鑲捐剰绉绘',
+ type: 'line',
+ smooth: true,
+ data: [94, 95, 95, 96, 95, 96, 97]
+ },
+ {
+ name: '蹇冭剰绉绘',
+ type: 'line',
+ smooth: true,
+ data: [88, 89, 90, 91, 92, 91, 92]
+ },
+ {
+ name: '骞冲潎鎴愬姛鐜�',
+ type: 'line',
+ smooth: true,
+ lineStyle: {
+ type: 'dashed'
+ },
+ data: [91.3, 92.3, 93, 94, 94.3, 94, 95]
+ }
+ ]
+ };
+ this.successTrendChart.setOption(option);
+ },
+
+ // 鍒濆鍖栭殢璁跨粺璁″浘琛�
+ initFollowupStatsChart() {
+ this.followupStatsChart = echarts.init(this.$refs.followupStatsChart);
+ const option = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ }
+ },
+ legend: {
+ data: ['璁″垝闅忚', '宸插畬鎴�', '閫炬湡鏈畬鎴�']
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'value'
+ },
+ yAxis: {
+ type: 'category',
+ data: ['1涓湀', '3涓湀', '6涓湀', '1骞�', '2骞�', '5骞�']
+ },
+ series: [
+ {
+ name: '璁″垝闅忚',
+ type: 'bar',
+ stack: 'total',
+ label: {
+ show: true
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [120, 132, 101, 134, 90, 60]
+ },
+ {
+ name: '宸插畬鎴�',
+ type: 'bar',
+ stack: 'total',
+ label: {
+ show: true
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [115, 125, 95, 120, 85, 55]
+ },
+ {
+ name: '閫炬湡鏈畬鎴�',
+ type: 'bar',
+ stack: 'total',
+ label: {
+ show: true
+ },
+ emphasis: {
+ focus: 'series'
+ },
+ data: [5, 7, 6, 14, 5, 5]
+ }
+ ]
+ };
+ this.followupStatsChart.setOption(option);
+ },
+
+ // 鍒濆鍖栧苟鍙戠棁鍒嗘瀽鍥捐〃
+ initComplicationChart() {
+ this.complicationChart = echarts.init(this.$refs.complicationChart);
+ const option = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ }
+ },
+ radar: {
+ indicator: [
+ { name: '鎰熸煋椋庨櫓', max: 100 },
+ { name: '鎺掓枼鍙嶅簲', max: 100 },
+ { name: '琛�绠″苟鍙戠棁', max: 100 },
+ { name: '鑳嗛亾骞跺彂鐥�', max: 100 },
+ { name: '浠h阿寮傚父', max: 100 },
+ { name: '鍏朵粬骞跺彂鐥�', max: 100 }
+ ]
+ },
+ series: [
+ {
+ type: 'radar',
+ data: [
+ {
+ value: [85, 90, 78, 82, 75, 70],
+ name: '鑲濊剰绉绘',
+ areaStyle: {}
+ },
+ {
+ value: [78, 85, 72, 65, 80, 68],
+ name: '鑲捐剰绉绘',
+ areaStyle: {}
+ },
+ {
+ value: [90, 88, 85, 60, 82, 75],
+ name: '蹇冭剰绉绘',
+ areaStyle: {}
+ }
+ ]
+ }
+ ]
+ };
+ this.complicationChart.setOption(option);
+ },
+
+ // 鏇存柊鍥捐〃瑙嗗浘
+ updateCharts() {
+ if (this.chartView === 'bar') {
+ this.updateToBarChart();
+ } else {
+ this.initOrganDistributionChart(); // 鍒囧洖楗煎浘
+ }
+ },
+
+ // 鏇存柊涓烘煴鐘跺浘
+ updateToBarChart() {
+ const option = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ }
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: ['鑲濊剰', '鑲捐剰', '蹇冭剰', '鑲鸿剰', '瑙掕啘', '鍏朵粬']
+ },
+ yAxis: {
+ type: 'value',
+ name: '鏁伴噺'
+ },
+ series: [
+ {
+ name: '鍣ㄥ畼鍒╃敤鏁伴噺',
+ type: 'bar',
+ data: [35, 30, 15, 10, 8, 2],
+ itemStyle: {
+ color: function(params) {
+ const colorList = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272'];
+ return colorList[params.dataIndex];
+ }
+ }
+ }
+ ]
+ };
+ this.organDistributionChart.setOption(option);
+ },
+
+ // 鑾峰彇鐘舵�佹枃鏈�
+ getStatusText() {
+ const status = this.stageData.status;
+ return status === 'completed' ? '宸插畬鎴�' :
+ status === 'in_progress' ? '杩涜涓�' : '鏈紑濮�';
+ },
+
+ // 鑾峰彇璀﹀憡绫诲瀷
+ getAlertType() {
+ const status = this.stageData.status;
+ return status === 'completed' ? 'success' :
+ status === 'in_progress' ? 'warning' : 'info';
+ },
+
+ // 鑾峰彇璀﹀憡鎻忚堪
+ getAlertDescription() {
+ const status = this.stageData.status;
+ if (status === 'completed') {
+ return '鍣ㄥ畼鍒╃敤闃舵宸插畬鎴愶紝鎵�鏈夊櫒瀹樺潎宸叉垚鍔熺Щ妞嶅苟寮�濮嬮殢璁�';
+ } else if (status === 'in_progress') {
+ return '鍣ㄥ畼鍒╃敤杩涜涓紝绉绘鎵嬫湳宸插畬鎴愶紝姝e湪杩涜鏈悗闅忚';
+ }
+ return '绛夊緟寮�濮嬪櫒瀹樺埄鐢ㄦ祦绋�';
+ },
+
+ // 鑾峰彇绉绘鐘舵�佹爣绛�
+ getTransplantStatusTag(status) {
+ const map = {
+ '绉绘鎴愬姛': 'success',
+ '绉绘涓�': 'warning',
+ '绉绘澶辫触': 'danger'
+ };
+ return map[status] || 'info';
+ },
+
+ // 鑾峰彇鍙椾綋鐘舵�佹爣绛�
+ getRecipientStatusTag(status) {
+ const map = {
+ '鎭㈠鑹ソ': 'success',
+ '绋冲畾鎭㈠': 'warning',
+ '瀵嗗垏瑙傚療': 'danger'
+ };
+ return map[status] || 'info';
+ },
+
+ // 鏌ョ湅璇︽儏
+ handleViewDetails(row) {
+ this.$message.info(`鏌ョ湅${row.organName}绉绘璇︽儏`);
+ },
+
+ // 娣诲姞闅忚
+ handleAddFollowup(row) {
+ this.$message.info(`涓�${row.recipientName}娣诲姞闅忚璁板綍`);
+ },
+
+ // 鐢熸垚鎶ュ憡
+ handleGenerateReport() {
+ this.$message.info('鐢熸垚鍣ㄥ畼鍒╃敤鍒嗘瀽鎶ュ憡');
+ },
+
+ // 瀹屾垚鍒╃敤
+ handleCompleteUtilization() {
+ this.$confirm('纭瀹屾垚鍣ㄥ畼鍒╃敤闃舵鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'success'
+ }).then(() => {
+ this.$message.success('鍣ㄥ畼鍒╃敤闃舵宸插畬鎴�');
+ });
+ },
+
+ // 缁熻鏁版嵁鍒嗘瀽
+ handleStatistics() {
+ this.$message.info('鎵撳紑缁熻鍒嗘瀽闈㈡澘');
+ },
+
+ // 瀵煎嚭鏁版嵁
+ exportData() {
+ this.$message.info('瀵煎嚭鍣ㄥ畼鍒╃敤鏁版嵁');
+ }
+ }
+};
+</script>
+
+<style scoped>
+.utilization-overview {
+ padding: 10px 0;
+}
+
+.overview-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.overview-icon {
+ font-size: 24px;
+ margin-right: 15px;
+}
+
+.overview-content .value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #303133;
+}
+
+.overview-content .label {
+ font-size: 12px;
+ color: #909399;
+}
+
+.success-stats, .time-tracking, .quality-assessment {
+ padding: 10px 0;
+}
+
+.success-item, .time-item, .quality-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.success-item .label, .time-item .label, .quality-item .label {
+ color: #606266;
+ font-size: 14px;
+ min-width: 80px;
+}
+
+.time-item .value {
+ font-weight: 600;
+ color: #409EFF;
+}
+
+.chart-container {
+ position: relative;
+ min-height: 300px;
+}
+
+.action-buttons {
+ display: flex;
+ justify-content: center;
+ gap: 15px;
+ margin-top: 20px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+</style>
diff --git a/src/views/business/course/donationProcess.js b/src/views/business/course/donationProcess.js
new file mode 100644
index 0000000..a70096e
--- /dev/null
+++ b/src/views/business/course/donationProcess.js
@@ -0,0 +1,453 @@
+// 妯℃嫙鎹愮尞杩涚▼鏁版嵁
+const mockDonationProcessData = {
+ caseInfo: {
+ id: '202312001',
+ caseNo: 'C202312001',
+ hospitalNo: 'D202312001',
+ donorName: '寮犱笁',
+ gender: '0',
+ age: 45,
+ bloodType: 'A',
+ diagnosis: '鑴戝浼�',
+ status: 'in_progress',
+ createTime: '2023-12-01 08:00:00',
+ registrant: '鏉庡崗璋冨憳',
+ currentStage: 'organ_allocation',
+ // 鏂板鍩烘湰淇℃伅
+ height: 175,
+ weight: 70,
+ bloodPressure: '120/80',
+ contactPerson: '寮犵埗',
+ contactPhone: '13800138000',
+ hospital: '鍖椾含鍗忓拰鍖婚櫌',
+ department: '绁炵粡澶栫',
+ attendingDoctor: '鐜嬩富浠�'
+ },
+ processStages: [
+ {
+ key: 'donor_maintenance',
+ name: '渚涜�呯淮鎶�',
+ status: 'completed',
+ completeTime: '2023-12-01 10:00:00',
+ updateTime: '2023-12-01 10:00:00',
+ operator: '寮犲尰鐢�',
+ details: {
+ maintenanceRecords: 5,
+ lastCheckup: '2023-12-01 09:30:00',
+ vitalSigns: {
+ heartRate: 75,
+ bloodPressure: '118/76',
+ temperature: 36.5,
+ oxygenSaturation: 98
+ },
+ medications: [
+ { name: '澶氬反鑳�', dosage: '5渭g/kg/min', time: '2023-12-01 08:00:00' },
+ { name: '鐢橀湶閱�', dosage: '125ml', time: '2023-12-01 09:00:00' }
+ ],
+ labResults: {
+ wbc: 8.5,
+ hgb: 12.5,
+ plt: 250,
+ na: 140,
+ k: 4.0
+ }
+ }
+ },
+ {
+ key: 'medical_assessment',
+ name: '鍖诲璇勪及',
+ status: 'completed',
+ completeTime: '2023-12-02 14:30:00',
+ updateTime: '2023-12-02 14:30:00',
+ operator: '鏉庝富浠�',
+ details: {
+ assessmentItems: [
+ { name: '绁炵粡绯荤粺璇勪及', result: '鑴戞浜$‘璁�', status: 'completed' },
+ { name: '蹇冭绠$郴缁熻瘎浼�', result: '鍔熻兘姝e父', status: 'completed' },
+ { name: '鍛煎惛绯荤粺璇勪及', result: '鍛煎惛鏈虹淮鎸�', status: 'completed' },
+ { name: '鑲濊偩鍔熻兘璇勪及', result: '鍔熻兘鑹ソ', status: 'completed' },
+ { name: '鎰熸煋鎬х柧鐥呯瓫鏌�', result: '闃存��', status: 'completed' }
+ ],
+ imagingResults: {
+ ctBrain: '鑴戞按鑲匡紝鑴戝共鍙嶅皠娑堝け',
+ chestXRay: '鍙岃偤娓呮櫚',
+ abdominalUS: '鑲濊儐鑳拌劸鏈寮傚父'
+ },
+ conclusion: '绗﹀悎鍣ㄥ畼鎹愮尞鍖诲鏍囧噯',
+ contraindications: '鏃犵粷瀵圭蹇岀棁'
+ }
+ },
+ {
+ key: 'death_judgment',
+ name: '姝讳骸鍒ゅ畾',
+ status: 'completed',
+ completeTime: '2023-12-03 09:15:00',
+ updateTime: '2023-12-03 09:15:00',
+ operator: '鐜嬪尰鐢�',
+ details: {
+ judgmentType: '鑴戞浜″垽瀹�',
+ judgmentTime: '2023-12-03 09:00:00',
+ doctors: ['寮犱富浠�', '鐜嬪尰鐢�'],
+ testResults: [
+ { test: '鑷富鍛煎惛娴嬭瘯', result: '鏃犺嚜涓诲懠鍚�', time: '2023-12-03 08:30:00' },
+ { test: '鐬冲瓟瀵瑰厜鍙嶅皠', result: '鍙嶅皠娑堝け', time: '2023-12-03 08:45:00' },
+ { test: '鑴戝共鍚璇卞彂鐢典綅', result: '鑴戝共鍔熻兘涓уけ', time: '2023-12-03 09:00:00' }
+ ],
+ certificateNo: 'SW20231203001',
+ legalDocuments: ['姝讳骸璇佹槑涔�', '鑴戞浜″垽瀹氫功']
+ }
+ },
+ {
+ key: 'donation_confirm',
+ name: '鎹愮尞纭',
+ status: 'completed',
+ completeTime: '2023-12-03 11:00:00',
+ updateTime: '2023-12-03 11:00:00',
+ operator: '璧靛崗璋冨憳',
+ details: {
+ familyConsent: {
+ mainRelative: '寮犵埗',
+ relationship: '鐖跺瓙',
+ consentTime: '2023-12-03 10:45:00',
+ consentForm: '宸茬缃�',
+ witness: '鏉庢姢澹�'
+ },
+ donationType: '澶氬櫒瀹樻崘鐚�',
+ organs: ['鑲濊剰', '鑲捐剰', '蹇冭剰', '瑙掕啘'],
+ legalDocuments: [
+ '鍣ㄥ畼鎹愮尞鍚屾剰涔�',
+ '瀹跺睘鍏崇郴璇佹槑',
+ '鍖荤枟鍏嶈矗澹版槑'
+ ],
+ coordinator: '璧靛崗璋冨憳',
+ confirmationTime: '2023-12-03 11:00:00'
+ }
+ },
+ {
+ key: 'ethical_review',
+ name: '浼︾悊瀹℃煡',
+ status: 'completed',
+ completeTime: '2023-12-03 15:20:00',
+ updateTime: '2023-12-03 15:20:00',
+ operator: '浼︾悊濮斿憳浼�',
+ details: {
+ committee: '鍖婚櫌浼︾悊瀹℃煡濮斿憳浼�',
+ meetingTime: '2023-12-03 14:00:00',
+ members: ['寮犳暀鎺�', '鏉庝富浠�', '鐜嬪尰鐢�', '璧靛鍛�', '閽变笓瀹�'],
+ reviewItems: [
+ { item: '鎹愮尞鎰忔効鐪熷疄鎬�', result: '纭鐪熷疄', vote: '鍏ㄧエ閫氳繃' },
+ { item: '鍖诲璇勪及鍑嗙‘鎬�', result: '纭鍑嗙‘', vote: '鍏ㄧエ閫氳繃' },
+ { item: '娉曞緥鏂囦欢瀹屾暣鎬�', result: '纭瀹屾暣', vote: '鍏ㄧエ閫氳繃' }
+ ],
+ conclusion: '绗﹀悎浼︾悊瑕佹眰锛屽悓鎰忚繘琛屽櫒瀹樻崘鐚�',
+ resolutionNo: 'LL20231203001'
+ }
+ },
+ {
+ key: 'organ_allocation',
+ name: '鍣ㄥ畼鍒嗛厤',
+ status: 'in_progress',
+ updateTime: '2023-12-04 10:00:00',
+ operator: '鍒嗛厤绯荤粺',
+ details: {
+ allocationStartTime: '2023-12-04 09:00:00',
+ allocationSystem: '涓浗浜轰綋鍣ㄥ畼鍒嗛厤涓庡叡浜绠楁満绯荤粺',
+ organs: [
+ {
+ organ: '鑲濊剰',
+ status: '鍒嗛厤涓�',
+ matchScore: 95,
+ recommendedRecipient: '鐜嬪厛鐢�',
+ recipientAge: 45,
+ recipientBloodType: 'A',
+ hospital: '鍖椾含鍗忓拰鍖婚櫌',
+ urgency: '绱ф��'
+ },
+ {
+ organ: '鑲捐剰',
+ status: '鍖归厤瀹屾垚',
+ matchScore: 92,
+ recommendedRecipient: '鏉庡コ澹�',
+ recipientAge: 38,
+ recipientBloodType: 'A',
+ hospital: '涓婃捣鐟為噾鍖婚櫌',
+ urgency: '楂�'
+ },
+ {
+ organ: '蹇冭剰',
+ status: '寰呭垎閰�',
+ matchScore: 88,
+ recommendedRecipient: '闄堝厛鐢�',
+ recipientAge: 52,
+ recipientBloodType: 'O',
+ hospital: '骞垮窞涓北鍖婚櫌',
+ urgency: '绱ф��'
+ }
+ ],
+ allocationFactors: [
+ { factor: '鐥呮儏鍗遍噸绋嬪害', weight: 35 },
+ { factor: '缁勭粐閰嶅瀷鍖归厤', weight: 25 },
+ { factor: '绛夊緟鏃堕棿', weight: 15 },
+ { factor: '鍦扮悊鍥犵礌', weight: 10 },
+ { factor: '骞撮緞鍥犵礌', weight: 15 }
+ ]
+ }
+ },
+ {
+ key: 'organ_procurement',
+ name: '鍣ㄥ畼鑾峰彇',
+ status: 'pending',
+ updateTime: '2023-12-03 16:00:00',
+ operator: '寰呭垎閰�',
+ details: {
+ scheduledTime: '2023-12-04 14:00:00',
+ operationRoom: '鎵嬫湳瀹や竴鍙�',
+ surgicalTeam: {
+ surgeon: '寰呭垎閰�',
+ assistant: '寰呭垎閰�',
+ anesthesiologist: '寰呭垎閰�',
+ nurse: '寰呭垎閰�'
+ },
+ preservationPlan: {
+ method: '浣庢俯鏈烘鐏屾敞',
+ solution: 'UW淇濆瓨娑�',
+ temperature: '4掳C'
+ },
+ organs: [
+ {
+ organ: '鑲濊剰',
+ planned: true,
+ preservation: '寰呭噯澶�',
+ estimatedTime: '4灏忔椂'
+ },
+ {
+ organ: '鑲捐剰',
+ planned: true,
+ preservation: '寰呭噯澶�',
+ estimatedTime: '3灏忔椂'
+ },
+ {
+ organ: '蹇冭剰',
+ planned: true,
+ preservation: '寰呭噯澶�',
+ estimatedTime: '5灏忔椂'
+ }
+ ]
+ }
+ },
+ {
+ key: 'organ_utilization',
+ name: '鍣ㄥ畼鍒╃敤',
+ status: 'pending',
+ updateTime: '2023-12-03 16:00:00',
+ operator: '寰呭垎閰�',
+ details: {
+ transplantCenters: [
+ {
+ hospital: '鍖椾含鍗忓拰鍖婚櫌',
+ organ: '鑲濊剰',
+ recipient: '鐜嬪厛鐢�',
+ scheduledTime: '2023-12-04 18:00:00',
+ surgicalTeam: '寰呯‘璁�'
+ },
+ {
+ hospital: '涓婃捣鐟為噾鍖婚櫌',
+ organ: '鑲捐剰',
+ recipient: '鏉庡コ澹�',
+ scheduledTime: '2023-12-04 19:00:00',
+ surgicalTeam: '寰呯‘璁�'
+ }
+ ],
+ followupPlan: {
+ frequency: '鏈悗1涓湀銆�3涓湀銆�6涓湀銆�1骞�',
+ items: ['鑲濆姛鑳芥鏌�', '鍏嶇柅鎶戝埗鍓傛祿搴�', '褰卞儚瀛︽鏌�'],
+ coordinator: '寰呭垎閰�'
+ },
+ qualityMetrics: {
+ expectedSurvivalRate: 92,
+ complicationRisk: '涓瓑',
+ successRate: 95
+ }
+ }
+ }
+ ],
+ // 鏂板鏃堕棿绾夸簨浠�
+ timelineEvents: [
+ {
+ time: '2023-12-01 08:00:00',
+ event: '妗堜緥鐧昏',
+ operator: '鏉庡崗璋冨憳',
+ description: '鎹愮尞妗堜緥姝e紡鐧昏鍚姩'
+ },
+ {
+ time: '2023-12-01 10:00:00',
+ event: '渚涜�呯淮鎶ゅ畬鎴�',
+ operator: '寮犲尰鐢�',
+ description: '瀹屾垚渚涜�呯敓鍛戒綋寰佺淮鎶ゅ拰鍖荤枟绠$悊'
+ },
+ {
+ time: '2023-12-02 14:30:00',
+ event: '鍖诲璇勪及瀹屾垚',
+ operator: '鏉庝富浠�',
+ description: '鍏ㄩ潰鍖诲璇勪及纭绗﹀悎鎹愮尞鏍囧噯'
+ },
+ {
+ time: '2023-12-03 09:15:00',
+ event: '姝讳骸鍒ゅ畾瀹屾垚',
+ operator: '鐜嬪尰鐢�',
+ description: '鑴戞浜″垽瀹氱▼搴忓畬鎴�'
+ },
+ {
+ time: '2023-12-03 11:00:00',
+ event: '鎹愮尞纭瀹屾垚',
+ operator: '璧靛崗璋冨憳',
+ description: '瀹跺睘绛剧讲鎹愮尞鍚屾剰涔�'
+ },
+ {
+ time: '2023-12-03 15:20:00',
+ event: '浼︾悊瀹℃煡閫氳繃',
+ operator: '浼︾悊濮斿憳浼�',
+ description: '浼︾悊瀹℃煡濮斿憳浼氬叏绁ㄩ�氳繃'
+ }
+ ],
+ // 鏂板缁熻淇℃伅
+ statistics: {
+ totalStages: 8,
+ completedStages: 5,
+ completionRate: 62.5,
+ timeElapsed: '2澶�6灏忔椂',
+ estimatedCompletion: '2023-12-04 20:00:00',
+ organsToDonate: 4,
+ potentialRecipients: 3
+ }
+};
+
+// 鑾峰彇鎹愮尞杩涚▼璇︽儏
+export const getDonationProcessDetail = async (caseId) => {
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // 妯℃嫙鏍规嵁caseId杩斿洖涓嶅悓鏁版嵁
+ const data = JSON.parse(JSON.stringify(mockDonationProcessData));
+ data.caseInfo.id = caseId;
+
+ return {
+ code: 200,
+ message: 'success',
+ data: data
+ };
+};
+
+// 鏇存柊闃舵鐘舵��
+export const updateStageStatus = async (caseId, stageKey, status) => {
+ await new Promise(resolve => setTimeout(resolve, 300));
+
+ // 妯℃嫙鏇存柊閫昏緫
+ const stage = mockDonationProcessData.processStages.find(s => s.key === stageKey);
+ if (stage) {
+ stage.status = status;
+ stage.updateTime = new Date().toISOString().replace('T', ' ').substring(0, 19);
+
+ if (status === 'completed') {
+ stage.completeTime = stage.updateTime;
+ }
+ }
+
+ return {
+ code: 200,
+ message: '闃舵鐘舵�佹洿鏂版垚鍔�',
+ data: {
+ caseId,
+ stageKey,
+ status,
+ updateTime: stage.updateTime
+ }
+ };
+};
+
+// 鑾峰彇闃舵璇︽儏
+export const getStageDetail = async (caseId, stageKey) => {
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ const stage = mockDonationProcessData.processStages.find(s => s.key === stageKey);
+ if (!stage) {
+ return {
+ code: 404,
+ message: '闃舵涓嶅瓨鍦�',
+ data: null
+ };
+ }
+
+ return {
+ code: 200,
+ message: 'success',
+ data: stage
+ };
+};
+
+// 鑾峰彇鏃堕棿绾夸簨浠�
+export const getTimelineEvents = async (caseId) => {
+ await new Promise(resolve => setTimeout(resolve, 150));
+
+ return {
+ code: 200,
+ message: 'success',
+ data: mockDonationProcessData.timelineEvents
+ };
+};
+
+// 鑾峰彇妗堜緥缁熻淇℃伅
+export const getCaseStatistics = async (caseId) => {
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ return {
+ code: 200,
+ message: 'success',
+ data: mockDonationProcessData.statistics
+ };
+};
+
+// 鎻愪氦闃舵瀹℃牳
+export const submitStageReview = async (caseId, stageKey, reviewData) => {
+ await new Promise(resolve => setTimeout(resolve, 400));
+
+ return {
+ code: 200,
+ message: '瀹℃牳鎻愪氦鎴愬姛',
+ data: {
+ caseId,
+ stageKey,
+ reviewId: `REV${Date.now()}`,
+ submitTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ ...reviewData
+ }
+ };
+};
+
+// 涓婁紶闃舵鏂囦欢
+export const uploadStageFile = async (caseId, stageKey, fileInfo) => {
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ return {
+ code: 200,
+ message: '鏂囦欢涓婁紶鎴愬姛',
+ data: {
+ caseId,
+ stageKey,
+ fileId: `FILE${Date.now()}`,
+ fileName: fileInfo.name,
+ fileSize: fileInfo.size,
+ uploadTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ url: `/files/${caseId}/${stageKey}/${fileInfo.name}`
+ }
+ };
+};
+
+export default {
+ getDonationProcessDetail,
+ updateStageStatus,
+ getStageDetail,
+ getTimelineEvents,
+ getCaseStatistics,
+ submitStageReview,
+ uploadStageFile
+};
diff --git a/src/views/business/course/index.vue b/src/views/business/course/index.vue
new file mode 100644
index 0000000..db767b5
--- /dev/null
+++ b/src/views/business/course/index.vue
@@ -0,0 +1,677 @@
+<template>
+ <div class="donation-process-detail">
+ <el-card class="process-card">
+ <div class="process-container">
+ <!-- 宸︿晶鏃堕棿绾� -->
+ <div class="timeline-section">
+ <div class="section-header">
+ <h3>鎹愮尞杩涚▼鏃堕棿绾�</h3>
+ <el-tag :type="getOverallStatusTag(caseInfo.status)">
+ {{ getStatusText(caseInfo.status) }}
+ </el-tag>
+ </div>
+
+ <div class="timeline-container">
+ <div
+ v-for="stage in processStages"
+ :key="stage.key"
+ class="timeline-item"
+ :class="{
+ 'active': activeStage === stage.key,
+ 'completed': stage.status === 'completed',
+ 'in-progress': stage.status === 'in_progress',
+ 'pending': stage.status === 'pending'
+ }"
+ @click="handleStageClick(stage)"
+ >
+ <div class="timeline-marker">
+ <i v-if="stage.status === 'completed'" class="el-icon-check"></i>
+ <i v-else-if="stage.status === 'in_progress'" class="el-icon-loading"></i>
+ <i v-else class="el-icon-time"></i>
+ </div>
+
+ <div class="timeline-content">
+ <div class="stage-header">
+ <span class="stage-name">{{ stage.name }}</span>
+ <el-tag
+ size="small"
+ :type="getStageStatusTag(stage.status)"
+ >
+ {{ getStageStatusText(stage.status) }}
+ </el-tag>
+ </div>
+
+ <div class="stage-info">
+ <div v-if="stage.completeTime" class="time-info">
+ <span>瀹屾垚鏃堕棿: {{ formatTime(stage.completeTime) }}</span>
+ </div>
+ <div v-if="stage.updateTime" class="time-info">
+ <span>鏈�杩戞洿鏂�: {{ formatTime(stage.updateTime) }}</span>
+ </div>
+ <div v-if="stage.operator" class="operator-info">
+ <span>璐熻矗浜�: {{ stage.operator }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鍙充晶鍐呭鍖哄煙 -->
+ <div class="content-section">
+ <!-- 妗堜緥鍩烘湰淇℃伅 -->
+ <div class="basic-info-section">
+ <div class="section-header">
+ <h3>妗堜緥鍩烘湰淇℃伅</h3>
+ <el-button
+ type="primary"
+ size="small"
+ @click="handleEditBasicInfo"
+ >
+ 缂栬緫淇℃伅
+ </el-button>
+ </div>
+
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="妗堜緥缂栧彿">
+ {{ caseInfo.caseNo }}
+ </el-descriptions-item>
+ <el-descriptions-item label="浣忛櫌鍙�">
+ {{ caseInfo.hospitalNo }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎹愮尞鑰呭鍚�">
+ {{ caseInfo.donorName }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎬у埆">
+ <dict-tag
+ :options="dict.type.sys_user_sex"
+ :value="parseInt(caseInfo.gender)"
+ />
+ </el-descriptions-item>
+ <el-descriptions-item label="骞撮緞">
+ {{ caseInfo.age }} 宀�
+ </el-descriptions-item>
+ <el-descriptions-item label="琛�鍨�">
+ <dict-tag
+ :options="dict.type.sys_BloodType"
+ :value="caseInfo.bloodType"
+ />
+ </el-descriptions-item>
+ <el-descriptions-item label="鐤剧梾璇婃柇">
+ {{ caseInfo.diagnosis }}
+ </el-descriptions-item>
+ <el-descriptions-item label="妗堜緥鐘舵��">
+ <el-tag :type="getOverallStatusTag(caseInfo.status)">
+ {{ getStatusText(caseInfo.status) }}
+ </el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鍒涘缓鏃堕棿">
+ {{ formatTime(caseInfo.createTime) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鐧昏浜�">
+ {{ caseInfo.registrant }}
+ </el-descriptions-item>
+ <el-descriptions-item label="褰撳墠闃舵">
+ {{ getCurrentStageName() }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </div>
+
+ <!-- 闃舵璇︽儏鍐呭 -->
+ <div class="stage-detail-section">
+ <div class="section-header">
+ <h3>{{ activeStageName }} - 闃舵璇︽儏</h3>
+ <div class="stage-actions">
+ <el-button
+ v-if="activeStageData.status !== 'completed'"
+ type="success"
+ size="small"
+ @click="handleCompleteStage"
+ >
+ 瀹屾垚闃舵
+ </el-button>
+ <el-button
+ type="primary"
+ size="small"
+ @click="handleViewDetail"
+ >
+ 鏌ョ湅璇︽儏
+ </el-button>
+ <el-button
+ v-if="activeStageData.status === 'completed'"
+ type="warning"
+ size="small"
+ @click="handleModifyStage"
+ >
+ 淇敼淇℃伅
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 鍔ㄦ�侀樁娈靛唴瀹� -->
+ <div class="stage-content">
+ <component
+ :is="getStageComponent()"
+ :stageData="activeStageData"
+ :caseInfo="caseInfo"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script>
+import { getDonationProcessDetail } from './donationProcess';
+import DonorMaintenanceStage from './components/DonorMaintenanceStage';
+import MedicalAssessmentStage from './components/MedicalAssessmentStage';
+import DeathJudgmentStage from './components/DeathJudgmentStage';
+import DonationConfirmStage from './components/DonationConfirmStage';
+import EthicalReviewStage from './components/EthicalReviewStage';
+import OrganAllocationStage from './components/OrganAllocationStage';
+import OrganProcurementStage from './components/OrganProcurementStage';
+import OrganUtilizationStage from './components/OrganUtilizationStage';
+import dayjs from "dayjs";
+
+export default {
+ name: 'DonationProcessDetail',
+ components: {
+ DonorMaintenanceStage,
+ MedicalAssessmentStage,
+ DeathJudgmentStage,
+ DonationConfirmStage,
+ EthicalReviewStage,
+ OrganAllocationStage,
+ OrganProcurementStage,
+ OrganUtilizationStage
+ },
+ dicts: ['sys_user_sex', 'sys_BloodType', 'sys_0_1'],
+ data() {
+ return {
+ caseId: null,
+ caseInfo: {
+ id: '',
+ caseNo: '',
+ hospitalNo: '',
+ donorName: '',
+ gender: '',
+ age: '',
+ bloodType: '',
+ diagnosis: '',
+ status: 'in_progress',
+ createTime: '',
+ registrant: '',
+ currentStage: 'donor_maintenance'
+ },
+ processStages: [
+ {
+ key: 'donor_maintenance',
+ name: '渚涜�呯淮鎶�',
+ status: 'completed',
+ completeTime: '2023-12-01 10:00:00',
+ updateTime: '2023-12-01 10:00:00',
+ operator: '寮犲尰鐢�'
+ },
+ {
+ key: 'medical_assessment',
+ name: '鍖诲璇勪及',
+ status: 'completed',
+ completeTime: '2023-12-02 14:30:00',
+ updateTime: '2023-12-02 14:30:00',
+ operator: '鏉庝富浠�'
+ },
+ {
+ key: 'death_judgment',
+ name: '姝讳骸鍒ゅ畾',
+ status: 'completed',
+ completeTime: '2023-12-03 09:15:00',
+ updateTime: '2023-12-03 09:15:00',
+ operator: '鐜嬪尰鐢�'
+ },
+ {
+ key: 'donation_confirm',
+ name: '鎹愮尞纭',
+ status: 'completed',
+ completeTime: '2023-12-03 11:00:00',
+ updateTime: '2023-12-03 11:00:00',
+ operator: '璧靛崗璋冨憳'
+ },
+ {
+ key: 'ethical_review',
+ name: '浼︾悊瀹℃煡',
+ status: 'completed',
+ completeTime: '2023-12-03 15:20:00',
+ updateTime: '2023-12-03 15:20:00',
+ operator: '浼︾悊濮斿憳浼�'
+ },
+ {
+ key: 'organ_allocation',
+ name: '鍣ㄥ畼鍒嗛厤',
+ status: 'in_progress',
+ updateTime: '2023-12-04 10:00:00',
+ operator: '鍒嗛厤绯荤粺'
+ },
+ {
+ key: 'organ_procurement',
+ name: '鍣ㄥ畼鑾峰彇',
+ status: 'pending',
+ operator: '寰呭垎閰�'
+ },
+ {
+ key: 'organ_utilization',
+ name: '鍣ㄥ畼鍒╃敤',
+ status: 'pending',
+ operator: '寰呭垎閰�'
+ }
+ ],
+ activeStage: 'organ_allocation',
+ activeStageName: '鍣ㄥ畼鍒嗛厤',
+ activeStageData: {},
+ loading: false
+ };
+ },
+ computed: {
+
+ },
+ created() {
+ this.caseId = this.$route.query.id;
+ if (this.caseId) {
+ this.getDetail();
+ } else {
+ this.generateMockData();
+ }
+ this.setActiveStage(this.activeStage);
+ },
+ methods: {
+ getStageComponent() {
+ const componentMap = {
+ 'donor_maintenance': 'DonorMaintenanceStage',
+ 'medical_assessment': 'MedicalAssessmentStage',
+ 'death_judgment': 'DeathJudgmentStage',
+ 'donation_confirm': 'DonationConfirmStage',
+ 'ethical_review': 'EthicalReviewStage',
+ 'organ_allocation': 'OrganAllocationStage',
+ 'organ_procurement': 'OrganProcurementStage',
+ 'organ_utilization': 'OrganUtilizationStage'
+ };
+ return componentMap[this.activeStage];
+ },
+ // 鑾峰彇璇︽儏鏁版嵁
+ async getDetail() {
+ this.loading = true;
+ try {
+ const response = await getDonationProcessDetail(this.caseId);
+ if (response.code === 200) {
+ this.caseInfo = response.data.caseInfo;
+ this.processStages = response.data.processStages;
+ this.setActiveStage(response.data.currentStage);
+ }
+ } catch (error) {
+ console.error('鑾峰彇鎹愮尞杩涚▼璇︽儏澶辫触:', error);
+ this.$message.error('鑾峰彇璇︽儏澶辫触');
+ } finally {
+ this.loading = false;
+ }
+ },
+ // 鐢熸垚妯℃嫙鏁版嵁
+ generateMockData() {
+ this.caseInfo = {
+ id: '202312001',
+ caseNo: 'C202312001',
+ hospitalNo: 'D202312001',
+ donorName: '寮犱笁',
+ gender: '0',
+ age: 45,
+ bloodType: 'A',
+ diagnosis: '鑴戝浼�',
+ status: 'in_progress',
+ createTime: '2023-12-01 08:00:00',
+ registrant: '鏉庡崗璋冨憳',
+ currentStage: 'organ_allocation'
+ };
+ },
+ // 璁剧疆褰撳墠婵�娲婚樁娈�
+ setActiveStage(stageKey) {
+ this.activeStage = stageKey;
+ const stage = this.processStages.find(s => s.key === stageKey);
+ if (stage) {
+ this.activeStageName = stage.name;
+ this.activeStageData = stage;
+ }
+ },
+ // 澶勭悊闃舵鐐瑰嚮
+ handleStageClick(stage) {
+ if (stage.status !== 'pending') {
+ this.setActiveStage(stage.key);
+ } else {
+ this.$message.warning('璇ラ樁娈靛皻鏈紑濮嬶紝鏃犳硶鏌ョ湅璇︽儏');
+ }
+ },
+ // 鑾峰彇闃舵鐘舵�佹爣绛剧被鍨�
+ getStageStatusTag(status) {
+ const map = {
+ 'completed': 'success',
+ 'in_progress': 'warning',
+ 'pending': 'info'
+ };
+ return map[status] || 'info';
+ },
+ // 鑾峰彇闃舵鐘舵�佹枃鏈�
+ getStageStatusText(status) {
+ const map = {
+ 'completed': '宸插畬鎴�',
+ 'in_progress': '杩涜涓�',
+ 'pending': '鏈紑濮�'
+ };
+ return map[status] || '鏈煡';
+ },
+ // 鑾峰彇鏁翠綋鐘舵�佹爣绛剧被鍨�
+ getOverallStatusTag(status) {
+ const map = {
+ 'completed': 'success',
+ 'in_progress': 'warning',
+ 'pending': 'info',
+ 'terminated': 'danger'
+ };
+ return map[status] || 'info';
+ },
+ // 鑾峰彇鏁翠綋鐘舵�佹枃鏈�
+ getStatusText(status) {
+ const map = {
+ 'completed': '宸插畬鎴�',
+ 'in_progress': '杩涜涓�',
+ 'pending': '鏈紑濮�',
+ 'terminated': '宸茬粓姝�'
+ };
+ return map[status] || '鏈煡';
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ formatTime(time) {
+ if (!time) return '-';
+ return dayjs(time).format('YYYY-MM-DD HH:mm');
+ },
+
+ // 鑾峰彇褰撳墠闃舵鍚嶇О
+ getCurrentStageName() {
+ const currentStage = this.processStages.find(
+ stage => stage.status === 'in_progress'
+ );
+ return currentStage ? currentStage.name : '宸插畬鎴�';
+ },
+ // 缂栬緫鍩烘湰淇℃伅
+ handleEditBasicInfo() {
+ this.$message.info('缂栬緫鍩烘湰淇℃伅鍔熻兘');
+ },
+ // 瀹屾垚闃舵
+ handleCompleteStage() {
+ this.$confirm(`纭畾瑕佸畬鎴愩��${this.activeStageName}銆戦樁娈靛悧锛焋, '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ // 鏇存柊褰撳墠闃舵鐘舵��
+ const currentIndex = this.processStages.findIndex(
+ stage => stage.key === this.activeStage
+ );
+
+ if (currentIndex !== -1) {
+ this.processStages[currentIndex].status = 'completed';
+ this.processStages[currentIndex].completeTime = new Date().toISOString();
+
+ // 婵�娲讳笅涓�涓樁娈�
+ if (currentIndex < this.processStages.length - 1) {
+ this.processStages[currentIndex + 1].status = 'in_progress';
+ this.setActiveStage(this.processStages[currentIndex + 1].key);
+ } else {
+ this.caseInfo.status = 'completed';
+ }
+
+ this.$message.success('闃舵宸插畬鎴�');
+ }
+ });
+ },
+ // 鏌ョ湅璇︽儏
+ handleViewDetail() {
+ const routeMap = {
+ 'donor_maintenance': '/case/donorMaintenance/detail',
+ 'medical_assessment': '/case/medicalAssessment/detail',
+ 'death_judgment': '/case/deathJudgment/detail',
+ 'donation_confirm': '/case/donationConfirm/detail',
+ 'ethical_review': '/case/ethicalReview/detail',
+ 'organ_allocation': '/case/organAllocation/detail',
+ 'organ_procurement': '/case/organProcurement/detail',
+ 'organ_utilization': '/case/organUtilization/detail'
+ };
+
+ const route = routeMap[this.activeStage];
+ if (route) {
+ this.$router.push({
+ path: route,
+ query: { id: this.caseId }
+ });
+ }
+ },
+ // 淇敼闃舵淇℃伅
+ handleModifyStage() {
+ this.$message.info(`淇敼${this.activeStageName}淇℃伅鍔熻兘`);
+ }
+ }
+};
+</script>
+
+<style scoped>
+.donation-process-detail {
+ padding: 20px;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.process-card {
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.process-container {
+ display: flex;
+ min-height: 800px;
+ gap: 20px;
+}
+
+/* 宸︿晶鏃堕棿绾挎牱寮� */
+.timeline-section {
+ flex: 0 0 300px;
+ background: white;
+ border-radius: 6px;
+ padding: 20px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid #e4e7ed;
+}
+
+.section-header h3 {
+ margin: 0;
+ color: #303133;
+ font-size: 16px;
+}
+
+.timeline-container {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.timeline-item {
+ display: flex;
+ align-items: flex-start;
+ padding: 15px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ border: 1px solid #e4e7ed;
+}
+
+.timeline-item:hover {
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ transform: translateY(-1px);
+}
+
+.timeline-item.active {
+ border-color: #409EFF;
+ background-color: #f0f9ff;
+}
+
+.timeline-item.completed {
+ border-color: #67C23A;
+ background-color: #f0f9e8;
+}
+
+.timeline-item.in-progress {
+ border-color: #E6A23C;
+ background-color: #fdf6ec;
+}
+
+.timeline-item.pending {
+ border-color: #909399;
+ background-color: #f4f4f5;
+}
+
+.timeline-marker {
+ flex: 0 0 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 15px;
+ font-size: 18px;
+ color: white;
+}
+
+.timeline-item.completed .timeline-marker {
+ background-color: #67C23A;
+}
+
+.timeline-item.in-progress .timeline-marker {
+ background-color: #E6A23C;
+}
+
+.timeline-item.pending .timeline-marker {
+ background-color: #909399;
+}
+
+.timeline-content {
+ flex: 1;
+}
+
+.stage-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.stage-name {
+ font-weight: 600;
+ color: #303133;
+ font-size: 14px;
+}
+
+.stage-info {
+ font-size: 12px;
+ color: #606266;
+}
+
+.time-info, .operator-info {
+ margin-bottom: 4px;
+}
+
+/* 鍙充晶鍐呭鍖哄煙鏍峰紡 */
+.content-section {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.basic-info-section,
+.stage-detail-section {
+ background: white;
+ border-radius: 6px;
+ padding: 20px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+
+.stage-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.stage-content {
+ margin-top: 20px;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 1200px) {
+ .process-container {
+ flex-direction: column;
+ }
+
+ .timeline-section {
+ flex: none;
+ margin-bottom: 20px;
+ }
+}
+
+@media (max-width: 768px) {
+ .donation-process-detail {
+ padding: 10px;
+ }
+
+ .process-container {
+ gap: 15px;
+ }
+
+ .timeline-section,
+ .basic-info-section,
+ .stage-detail-section {
+ padding: 15px;
+ }
+
+ .section-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .stage-actions {
+ flex-wrap: wrap;
+ }
+}
+
+/* 鍔ㄧ敾鏁堟灉 */
+.timeline-item {
+ transition: all 0.3s ease;
+}
+
+.timeline-item:hover {
+ transform: translateY(-2px);
+}
+
+/* 杩涘害鏉℃牱寮忎紭鍖� */
+:deep(.el-progress-bar) {
+ padding-right: 0;
+}
+
+:deep(.el-progress__text) {
+ font-size: 12px;
+}
+</style>
diff --git a/src/views/business/decide/DecideInfo.vue b/src/views/business/decide/DecideInfo.vue
new file mode 100644
index 0000000..927d786
--- /dev/null
+++ b/src/views/business/decide/DecideInfo.vue
@@ -0,0 +1,653 @@
+<template>
+ <div class="death-judgment-detail">
+ <el-card class="detail-card">
+ <!-- 鍩虹淇℃伅 -->
+ <div slot="header" class="clearfix">
+ <span class="detail-title">姝讳骸鍒ゅ畾鍩烘湰淇℃伅</span>
+ <el-button
+ v-if="isEdit"
+ type="primary"
+ style="float: right; padding: 3px 0"
+ @click="handleSave"
+ :loading="saveLoading"
+ >
+ 淇濆瓨淇℃伅
+ </el-button>
+ </div>
+
+ <el-form :model="form" ref="form" :rules="rules" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="妗堜緥缂栧彿" prop="hospitalNo">
+ <el-input
+ v-model="form.hospitalNo"
+ :readonly="!isEdit"
+ placeholder="鑷姩鐢熸垚 D+鏁板瓧"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎹愮尞鑰呭鍚�" prop="donorName">
+ <el-input v-model="form.donorName" :readonly="!isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎬у埆" prop="gender">
+ <el-select
+ v-model="form.gender"
+ :disabled="!isEdit"
+ style="width: 100%"
+ >
+ <el-option label="鐢�" value="0" />
+ <el-option label="濂�" value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="骞撮緞" prop="age">
+ <el-input v-model="form.age" :readonly="!isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鐤剧梾璇婃柇" prop="diagnosis">
+ <el-input v-model="form.diagnosis" :readonly="!isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="姝讳骸鍘熷洜" prop="deathReason">
+ <el-select
+ v-model="form.deathReason"
+ :disabled="!isEdit"
+ style="width: 100%"
+ >
+ <el-option label="鑴戞浜�" value="brain_death" />
+ <el-option label="蹇冩浜�" value="heart_death" />
+ <el-option label="鍏朵粬" value="other" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="姝讳骸鏃堕棿" prop="deathTime">
+ <el-date-picker
+ v-model="form.deathTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ :disabled="!isEdit"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鍒ゅ畾鍖荤敓" prop="judgmentDoctor">
+ <el-input v-model="form.judgmentDoctor" :readonly="!isEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鐧昏浜�" prop="registrant">
+ <el-input v-model="form.registrant" :readonly="!isEdit" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="姝讳骸鍒ゅ畾璇存槑" prop="judgmentDescription">
+ <el-input
+ type="textarea"
+ :rows="3"
+ v-model="form.judgmentDescription"
+ :readonly="!isEdit"
+ placeholder="璇︾粏璁板綍姝讳骸鍒ゅ畾杩囩▼鍜屼緷鎹�"
+ />
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <!-- 璇勪及琛ㄩ檮浠� -->
+ <el-card class="attachment-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">姝讳骸鍒ゅ畾璇勪及琛ㄩ檮浠�</span>
+ <el-button
+ v-if="isEdit"
+ type="primary"
+ size="mini"
+ @click="openUploadDialog"
+ :loading="uploadLoading"
+ >
+ 涓婁紶闄勪欢
+ </el-button>
+ </div>
+
+ <!-- 闄勪欢绫诲瀷閫夐」鍗� -->
+ <el-tabs v-model="activeAttachmentType" type="card">
+ <el-tab-pane
+ v-for="type in attachmentTypes"
+ :key="type.value"
+ :label="type.label"
+ :name="type.value"
+ >
+ <div class="attachment-upload-section">
+ <div class="upload-header">
+ <span class="upload-title">{{ type.label }}</span>
+ <el-tooltip content="鐐瑰嚮涓婁紶璇ョ被鍨嬭瘎浼拌〃" placement="top">
+ <el-button
+ size="mini"
+ type="primary"
+ icon="el-icon-plus"
+ @click="openUploadDialog(type.value)"
+ :disabled="!isEdit"
+ >
+ 娣诲姞璇勪及琛�
+ </el-button>
+ </el-tooltip>
+ </div>
+
+ <!-- 闄勪欢鍒楄〃 -->
+ <el-table
+ :data="getAttachmentsByType(type.value)"
+ v-loading="attachmentLoading"
+ style="width: 100%; margin-top: 15px;"
+ >
+ <el-table-column label="鏂囦欢鍚嶇О" min-width="200">
+ <template slot-scope="scope">
+ <div class="file-info">
+ <i
+ class="el-icon-document"
+ style="margin-right: 8px; color: #409EFF;"
+ ></i>
+ <span>{{ scope.row.fileName }}</span>
+ </div>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢绫诲瀷" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag size="small">{{ getFileType(scope.row.fileName) }}</el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢澶у皬" width="100" align="center">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓婁紶鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.uploadTime) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓婁紶浜�" width="100" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.uploader }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ label="鎿嶄綔"
+ width="180"
+ align="center"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handlePreview(scope.row)"
+ >棰勮</el-button
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-download"
+ @click="handleDownload(scope.row)"
+ >涓嬭浇</el-button
+ >
+ <el-button
+ v-if="isEdit"
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C;"
+ @click="handleRemoveAttachment(scope.row)"
+ >鍒犻櫎</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <div
+ v-if="getAttachmentsByType(type.value).length === 0"
+ class="empty-attachment"
+ >
+ <el-empty description="鏆傛棤璇勪及琛ㄩ檮浠�" :image-size="80"></el-empty>
+ </div>
+ </div>
+ </el-tab-pane>
+ </el-tabs>
+ </el-card>
+
+ <!-- 涓婁紶瀵硅瘽妗� -->
+ <el-dialog
+ :title="`涓婁紶${getCurrentTypeLabel}璇勪及琛╜"
+ :visible.sync="uploadDialogVisible"
+ width="500px"
+ :close-on-click-modal="false"
+ >
+ <el-upload
+ ref="uploadRef"
+ class="upload-demo"
+ drag
+ action="#"
+ multiple
+ :file-list="tempFileList"
+ :before-upload="beforeUpload"
+ :on-change="handleFileChange"
+ :on-remove="handleTempRemove"
+ :auto-upload="false"
+ >
+ <i class="el-icon-upload"></i>
+ <div class="el-upload__text">灏嗚瘎浼拌〃鏂囦欢鎷栧埌姝ゅ锛屾垨<em>鐐瑰嚮涓婁紶</em></div>
+ <div class="el-upload__tip" slot="tip">
+ 鏀寔涓婁紶pdf銆乯pg銆乸ng銆乨oc銆乨ocx銆亁ls銆亁lsx鏍煎紡鏂囦欢锛屽崟涓枃浠朵笉瓒呰繃10MB
+ </div>
+ </el-upload>
+
+ <div slot="footer" class="dialog-footer">
+ <el-button @click="uploadDialogVisible = false">鍙栨秷</el-button>
+ <el-button
+ type="primary"
+ @click="submitUpload"
+ :loading="uploadLoading"
+ :disabled="tempFileList.length === 0"
+ >
+ 纭涓婁紶
+ </el-button>
+ </div>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import { getDeathJudgmentDetail, updateDeathJudgment } from "./mockDeathJudgmentApi";
+
+export default {
+ name: "DeathJudgmentDetail",
+ data() {
+ return {
+ // 鏄惁缂栬緫妯″紡
+ isEdit: false,
+ // 淇濆瓨鍔犺浇鐘舵��
+ saveLoading: false,
+ // 琛ㄥ崟鏁版嵁
+ form: {
+ id: undefined,
+ hospitalNo: "",
+ donorName: "",
+ gender: "",
+ age: "",
+ diagnosis: "",
+ deathReason: "",
+ deathTime: "",
+ judgmentDoctor: "",
+ judgmentDescription: "",
+ registrant: "",
+ registrationTime: ""
+ },
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ rules: {
+ donorName: [
+ { required: true, message: "鎹愮尞鑰呭鍚嶄笉鑳戒负绌�", trigger: "blur" }
+ ],
+ deathReason: [
+ { required: true, message: "姝讳骸鍘熷洜涓嶈兘涓虹┖", trigger: "change" }
+ ],
+ deathTime: [
+ { required: true, message: "姝讳骸鏃堕棿涓嶈兘涓虹┖", trigger: "change" }
+ ],
+ judgmentDoctor: [
+ { required: true, message: "鍒ゅ畾鍖荤敓涓嶈兘涓虹┖", trigger: "blur" }
+ ]
+ },
+ // 闄勪欢鐩稿叧鏁版嵁
+ activeAttachmentType: "1",
+ attachmentLoading: false,
+ uploadDialogVisible: false,
+ uploadLoading: false,
+ tempFileList: [],
+ currentUploadType: "",
+
+ // 璇勪及琛ㄧ被鍨嬪畾涔�
+ attachmentTypes: [
+ { value: "1", label: "鑴戞浜″垽瀹氳〃" },
+ { value: "2", label: "鑴戠數鍥捐瘎浼拌〃" },
+ { value: "3", label: "鐭綔浼忔湡浣撴劅璇卞彂鐢典綅璇勪及琛�" },
+ { value: "4", label: "缁忛澶氭櫘鍕掕秴澹拌瘎浼拌褰�" },
+ { value: "5", label: "鍗仴濮旇剳鎹熶激璐ㄦ帶涓績 - 涓村簥缁煎悎璇勪及琛�" },
+ { value: "6", label: "UW璇勫垎琛�" },
+ { value: "7", label: "蹇冩浜″垽瀹氳〃" }
+ ],
+
+ // 闄勪欢鍒楄〃鏁版嵁
+ attachmentList: []
+ };
+ },
+ computed: {
+ getCurrentTypeLabel() {
+ const type = this.attachmentTypes.find(
+ t => t.value === this.currentUploadType
+ );
+ return type ? type.label : "";
+ }
+ },
+ created() {
+ const id = this.$route.query.id;
+ this.isEdit = this.$route.path.includes('/edit') || this.$route.path.includes('/add');
+ if (id && !this.$route.path.includes('/add')) {
+ this.getDetail(id);
+ } else if (this.$route.path.includes('/add')) {
+ this.generateHospitalNo();
+ }
+ this.getAttachmentList();
+ },
+ methods: {
+ // 鐢熸垚妗堜緥缂栧彿
+ generateHospitalNo() {
+ // 妯℃嫙鐢熸垚妗堜緥缂栧彿锛欴 + 鏃堕棿鎴冲悗6浣�
+ const timestamp = Date.now().toString();
+ this.form.hospitalNo = 'D' + timestamp.slice(-6);
+ },
+ // 鑾峰彇璇︽儏
+ getDetail(id) {
+ getDeathJudgmentDetail(id).then(response => {
+ if (response.code === 200) {
+ this.form = response.data;
+ }
+ });
+ },
+ // 鑾峰彇闄勪欢鍒楄〃
+ getAttachmentList() {
+ this.attachmentLoading = true;
+ // 妯℃嫙闄勪欢鏁版嵁 - 瀹為檯椤圭洰涓粠鎺ュ彛鑾峰彇
+ setTimeout(() => {
+ this.attachmentList = [
+ {
+ id: 1,
+ type: "1",
+ typeName: "鑴戞浜″垽瀹氳〃",
+ fileName: "鑴戞浜″垽瀹氳〃_202312001.pdf",
+ fileSize: 2548321,
+ uploadTime: "2023-12-01 10:30:00",
+ uploader: "寮犲尰鐢�",
+ fileUrl: "/attachments/brain_death_1.pdf"
+ },
+ {
+ id: 2,
+ type: "2",
+ typeName: "鑴戠數鍥捐瘎浼拌〃",
+ fileName: "鑴戠數鍥捐瘎浼拌〃_202312001.docx",
+ fileSize: 512345,
+ uploadTime: "2023-12-01 14:20:00",
+ uploader: "鏉庡尰鐢�",
+ fileUrl: "/attachments/eeg_1.docx"
+ }
+ ];
+ this.attachmentLoading = false;
+ }, 500);
+ },
+ // 鏍规嵁绫诲瀷鑾峰彇闄勪欢
+ getAttachmentsByType(type) {
+ return this.attachmentList.filter(item => item.type === type);
+ },
+ // 鑾峰彇鏂囦欢绫诲瀷
+ getFileType(fileName) {
+ const ext = fileName.split('.').pop().toLowerCase();
+ const typeMap = {
+ 'pdf': 'PDF',
+ 'doc': 'DOC',
+ 'docx': 'DOCX',
+ 'xls': 'XLS',
+ 'xlsx': 'XLSX',
+ 'jpg': 'JPG',
+ 'jpeg': 'JPEG',
+ 'png': 'PNG'
+ };
+ return typeMap[ext] || ext.toUpperCase();
+ },
+ // 鎵撳紑涓婁紶瀵硅瘽妗�
+ openUploadDialog(type = null) {
+ this.currentUploadType = type || this.activeAttachmentType;
+ this.tempFileList = [];
+ this.uploadDialogVisible = true;
+ this.$nextTick(() => {
+ if (this.$refs.uploadRef) {
+ this.$refs.uploadRef.clearFiles();
+ }
+ });
+ },
+ // 涓婁紶鍓嶆牎楠�
+ beforeUpload(file) {
+ const allowedTypes = [
+ 'application/pdf',
+ 'image/jpeg',
+ 'image/png',
+ 'application/msword',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.ms-excel',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ ];
+
+ const maxSize = 10 * 1024 * 1024; // 10MB
+
+ // 鏍¢獙鏂囦欢绫诲瀷
+ const isTypeOk = allowedTypes.includes(file.type) ||
+ file.name.endsWith('.pdf') ||
+ file.name.endsWith('.jpg') ||
+ file.name.endsWith('.jpeg') ||
+ file.name.endsWith('.png') ||
+ file.name.endsWith('.doc') ||
+ file.name.endsWith('.docx') ||
+ file.name.endsWith('.xls') ||
+ file.name.endsWith('.xlsx');
+
+ if (!isTypeOk) {
+ this.$message.error('鏂囦欢鏍煎紡涓嶆敮鎸侊紝璇蜂笂浼爌df銆乯pg銆乸ng銆乨oc銆乨ocx銆亁ls鎴杧lsx鏍煎紡鏂囦欢');
+ return false;
+ }
+
+ // 鏍¢獙鏂囦欢澶у皬
+ if (file.size > maxSize) {
+ this.$message.error('鏂囦欢澶у皬涓嶈兘瓒呰繃10MB');
+ return false;
+ }
+
+ return true;
+ },
+ // 鏂囦欢閫夋嫨鍙樺寲
+ handleFileChange(file, fileList) {
+ this.tempFileList = fileList;
+ },
+ // 绉婚櫎涓存椂鏂囦欢
+ handleTempRemove(file, fileList) {
+ this.tempFileList = fileList;
+ },
+ // 鎻愪氦涓婁紶
+ async submitUpload() {
+ if (this.tempFileList.length === 0) {
+ this.$message.warning('璇峰厛閫夋嫨瑕佷笂浼犵殑鏂囦欢');
+ return;
+ }
+
+ this.uploadLoading = true;
+
+ try {
+ // 妯℃嫙涓婁紶杩囩▼ - 瀹為檯椤圭洰涓皟鐢ㄤ笂浼犳帴鍙�
+ for (const file of this.tempFileList) {
+ const newAttachment = {
+ id: Date.now() + Math.random(),
+ type: this.currentUploadType,
+ typeName: this.getCurrentTypeLabel,
+ fileName: file.name,
+ fileSize: file.size,
+ uploadTime: new Date().toISOString(),
+ uploader: '褰撳墠鐢ㄦ埛',
+ fileUrl: URL.createObjectURL(file.raw)
+ };
+
+ this.attachmentList.push(newAttachment);
+ }
+
+ this.$message.success('鏂囦欢涓婁紶鎴愬姛');
+ this.uploadDialogVisible = false;
+ this.tempFileList = [];
+ } catch (error) {
+ this.$message.error('鏂囦欢涓婁紶澶辫触');
+ console.error('涓婁紶澶辫触:', error);
+ } finally {
+ this.uploadLoading = false;
+ }
+ },
+ // 鍒犻櫎闄勪欢
+ handleRemoveAttachment(attachment) {
+ this.$confirm('纭畾瑕佸垹闄よ繖涓瘎浼拌〃闄勪欢鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ const index = this.attachmentList.findIndex(item => item.id === attachment.id);
+ if (index !== -1) {
+ this.attachmentList.splice(index, 1);
+ this.$message.success('璇勪及琛ㄥ垹闄ゆ垚鍔�');
+ }
+ }).catch(() => {});
+ },
+ // 棰勮闄勪欢
+ handlePreview(attachment) {
+ if (attachment.fileName.endsWith('.pdf')) {
+ window.open(attachment.fileUrl, '_blank');
+ } else if (attachment.fileName.match(/\.(jpg|jpeg|png)$/i)) {
+ this.$alert(`<img src="${attachment.fileUrl}" style="max-width: 100%;" alt="${attachment.fileName}">`,
+ '鍥剧墖棰勮', {
+ dangerouslyUseHTMLString: true,
+ customClass: 'image-preview-dialog'
+ });
+ } else {
+ this.$message.info('璇ユ枃浠剁被鍨嬫殏涓嶆敮鎸佸湪绾块瑙堬紝璇蜂笅杞藉悗鏌ョ湅');
+ }
+ },
+ // 涓嬭浇闄勪欢
+ handleDownload(attachment) {
+ // 瀹為檯椤圭洰涓皟鐢ㄤ笅杞芥帴鍙�
+ const link = document.createElement('a');
+ link.href = attachment.fileUrl;
+ link.download = attachment.fileName;
+ link.click();
+ this.$message.success(`寮�濮嬩笅杞�: ${attachment.fileName}`);
+ },
+ // 淇濆瓨淇℃伅
+ handleSave() {
+ this.$refs.form.validate(valid => {
+ if (valid) {
+ this.saveLoading = true;
+
+ // 妯℃嫙淇濆瓨杩囩▼
+ updateDeathJudgment(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success('淇濆瓨鎴愬姛');
+ if (this.$route.path.includes('/add')) {
+ this.$router.push('/case/deathJudgment');
+ } else {
+ this.isEdit = false;
+ }
+ }
+ })
+ .catch(error => {
+ console.error('淇濆瓨澶辫触:', error);
+ this.$message.error('淇濆瓨澶辫触');
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ }
+ });
+ },
+ // 鏂囦欢澶у皬鏍煎紡鍖�
+ formatFileSize(size) {
+ if (size === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(size) / Math.log(k));
+ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time) {
+ if (!time) return '';
+ const date = new Date(time);
+ return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.death-judgment-detail {
+ padding: 20px;
+}
+
+.detail-card {
+ margin-bottom: 20px;
+}
+
+.attachment-card {
+ margin-bottom: 20px;
+}
+
+.detail-title {
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.attachment-upload-section {
+ padding: 10px;
+}
+
+.upload-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.upload-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.file-info {
+ display: flex;
+ align-items: center;
+}
+
+.empty-attachment {
+ text-align: center;
+ padding: 40px 0;
+ color: #909399;
+}
+
+/* 鍥剧墖棰勮瀵硅瘽妗嗘牱寮� */
+:deep(.image-preview-dialog) {
+ width: auto;
+ max-width: 90vw;
+}
+
+:deep(.image-preview-dialog .el-message-box__content) {
+ text-align: center;
+}
+</style>
diff --git a/src/views/business/decide/index.vue b/src/views/business/decide/index.vue
new file mode 100644
index 0000000..a285ce9
--- /dev/null
+++ b/src/views/business/decide/index.vue
@@ -0,0 +1,437 @@
+<template>
+ <div class="death-judgment-list">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-card class="search-card">
+ <el-form
+ :model="queryParams"
+ ref="queryForm"
+ :inline="true"
+ label-width="100px"
+ >
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input
+ v-model="queryParams.hospitalNo"
+ 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="deathReason">
+ <el-select
+ v-model="queryParams.deathReason"
+ placeholder="璇烽�夋嫨姝讳骸鍘熷洜"
+ clearable
+ style="width: 200px"
+ >
+ <el-option label="鑴戞浜�" value="brain_death" />
+ <el-option label="蹇冩浜�" value="heart_death" />
+ <el-option label="鍏朵粬" value="other" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="姝讳骸鏃堕棿鑼冨洿" prop="deathTimeRange">
+ <el-date-picker
+ v-model="queryParams.deathTimeRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ style="width: 240px"
+ />
+ </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-card class="tool-card">
+ <el-row :gutter="10">
+ <el-col :span="16">
+ <el-button type="primary" icon="el-icon-plus" @click="handleCreate"
+ >鏂板缓姝讳骸鍒ゅ畾</el-button
+ >
+ <el-button
+ type="success"
+ icon="el-icon-edit"
+ :disabled="single"
+ @click="handleUpdate"
+ >淇敼</el-button
+ >
+ <el-button
+ type="danger"
+ icon="el-icon-delete"
+ :disabled="multiple"
+ @click="handleDelete"
+ >鍒犻櫎</el-button
+ >
+ <el-button
+ type="warning"
+ icon="el-icon-download"
+ @click="handleExport"
+ >瀵煎嚭</el-button
+ >
+ </el-col>
+ <el-col :span="8" style="text-align: right">
+ <el-tooltip content="鍒锋柊" placement="top">
+ <el-button icon="el-icon-refresh" circle @click="getList" />
+ </el-tooltip>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-card>
+ <el-table
+ v-loading="loading"
+ :data="deathJudgmentList"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column
+ label="浣忛櫌鍙�"
+ align="center"
+ prop="hospitalNo"
+ width="120"
+ />
+ <el-table-column
+ label="鎹愮尞鑰呭鍚�"
+ align="center"
+ prop="donorName"
+ width="120"
+ />
+ <el-table-column label="鎬у埆" align="center" prop="gender" width="80">
+ <template slot-scope="scope">
+ <dict-tag
+ :options="dict.type.sys_user_sex"
+ :value="parseInt(scope.row.gender)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="骞撮緞" align="center" prop="age" width="80" />
+ <el-table-column
+ label="鐤剧梾璇婃柇"
+ align="center"
+ prop="diagnosis"
+ min-width="180"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="姝讳骸鍘熷洜"
+ align="center"
+ prop="deathReason"
+ width="120"
+ >
+ <template slot-scope="scope">
+ <el-tag :type="reasonFilter(scope.row.deathReason)">
+ {{ reasonTextFilter(scope.row.deathReason) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="姝讳骸鏃堕棿"
+ align="center"
+ prop="deathTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.deathTime
+ ? parseTime(scope.row.deathTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鍒ゅ畾鍖荤敓"
+ align="center"
+ prop="judgmentDoctor"
+ width="120"
+ />
+ <el-table-column
+ label="鐧昏鏃堕棿"
+ align="center"
+ prop="registrationTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.registrationTime
+ ? parseTime(scope.row.registrationTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鐧昏浜�"
+ align="center"
+ prop="registrant"
+ width="100"
+ />
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="180"
+ class-name="small-padding fixed-width"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleView(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-delete"
+ style="color: #F56C6C"
+ @click="handleDelete(scope.row)"
+ >鍒犻櫎</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-card>
+ </div>
+</template>
+
+<script>
+import { listDeathJudgment, delDeathJudgment, exportDeathJudgment } from "./mockDeathJudgmentApi";
+import Pagination from "@/components/Pagination";
+
+export default {
+ name: "DeathJudgmentList",
+ components: { Pagination },
+ dicts: ["sys_user_sex"],
+ data() {
+ return {
+ // 閬僵灞�
+ loading: true,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 姝讳骸鍒ゅ畾琛ㄦ牸鏁版嵁
+ deathJudgmentList: [],
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ hospitalNo: undefined,
+ donorName: undefined,
+ deathReason: undefined,
+ deathTimeRange: []
+ }
+ };
+ },
+ created() {
+ this.getList();
+ },
+ methods: {
+ // 姝讳骸鍘熷洜杩囨护鍣�
+ reasonFilter(reason) {
+ const reasonMap = {
+ "brain_death": "primary", // 鑴戞浜�
+ "heart_death": "danger", // 蹇冩浜�
+ "other": "info" // 鍏朵粬
+ };
+ return reasonMap[reason] || "info";
+ },
+ reasonTextFilter(reason) {
+ const reasonMap = {
+ "brain_death": "鑴戞浜�",
+ "heart_death": "蹇冩浜�",
+ "other": "鍏朵粬"
+ };
+ return reasonMap[reason] || "鏈煡";
+ },
+ // 鏌ヨ姝讳骸鍒ゅ畾鍒楄〃
+ getList() {
+ this.loading = true;
+ listDeathJudgment(this.queryParams)
+ .then(response => {
+ if (response.code === 200) {
+ this.deathJudgmentList = response.data.rows;
+ this.total = response.data.total;
+ } else {
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇姝讳骸鍒ゅ畾鍒楄〃澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ });
+ },
+ // 鎼滅储鎸夐挳鎿嶄綔
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ // 閲嶇疆鎸夐挳鎿嶄綔
+ resetQuery() {
+ this.$refs.queryForm.resetFields();
+ this.handleQuery();
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.id);
+ this.single = selection.length !== 1;
+ this.multiple = !selection.length;
+ },
+ // 鏌ョ湅璇︽儏
+ handleView(row) {
+ this.$router.push({
+ path: "/case/DecideInfo",
+ query: { id: row.id }
+ });
+ },
+ // 鏂板鎸夐挳鎿嶄綔
+ handleCreate() {
+ this.$router.push("/case/DecideInfo");
+ },
+ // 淇敼鎸夐挳鎿嶄綔
+ handleUpdate(row) {
+ const id = row.id || this.ids[0];
+ this.$router.push({
+ path: "/case/DecideInfo",
+ query: { id: id }
+ });
+ },
+ // 鍒犻櫎鎸夐挳鎿嶄綔
+ handleDelete(row) {
+ const ids = row.id ? [row.id] : this.ids;
+ this.$confirm("鏄惁纭鍒犻櫎閫変腑鐨勬暟鎹」锛�", "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return delDeathJudgment(ids);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒犻櫎鎴愬姛");
+ this.getList();
+ }
+ })
+ .catch(() => {});
+ },
+ // 瀵煎嚭鎸夐挳鎿嶄綔
+ handleExport() {
+ const queryParams = this.queryParams;
+ this.$confirm("鏄惁纭瀵煎嚭鎵�鏈夋浜″垽瀹氭暟鎹紵", "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.loading = true;
+ return exportDeathJudgment(queryParams);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("瀵煎嚭鎴愬姛");
+ // 瀹為檯椤圭洰涓繖閲屽鐞嗘枃浠朵笅杞�
+ }
+ this.loading = false;
+ })
+ .catch(() => {
+ this.loading = false;
+ });
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time, pattern) {
+ if (!time) return "";
+ const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
+ let date;
+ if (typeof time === "object") {
+ date = time;
+ } else {
+ if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+ time = parseInt(time);
+ }
+ if (typeof time === "number" && time.toString().length === 10) {
+ time = time * 1000;
+ }
+ date = new Date(time);
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ };
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key];
+ if (key === "a") {
+ return ["鏃�", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�"][value];
+ }
+ if (result.length > 0 && value < 10) {
+ value = "0" + value;
+ }
+ return value || 0;
+ });
+ return time_str;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.death-judgment-list {
+ padding: 20px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.tool-card {
+ margin-bottom: 20px;
+}
+
+.fixed-width .el-button {
+ margin: 0 5px;
+}
+</style>
diff --git a/src/views/business/decide/mockDeathJudgmentApi.js b/src/views/business/decide/mockDeathJudgmentApi.js
new file mode 100644
index 0000000..6fd13c9
--- /dev/null
+++ b/src/views/business/decide/mockDeathJudgmentApi.js
@@ -0,0 +1,391 @@
+// 妯℃嫙姝讳骸鍒ゅ畾鏁版嵁
+const mockDeathJudgmentData = [
+ {
+ id: 1,
+ hospitalNo: "D202312001",
+ donorName: "寮犱笁",
+ gender: "0",
+ age: 45,
+ diagnosis: "鑴戝浼�",
+ deathReason: "brain_death",
+ deathTime: "2023-12-01 14:30:00",
+ judgmentDoctor: "鐜嬪尰鐢�",
+ judgmentDescription: "缁忚繃澶氭鑴戞浜″垽瀹氾紝绗﹀悎鑴戞浜′复搴婅瘖鏂爣鍑�",
+ registrant: "鏉庡崗璋冨憳",
+ registrationTime: "2023-12-01 15:00:00",
+ createTime: "2023-12-01 10:00:00"
+ },
+ {
+ id: 2,
+ hospitalNo: "D202312002",
+ donorName: "鏉庡洓",
+ gender: "1",
+ age: 38,
+ diagnosis: "蹇冭剰楠ゅ仠",
+ deathReason: "heart_death",
+ deathTime: "2023-12-02 09:15:00",
+ judgmentDoctor: "鍒樺尰鐢�",
+ judgmentDescription: "蹇冩浜″垽瀹氾紝蹇冪數鍥惧憟鐩寸嚎锛屾棤鑷富鍛煎惛",
+ registrant: "寮犲崗璋冨憳",
+ registrationTime: "2023-12-02 10:00:00",
+ createTime: "2023-12-02 08:30:00"
+ },
+ {
+ id: 3,
+ hospitalNo: "D202312003",
+ donorName: "鐜嬩簲",
+ gender: "0",
+ age: 52,
+ diagnosis: "鑴戞姝�",
+ deathReason: "brain_death",
+ deathTime: "2023-12-03 16:45:00",
+ judgmentDoctor: "闄堝尰鐢�",
+ judgmentDescription: "鑴戝共鍔熻兘瀹屽叏涓уけ锛岀鍚堣剳姝讳骸鏍囧噯",
+ registrant: "璧靛崗璋冨憳",
+ registrationTime: "2023-12-03 17:20:00",
+ createTime: "2023-12-03 14:00:00"
+ },
+ {
+ id: 4,
+ hospitalNo: "D202312004",
+ donorName: "璧靛叚",
+ gender: "1",
+ age: 29,
+ diagnosis: "澶氬彂浼�",
+ deathReason: "other",
+ deathTime: "2023-12-04 11:20:00",
+ judgmentDoctor: "瀛欏尰鐢�",
+ judgmentDescription: "鍒涗激鎬т紤鍏嬪鑷村鍣ㄥ畼鍔熻兘琛扮",
+ registrant: "閽卞崗璋冨憳",
+ registrationTime: "2023-12-04 12:00:00",
+ createTime: "2023-12-04 09:45:00"
+ },
+ {
+ id: 5,
+ hospitalNo: "D202312005",
+ donorName: "瀛欎竷",
+ gender: "0",
+ age: 61,
+ diagnosis: "鑴戣偪鐦�",
+ deathReason: "brain_death",
+ deathTime: "2023-12-05 13:10:00",
+ judgmentDoctor: "鍛ㄥ尰鐢�",
+ judgmentDescription: "棰呭唴鍘嬪楂樺鑷磋剳鐤濆舰鎴�",
+ registrant: "鍚村崗璋冨憳",
+ registrationTime: "2023-12-05 13:45:00",
+ createTime: "2023-12-05 10:30:00"
+ }
+];
+
+// 妯℃嫙闄勪欢鏁版嵁
+const mockAttachmentData = [
+ {
+ id: 1,
+ judgmentId: 1,
+ type: "1",
+ typeName: "鑴戞浜″垽瀹氳〃",
+ fileName: "鑴戞浜″垽瀹氳〃_202312001.pdf",
+ fileSize: 2548321,
+ uploadTime: "2023-12-01 14:35:00",
+ uploader: "鐜嬪尰鐢�",
+ fileUrl: "/attachments/brain_death_1.pdf"
+ },
+ {
+ id: 2,
+ judgmentId: 1,
+ type: "2",
+ typeName: "鑴戠數鍥捐瘎浼拌〃",
+ fileName: "鑴戠數鍥捐瘎浼拌〃_202312001.docx",
+ fileSize: 512345,
+ uploadTime: "2023-12-01 15:20:00",
+ uploader: "鏉庡尰鐢�",
+ fileUrl: "/attachments/eeg_1.docx"
+ },
+ {
+ id: 3,
+ judgmentId: 2,
+ type: "7",
+ typeName: "蹇冩浜″垽瀹氳〃",
+ fileName: "蹇冩浜″垽瀹氳〃_202312002.pdf",
+ fileSize: 1892345,
+ uploadTime: "2023-12-02 09:30:00",
+ uploader: "鍒樺尰鐢�",
+ fileUrl: "/attachments/heart_death_2.pdf"
+ },
+ {
+ id: 4,
+ judgmentId: 3,
+ type: "1",
+ typeName: "鑴戞浜″垽瀹氳〃",
+ fileName: "鑴戞浜″垽瀹氳〃_202312003.pdf",
+ fileSize: 3124567,
+ uploadTime: "2023-12-03 17:00:00",
+ uploader: "闄堝尰鐢�",
+ fileUrl: "/attachments/brain_death_3.pdf"
+ },
+ {
+ id: 5,
+ judgmentId: 3,
+ type: "4",
+ typeName: "缁忛澶氭櫘鍕掕秴澹拌瘎浼拌褰�",
+ fileName: "缁忛澶氭櫘鍕抇202312003.xlsx",
+ fileSize: 845672,
+ uploadTime: "2023-12-03 17:15:00",
+ uploader: "寮犳妧甯�",
+ fileUrl: "/attachments/tcd_3.xlsx"
+ }
+];
+
+// 妯℃嫙API鍝嶅簲寤惰繜
+const delay = (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));
+
+// 鏌ヨ姝讳骸鍒ゅ畾鍒楄〃
+export const listDeathJudgment = async (queryParams = {}) => {
+ await delay();
+
+ const {
+ pageNum = 1,
+ pageSize = 10,
+ hospitalNo,
+ donorName,
+ deathReason,
+ deathTimeRange = []
+ } = queryParams;
+
+ // 杩囨护鏁版嵁
+ let filteredData = mockDeathJudgmentData.filter(item => {
+ let match = true;
+
+ if (hospitalNo && !item.hospitalNo.includes(hospitalNo)) {
+ match = false;
+ }
+
+ if (donorName && !item.donorName.includes(donorName)) {
+ match = false;
+ }
+
+ if (deathReason && item.deathReason !== deathReason) {
+ match = false;
+ }
+
+ if (deathTimeRange.length === 2) {
+ const deathTime = new Date(item.deathTime);
+ const startTime = new Date(deathTimeRange[0]);
+ const endTime = new Date(deathTimeRange[1]);
+ endTime.setDate(endTime.getDate() + 1);
+
+ if (deathTime < startTime || deathTime >= endTime) {
+ match = false;
+ }
+ }
+
+ return match;
+ });
+
+ // 鍒嗛〉
+ const startIndex = (pageNum - 1) * pageSize;
+ const endIndex = startIndex + parseInt(pageSize);
+ const paginatedData = filteredData.slice(startIndex, endIndex);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ rows: paginatedData,
+ total: filteredData.length,
+ pageNum: parseInt(pageNum),
+ pageSize: parseInt(pageSize)
+ }
+ };
+};
+
+// 鑾峰彇姝讳骸鍒ゅ畾璇︾粏淇℃伅
+export const getDeathJudgmentDetail = async (id) => {
+ await delay();
+
+ const detail = mockDeathJudgmentData.find(item => item.id == id);
+
+ if (detail) {
+ // 鑾峰彇璇ュ垽瀹氬搴旂殑闄勪欢
+ const attachments = mockAttachmentData.filter(item => item.judgmentId == id);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ ...detail,
+ attachments
+ }
+ };
+ } else {
+ return {
+ code: 404,
+ message: "姝讳骸鍒ゅ畾璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鏂板姝讳骸鍒ゅ畾
+export const addDeathJudgment = async (data) => {
+ await delay();
+
+ const newId = Math.max(...mockDeathJudgmentData.map(item => item.id)) + 1;
+ const hospitalNo = `D${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+
+ const newRecord = {
+ ...data,
+ id: newId,
+ hospitalNo,
+ registrationTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ createTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ // 妯℃嫙娣诲姞鍒版暟鎹�
+ mockDeathJudgmentData.unshift(newRecord);
+
+ return {
+ code: 200,
+ message: "鏂板鎴愬姛",
+ data: newRecord
+ };
+};
+
+// 淇敼姝讳骸鍒ゅ畾
+export const updateDeathJudgment = async (data) => {
+ await delay();
+
+ const index = mockDeathJudgmentData.findIndex(item => item.id == data.id);
+
+ if (index !== -1) {
+ mockDeathJudgmentData[index] = {
+ ...mockDeathJudgmentData[index],
+ ...data,
+ updateTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ return {
+ code: 200,
+ message: "淇敼鎴愬姛",
+ data: mockDeathJudgmentData[index]
+ };
+ } else {
+ return {
+ code: 404,
+ message: "姝讳骸鍒ゅ畾璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鍒犻櫎姝讳骸鍒ゅ畾
+export const delDeathJudgment = async (ids) => {
+ await delay();
+
+ const idArray = Array.isArray(ids) ? ids : [ids];
+
+ idArray.forEach(id => {
+ const index = mockDeathJudgmentData.findIndex(item => item.id == id);
+ if (index !== -1) {
+ mockDeathJudgmentData.splice(index, 1);
+
+ // 鍚屾椂鍒犻櫎瀵瑰簲鐨勯檮浠惰褰�
+ const attachmentIndex = mockAttachmentData.findIndex(item => item.judgmentId == id);
+ while (attachmentIndex !== -1) {
+ mockAttachmentData.splice(attachmentIndex, 1);
+ }
+ }
+ });
+
+ return {
+ code: 200,
+ message: "鍒犻櫎鎴愬姛"
+ };
+};
+
+// 瀵煎嚭姝讳骸鍒ゅ畾
+export const exportDeathJudgment = async (queryParams) => {
+ await delay(1000);
+
+ // 妯℃嫙瀵煎嚭鍔熻兘
+ const { data } = await listDeathJudgment(queryParams);
+
+ return {
+ code: 200,
+ message: "瀵煎嚭鎴愬姛",
+ data: {
+ fileName: `姝讳骸鍒ゅ畾鏁版嵁_${new Date().getTime()}.xlsx`,
+ downloadUrl: "/api/export/deathJudgment"
+ }
+ };
+};
+
+// 闄勪欢鐩稿叧API
+export const uploadAttachment = async (file, judgmentId, type) => {
+ await delay(1500);
+
+ const newAttachment = {
+ id: Math.max(...mockAttachmentData.map(item => item.id), 0) + 1,
+ judgmentId: parseInt(judgmentId),
+ type: type,
+ typeName: getTypeName(type),
+ fileName: file.name,
+ fileSize: file.size,
+ uploadTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ uploader: "褰撳墠鐢ㄦ埛",
+ fileUrl: URL.createObjectURL(file)
+ };
+
+ mockAttachmentData.push(newAttachment);
+
+ return {
+ code: 200,
+ message: "涓婁紶鎴愬姛",
+ data: newAttachment
+ };
+};
+
+export const deleteAttachment = async (attachmentId) => {
+ await delay();
+
+ const index = mockAttachmentData.findIndex(item => item.id == attachmentId);
+
+ if (index !== -1) {
+ mockAttachmentData.splice(index, 1);
+
+ return {
+ code: 200,
+ message: "鍒犻櫎鎴愬姛"
+ };
+ } else {
+ return {
+ code: 404,
+ message: "闄勪欢涓嶅瓨鍦�"
+ };
+ }
+};
+
+export const getAttachments = async (judgmentId) => {
+ await delay();
+
+ const attachments = mockAttachmentData.filter(item => item.judgmentId == judgmentId);
+
+ return {
+ code: 200,
+ message: "success",
+ data: attachments
+ };
+};
+
+// 杈呭姪鍑芥暟
+function getTypeName(type) {
+ const typeMap = {
+ "1": "鑴戞浜″垽瀹氳〃",
+ "2": "鑴戠數鍥捐瘎浼拌〃",
+ "3": "鐭綔浼忔湡浣撴劅璇卞彂鐢典綅璇勪及琛�",
+ "4": "缁忛澶氭櫘鍕掕秴澹拌瘎浼拌褰�",
+ "5": "鍗仴濮旇剳鎹熶激璐ㄦ帶涓績 - 涓村簥缁煎悎璇勪及琛�",
+ "6": "UW璇勫垎琛�",
+ "7": "蹇冩浜″垽瀹氳〃"
+ };
+
+ return typeMap[type] || "鏈煡绫诲瀷";
+}
diff --git a/src/views/business/ethicalReview/ethicalReviewInfo.vue b/src/views/business/ethicalReview/ethicalReviewInfo.vue
new file mode 100644
index 0000000..d505029
--- /dev/null
+++ b/src/views/business/ethicalReview/ethicalReviewInfo.vue
@@ -0,0 +1,1526 @@
+<template>
+ <div class="ethics-review-detail">
+ <el-card class="detail-card">
+ <!-- 鍩虹淇℃伅 -->
+ <div slot="header" class="clearfix">
+ <span class="detail-title">浼︾悊瀹℃煡鍩烘湰淇℃伅</span>
+ <div style="float: right;">
+ <el-button type="primary" @click="handleSave" :loading="saveLoading">
+ 淇濆瓨
+ </el-button>
+
+ <el-button
+ type="warning"
+ @click="handleEndReview"
+ :disabled="form.ethicsConclusion === 'terminated'"
+ >
+ 缁撴潫瀹℃煡
+ </el-button>
+ </div>
+ </div>
+
+ <el-form :model="form" ref="form" :rules="rules" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input v-model="form.hospitalNo" readonly />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎹愮尞鑰呭鍚�" prop="donorName">
+ <el-input v-model="form.donorName" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鎬у埆" prop="gender">
+ <el-select v-model="form.gender" style="width: 100%">
+ <el-option label="鐢�" value="0" />
+ <el-option label="濂�" value="1" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="骞撮緞" prop="age">
+ <el-input v-model="form.age" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="16">
+ <el-form-item label="鐤剧梾璇婃柇" prop="diagnosis">
+ <el-input v-model="form.diagnosis" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="8">
+ <el-form-item label="浼︾悊缁撹" prop="ethicsConclusion">
+ <el-select v-model="form.ethicsConclusion" style="width: 100%">
+ <el-option label="瀹℃煡涓�" value="reviewing" />
+ <el-option label="鍚屾剰" value="approved" />
+ <el-option
+ label="淇敼鍚庡悓鎰�"
+ value="approved_with_modifications"
+ />
+ <el-option label="淇敼鍚庨噸瀹�" value="re-review" />
+ <el-option label="涓嶅悓鎰�" value="disapproved" />
+ <el-option label="缁堟瀹℃煡" value="terminated" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="瀹℃煡鏃堕棿" prop="reviewTime">
+ <el-date-picker
+ v-model="form.reviewTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="鐧昏浜�" prop="registrant">
+ <el-input v-model="form.registrant" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="24">
+ <el-form-item label="浼︾悊鎰忚" prop="ethicsOpinion">
+ <el-input
+ type="textarea"
+ :rows="3"
+ v-model="form.ethicsOpinion"
+ placeholder="璇疯緭鍏ヤ鸡鐞嗗鏌ユ剰瑙�"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-form-item label="鐧昏鏃堕棿" prop="registrationTime">
+ <el-date-picker
+ v-model="form.registrationTime"
+ type="datetime"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ </el-card>
+ <!-- 闄勪欢涓婁紶 -->
+ <el-card class="attachment-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鐩稿叧闄勪欢</span>
+ <el-button type="primary" size="mini" @click="handleUploadAttachment">
+ 涓婁紶闄勪欢
+ </el-button>
+ </div>
+
+ <el-table :data="attachments" style="width: 100%">
+ <el-table-column label="鏂囦欢鍚嶇О" min-width="200">
+ <template slot-scope="scope">
+ <div class="file-info">
+ <i
+ class="el-icon-document"
+ style="margin-right: 8px; color: #409EFF;"
+ ></i>
+ <span>{{ scope.row.fileName }}</span>
+ </div>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢绫诲瀷" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag size="small">{{ getFileType(scope.row.fileName) }}</el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鏂囦欢澶у皬" width="100" align="center">
+ <template slot-scope="scope">
+ <span>{{ formatFileSize(scope.row.fileSize) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓婁紶鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ <span>{{ parseTime(scope.row.uploadTime) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓婁紶浜�" width="100" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.uploader }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鎿嶄綔" width="120" align="center">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handlePreviewAttachment(scope.row)"
+ >棰勮</el-button
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-download"
+ @click="handleDownloadAttachment(scope.row)"
+ >涓嬭浇</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ <!-- 涓撳瀹℃煡鎯呭喌 -->
+ <el-card class="expert-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title"
+ >涓撳瀹℃煡鎯呭喌 (18浣嶆櫘閫氫笓瀹� + 1浣嶄富浠讳笓瀹�)</span
+ >
+ <div style="float: right;">
+ <el-button
+ size="mini"
+ type="primary"
+ @click="handleSendToNormalExperts"
+ :disabled="!canSendToNormalExperts"
+ >
+ 鍙戦�佹櫘閫氫笓瀹�
+ </el-button>
+ <el-button
+ size="mini"
+ type="success"
+ @click="handleSendToChiefExpert"
+ :disabled="!canSendToChiefExpert"
+ >
+ 鍙戦�佷富浠讳笓瀹�
+ </el-button>
+ <el-button
+ size="mini"
+ type="warning"
+ @click="handleBatchSend"
+ :disabled="!canBatchSend"
+ >
+ 鎵归噺鍙戦��
+ </el-button>
+ </div>
+ </div>
+ <!-- 涓撳缁熻淇℃伅 -->
+ <div
+ class="expert-stats"
+ style="margin-top: 20px; padding: 15px; background: #f5f7fa; border-radius: 4px;"
+ >
+ <el-row :gutter="20">
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">鏅�氫笓瀹跺凡鍚屾剰:</span>
+ <span class="stat-value">{{ approvedNormalExperts }}/18</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">涓讳换涓撳鐘舵��:</span>
+ <span class="stat-value">{{ chiefExpertStatus }}</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">鎬诲畬鎴愯繘搴�:</span>
+ <span class="stat-value">{{ completionRate }}%</span>
+ </div>
+ </el-col>
+ <el-col :span="6">
+ <div class="stat-item">
+ <span class="stat-label">瀹℃煡缁撴灉:</span>
+ <span class="stat-value">
+ <el-tag :type="overallConclusionFilter">
+ {{ overallConclusionText }}
+ </el-tag>
+ </span>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ <!-- 涓撳瀹℃煡琛ㄦ牸 -->
+ <el-table
+ :data="expertReviews"
+ v-loading="expertLoading"
+ style="width: 100%"
+ heiht="300"
+ :row-class-name="getExpertRowClassName"
+ >
+ <el-table-column label="搴忓彿" width="60" align="center" type="index" />
+
+ <el-table-column
+ label="涓撳濮撳悕"
+ width="120"
+ align="center"
+ fixed="left"
+ >
+ <template slot-scope="scope">
+ <span>{{ scope.row.expertName }}</span>
+ <el-tag
+ v-if="scope.row.isChief"
+ size="mini"
+ type="danger"
+ style="margin-left: 5px;"
+ >涓讳换</el-tag
+ >
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓撳绫诲瀷" width="100" align="center">
+ <template slot-scope="scope">
+ <span :class="scope.row.isChief ? 'chief-expert' : 'normal-expert'">
+ {{ scope.row.isChief ? "涓讳换涓撳" : "鏅�氫笓瀹�" }}
+ </span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="瀹℃煡鐘舵��" width="100" align="center">
+ <template slot-scope="scope">
+ <el-tag :type="statusFilter(scope.row.reviewStatus)" size="small">
+ {{ statusTextFilter(scope.row.reviewStatus) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="涓撳缁撹" width="120" align="center">
+ <template slot-scope="scope">
+ <el-tag
+ v-if="scope.row.expertConclusion"
+ :type="conclusionFilter(scope.row.expertConclusion)"
+ size="small"
+ >
+ {{ conclusionTextFilter(scope.row.expertConclusion) }}
+ </el-tag>
+ <span v-else class="no-data">-</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="瀹℃煡鎰忚" min-width="200" show-overflow-tooltip>
+ <template slot-scope="scope">
+ <span :class="{ 'expert-opinion': scope.row.expertOpinion }">
+ {{ scope.row.expertOpinion || "鏆傛棤鎰忚" }}
+ </span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="瀹℃煡鏃堕棿" width="160" align="center">
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.reviewTime ? parseTime(scope.row.reviewTime) : "鏈鏌�"
+ }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="鎿嶄綔" width="180" align="center" fixed="right">
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-s-promotion"
+ @click="handleSendToExpert(scope.row)"
+ :disabled="scope.row.reviewStatus === 'submitted'"
+ :class="{ 'sent-button': scope.row.reviewStatus === 'submitted' }"
+ >
+ {{ scope.row.reviewStatus === "submitted" ? "宸插彂閫�" : "鍙戦��" }}
+ </el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-edit"
+ @click="handleEditExpertReview(scope.row)"
+ :disabled="scope.row.reviewStatus !== 'submitted'"
+ >
+ 缂栬緫
+ </el-button>
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleViewExpertReview(scope.row)"
+ >
+ 璇︽儏
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+
+ </el-card>
+
+ <!-- 鍙戦�佷笓瀹跺璇濇 -->
+ <el-dialog
+ title="鍙戦�佷笓瀹跺鏌�"
+ :visible.sync="sendDialogVisible"
+ width="500px"
+ >
+ <el-form :model="sendForm" ref="sendForm" label-width="100px">
+ <el-form-item label="涓撳绫诲瀷" prop="expertType">
+ <el-radio-group v-model="sendForm.expertType">
+ <el-radio label="normal">鏅�氫笓瀹�</el-radio>
+ <el-radio label="chief">涓讳换涓撳</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item
+ label="閫夋嫨涓撳"
+ prop="expertIds"
+ v-if="sendForm.expertType === 'normal'"
+ >
+ <el-select
+ v-model="sendForm.expertIds"
+ multiple
+ placeholder="璇烽�夋嫨涓撳"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="expert in availableExperts"
+ :key="expert.id"
+ :label="expert.name"
+ :value="expert.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍙戦�佸唴瀹�" prop="content">
+ <el-input
+ type="textarea"
+ :rows="4"
+ v-model="sendForm.content"
+ placeholder="璇疯緭鍏ュ彂閫佺粰涓撳鐨勫鏌ュ唴瀹硅鏄�"
+ />
+ </el-form-item>
+ </el-form>
+ <div slot="footer">
+ <el-button @click="sendDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="handleSendConfirm"
+ >纭鍙戦��</el-button
+ >
+ </div>
+ </el-dialog>
+ </div>
+</template>
+<script>
+import {
+ getEthicsReviewDetail,
+ updateEthicsReview,
+ sendExpertReview,
+ endEthicsReview,
+ uploadAttachment,
+ deleteAttachment,
+ getAttachments
+} from "./ethicsReview";
+
+export default {
+ name: "EthicsReviewDetail",
+ data() {
+ return {
+ // 琛ㄥ崟鏁版嵁
+ form: {
+ id: undefined,
+ hospitalNo: "",
+ donorName: "",
+ gender: "",
+ age: "",
+ diagnosis: "",
+ ethicsConclusion: "reviewing",
+ ethicsOpinion: "",
+ reviewTime: "",
+ registrant: "",
+ registrationTime: new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19)
+ },
+ // 琛ㄥ崟楠岃瘉瑙勫垯
+ rules: {
+ donorName: [
+ { required: true, message: "鎹愮尞鑰呭鍚嶄笉鑳戒负绌�", trigger: "blur" }
+ ],
+ ethicsConclusion: [
+ { required: true, message: "浼︾悊缁撹涓嶈兘涓虹┖", trigger: "change" }
+ ],
+ reviewTime: [
+ { required: true, message: "瀹℃煡鏃堕棿涓嶈兘涓虹┖", trigger: "change" }
+ ]
+ },
+ // 淇濆瓨鍔犺浇鐘舵��
+ saveLoading: false,
+
+ // 闄勪欢鏁版嵁
+ attachments: [],
+ expertReviews: [
+ // 鏅�氫笓瀹讹紙18浣嶏級- 鍒濆鐘舵�佷负鐢宠涓�
+ {
+ id: 1,
+ expertName: "寮犳暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 2,
+ expertName: "鏉庢暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 3,
+ expertName: "鐜嬫暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 4,
+ expertName: "鍒樻暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 5,
+ expertName: "闄堟暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 6,
+ expertName: "鏉ㄦ暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 7,
+ expertName: "榛勬暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 8,
+ expertName: "璧垫暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 9,
+ expertName: "鍛ㄦ暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 10,
+ expertName: "鍚存暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 11,
+ expertName: "寰愭暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 12,
+ expertName: "瀛欐暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 13,
+ expertName: "鏈辨暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 14,
+ expertName: "椹暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 15,
+ expertName: "鑳℃暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 16,
+ expertName: "鏋楁暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 17,
+ expertName: "閮暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ {
+ id: 18,
+ expertName: "浣曟暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ // 涓讳换涓撳锛�1浣嶏級
+ {
+ id: 19,
+ expertName: "涓讳换涓撳",
+ isChief: true,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ }
+ ],
+ expertLoading: false,
+ attachmentLoading: false,
+ // 鍙戦�佸璇濇
+ sendDialogVisible: false,
+ sendForm: {
+ expertType: "normal",
+ expertIds: [],
+ content: ""
+ },
+ // 涓婁紶鐩稿叧
+ uploadDialogVisible: false,
+ uploadLoading: false,
+ tempFileList: [],
+ // 鍙敤涓撳鍒楄〃
+ availableExperts: [
+ { id: 1, name: "寮犳暀鎺�", type: "normal" },
+ { id: 2, name: "鏉庢暀鎺�", type: "normal" },
+ { id: 3, name: "鐜嬫暀鎺�", type: "normal" },
+ { id: 4, name: "璧典富浠�", type: "chief" }
+ ]
+ };
+ },
+ computed: {
+ // 璁$畻灞炴�э細鏅�氫笓瀹跺悓鎰忔暟閲�
+ approvedNormalExperts() {
+ return this.expertReviews.filter(
+ expert => !expert.isChief && expert.expertConclusion === "approved"
+ ).length;
+ },
+ // 璁$畻灞炴�э細涓讳换涓撳鐘舵��
+ chiefExpertStatus() {
+ const chiefExpert = this.expertReviews.find(expert => expert.isChief);
+ return chiefExpert
+ ? this.statusTextFilter(chiefExpert.reviewStatus)
+ : "鏈垎閰�";
+ },
+ // 璁$畻灞炴�э細瀹屾垚杩涘害
+ completionRate() {
+ const totalExperts = this.expertReviews.length;
+ const completedExperts = this.expertReviews.filter(
+ expert => expert.reviewStatus === "submitted"
+ ).length;
+ return totalExperts > 0
+ ? Math.round((completedExperts / totalExperts) * 100)
+ : 0;
+ },
+ // 璁$畻灞炴�э細鎬讳綋缁撹
+ overallConclusionText() {
+ if (this.approvedNormalExperts >= 12) {
+ return "閫氳繃";
+ } else if (this.approvedNormalExperts >= 9) {
+ return "淇敼鍚庨�氳繃";
+ } else {
+ return "涓嶉�氳繃";
+ }
+ },
+ overallConclusionFilter() {
+ if (this.approvedNormalExperts >= 12) {
+ return "success";
+ } else if (this.approvedNormalExperts >= 9) {
+ return "warning";
+ } else {
+ return "danger";
+ }
+ },
+ // 鏄惁鍙互鍙戦�佺粰鏅�氫笓瀹�
+ canSendToNormalExperts() {
+ return (
+ this.expertReviews.filter(
+ expert => !expert.isChief && expert.reviewStatus === "applying"
+ ).length > 0
+ );
+ },
+ // 鏄惁鍙互鍙戦�佺粰涓讳换涓撳锛堥渶瑕佽嚦灏�12涓櫘閫氫笓瀹跺悓鎰忥級
+ canSendToChiefExpert() {
+ return (
+ this.approvedNormalExperts >= 12 &&
+ this.expertReviews.filter(
+ expert => expert.isChief && expert.reviewStatus === "applying"
+ ).length > 0
+ );
+ },
+ // 鏄惁鍙互鎵归噺鍙戦��
+ canBatchSend() {
+ return (
+ this.expertReviews.filter(expert => expert.reviewStatus === "applying")
+ .length > 0
+ );
+ },
+ // 鏄惁鍙互鍙戦�佷笓瀹跺鏌�
+ canSendToExperts() {
+ return this.form.id && this.form.ethicsConclusion === "reviewing";
+ },
+ // 褰撳墠鐢ㄦ埛淇℃伅
+ currentUser() {
+ return JSON.parse(sessionStorage.getItem("user") || "{}");
+ }
+ },
+ created() {
+ const id = this.$route.query.id;
+ if (id) {
+ this.getDetail(id);
+ this.getAttachments(id);
+ // 涓嶅啀闇�瑕佷粠鎺ュ彛鑾峰彇涓撳鍒楄〃锛屼娇鐢ㄥ浐瀹氱殑expertReviews鏁版嵁
+ } else if (this.$route.path.includes("/add")) {
+ this.generateHospitalNo();
+ this.form.registrant = this.currentUser.username || "褰撳墠鐢ㄦ埛";
+ }
+ },
+ methods: {
+ // 鐢熸垚浣忛櫌鍙�
+ generateHospitalNo() {
+ const timestamp = Date.now().toString();
+ this.form.hospitalNo = "D" + timestamp.slice(-6);
+ },
+ getExpertRowClassName({ row }) {
+ return row.isChief ? "chief-expert-row" : "normal-expert-row";
+ },
+ // 鑾峰彇璇︽儏
+ getDetail(id) {
+ getEthicsReviewDetail(id)
+ .then(response => {
+ if (response.code === 200) {
+ this.form = response.data;
+ }
+ })
+ .catch(error => {
+ console.error("鑾峰彇浼︾悊瀹℃煡璇︽儏澶辫触:", error);
+ this.$message.error("鑾峰彇璇︽儏澶辫触");
+ });
+ },
+
+ // 鑾峰彇涓撳瀹℃煡鍒楄〃
+ getExpertReviews(ethicsReviewId) {
+ this.expertLoading = true;
+ // 妯℃嫙鏁版嵁 - 瀹為檯椤圭洰涓粠鎺ュ彛鑾峰彇
+ setTimeout(() => {
+ this.expertReviews = [
+ // 鏅�氫笓瀹讹紙18浣嶏級
+ {
+ id: 1,
+ expertName: "寮犳暀鎺�",
+ isChief: false,
+ reviewStatus: "submitted",
+ expertConclusion: "approved",
+ expertOpinion: "绗﹀悎浼︾悊瑕佹眰",
+ reviewTime: "2023-12-01 10:30:00"
+ },
+ {
+ id: 2,
+ expertName: "鏉庢暀鎺�",
+ isChief: false,
+ reviewStatus: "submitted",
+ expertConclusion: "approved",
+ expertOpinion: "鏂规璁捐鍚堢悊",
+ reviewTime: "2023-12-01 11:20:00"
+ },
+ {
+ id: 3,
+ expertName: "鐜嬫暀鎺�",
+ isChief: false,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ },
+ // 涓讳换涓撳锛�1浣嶏級
+ {
+ id: 19,
+ expertName: "璧典富浠�",
+ isChief: true,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ }
+ ];
+ this.expertLoading = false;
+ }, 500);
+ },
+
+ // 鑾峰彇闄勪欢鍒楄〃
+ getAttachments(ethicsReviewId) {
+ this.attachmentLoading = true;
+ getAttachments(ethicsReviewId)
+ .then(response => {
+ if (response.code === 200) {
+ this.attachments = response.data;
+ }
+ this.attachmentLoading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇闄勪欢鍒楄〃澶辫触:", error);
+ this.attachmentLoading = false;
+ });
+ },
+
+ // 鐘舵�佽繃婊ゅ櫒
+ statusFilter(status) {
+ const statusMap = {
+ applying: "info",
+ submitted: "success"
+ };
+ return statusMap[status] || "info";
+ },
+
+ statusTextFilter(status) {
+ const statusMap = {
+ applying: "鐢宠涓�",
+ submitted: "宸叉彁浜�"
+ };
+ return statusMap[status] || "鏈煡";
+ },
+
+ // 缁撹杩囨护鍣�
+ conclusionFilter(conclusion) {
+ const conclusionMap = {
+ approved: "success",
+ approved_with_modifications: "warning",
+ disapproved: "danger"
+ };
+ return conclusionMap[conclusion] || "info";
+ },
+
+ conclusionTextFilter(conclusion) {
+ const conclusionMap = {
+ approved: "鍚屾剰",
+ approved_with_modifications: "淇敼鍚庡悓鎰�",
+ disapproved: "涓嶅悓鎰�"
+ };
+ return conclusionMap[conclusion] || "鏈煡";
+ },
+
+ // 淇濆瓨淇℃伅
+ handleSave() {
+ this.$refs.form.validate(valid => {
+ if (valid) {
+ this.saveLoading = true;
+ const apiMethod = this.form.id ? updateEthicsReview : addEthicsReview;
+
+ apiMethod(this.form)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("淇濆瓨鎴愬姛");
+ if (!this.form.id) {
+ this.form.id = response.data.id;
+ this.$router.replace({
+ query: { ...this.$route.query, id: this.form.id }
+ });
+ }
+ }
+ })
+ .catch(error => {
+ console.error("淇濆瓨澶辫触:", error);
+ this.$message.error("淇濆瓨澶辫触");
+ })
+ .finally(() => {
+ this.saveLoading = false;
+ });
+ }
+ });
+ },
+
+ // 鍙戦�佷笓瀹跺鏌�
+ handleSendToExperts() {
+ this.sendDialogVisible = true;
+ },
+
+ // 鍙戦�佺粰鏅�氫笓瀹�
+ handleSendToNormalExperts() {
+ const normalExperts = this.expertReviews.filter(
+ expert => !expert.isChief && expert.reviewStatus === "applying"
+ );
+ this.sendForm.expertIds = normalExperts.map(expert => expert.id);
+ this.sendForm.expertType = "normal";
+ this.sendDialogVisible = true;
+ },
+
+ // 鍙戦�佺粰涓讳换涓撳
+ handleSendToChiefExpert() {
+ const chiefExpert = this.expertReviews.find(
+ expert => expert.isChief && expert.reviewStatus === "applying"
+ );
+ if (chiefExpert) {
+ this.sendForm.expertIds = [chiefExpert.id];
+ this.sendForm.expertType = "chief";
+ this.sendDialogVisible = true;
+ }
+ },
+
+ // 鎵归噺鍙戦��
+ handleBatchSend() {
+ const applyingExperts = this.expertReviews.filter(
+ expert => expert.reviewStatus === "applying"
+ );
+ this.sendForm.expertIds = applyingExperts.map(expert => expert.id);
+ this.sendForm.expertType = "batch";
+ this.sendDialogVisible = true;
+ },
+
+ // 鍙戦�佺粰鍗曚釜涓撳
+ handleSendToExpert(expert) {
+ this.sendForm.expertIds = [expert.id];
+ this.sendForm.expertType = expert.isChief ? "chief" : "normal";
+ this.sendDialogVisible = true;
+ },
+
+ // 纭鍙戦��
+ handleSendConfirm() {
+ if (this.sendForm.expertIds.length === 0) {
+ this.$message.warning("璇烽�夋嫨瑕佸彂閫佺殑涓撳");
+ return;
+ }
+
+ sendExpertReview({
+ ethicsReviewId: this.form.id,
+ expertIds: this.sendForm.expertIds,
+ content: this.sendForm.content
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍙戦�佹垚鍔�");
+ this.sendDialogVisible = false;
+ this.getExpertReviews(this.form.id);
+ this.sendForm = {
+ expertType: "normal",
+ expertIds: [],
+ content: ""
+ };
+ }
+ })
+ .catch(error => {
+ console.error("鍙戦�佸け璐�:", error);
+ this.$message.error("鍙戦�佸け璐�");
+ });
+ },
+
+ // 缁撴潫瀹℃煡
+ handleEndReview() {
+ this.$confirm(
+ "纭畾瑕佺粨鏉熸湰娆′鸡鐞嗗鏌ュ悧锛熺粨鏉熷悗灏嗘棤娉曚慨鏀逛笓瀹跺鏌ョ粨鏋溿��",
+ "鎻愮ず",
+ {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ }
+ )
+ .then(() => {
+ endEthicsReview(this.form.id)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("瀹℃煡宸茬粨鏉�");
+ this.form.ethicsConclusion = "terminated";
+ }
+ })
+ .catch(error => {
+ console.error("缁撴潫瀹℃煡澶辫触:", error);
+ this.$message.error("缁撴潫瀹℃煡澶辫触");
+ });
+ })
+ .catch(() => {});
+ },
+
+ // 缂栬緫涓撳瀹℃煡
+ handleEditExpertReview(expert) {
+ this.$prompt("璇疯緭鍏ュ鏌ユ剰瑙�", "缂栬緫涓撳瀹℃煡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ inputValue: expert.expertOpinion || "",
+ inputValidator: value => {
+ if (!value || value.trim() === "") {
+ return "瀹℃煡鎰忚涓嶈兘涓虹┖";
+ }
+ return true;
+ }
+ })
+ .then(({ value }) => {
+ // 妯℃嫙鏇存柊涓撳瀹℃煡
+ const index = this.expertReviews.findIndex(e => e.id === expert.id);
+ if (index !== -1) {
+ this.expertReviews[index].expertOpinion = value;
+ this.$message.success("瀹℃煡鎰忚宸叉洿鏂�");
+ }
+ })
+ .catch(() => {});
+ },
+
+ // 鏌ョ湅涓撳瀹℃煡璇︽儏
+ handleViewExpertReview(expert) {
+ this.$alert(
+ `
+ <div>
+ <p><strong>涓撳濮撳悕锛�</strong>${expert.expertName}</p>
+ <p><strong>涓撳绫诲瀷锛�</strong>${
+ expert.isChief ? "涓讳换涓撳" : "鏅�氫笓瀹�"
+ }</p>
+ <p><strong>瀹℃煡鐘舵�侊細</strong>${this.statusTextFilter(
+ expert.reviewStatus
+ )}</p>
+ <p><strong>涓撳缁撹锛�</strong>${
+ expert.expertConclusion
+ ? this.conclusionTextFilter(expert.expertConclusion)
+ : "鏈彁浜�"
+ }</p>
+ <p><strong>瀹℃煡鎰忚锛�</strong>${expert.expertOpinion || "鏃�"}</p>
+ <p><strong>瀹℃煡鏃堕棿锛�</strong>${expert.reviewTime || "鏈鏌�"}</p>
+ </div>
+ `,
+ "涓撳瀹℃煡璇︽儏",
+ {
+ dangerouslyUseHTMLString: true,
+ customClass: "expert-review-detail-dialog"
+ }
+ );
+ },
+
+ // 涓婁紶闄勪欢
+ handleUploadAttachment() {
+ this.uploadDialogVisible = true;
+ },
+
+ // 涓婁紶鍓嶆牎楠�
+ beforeUpload(file) {
+ const allowedTypes = [
+ "application/pdf",
+ "image/jpeg",
+ "image/png",
+ "application/msword",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "application/vnd.ms-excel",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+ ];
+
+ const maxSize = 10 * 1024 * 1024;
+
+ const isTypeOk =
+ allowedTypes.includes(file.type) ||
+ file.name.endsWith(".pdf") ||
+ file.name.endsWith(".jpg") ||
+ file.name.endsWith(".jpeg") ||
+ file.name.endsWith(".png") ||
+ file.name.endsWith(".doc") ||
+ file.name.endsWith(".docx") ||
+ file.name.endsWith(".xls") ||
+ file.name.endsWith(".xlsx");
+
+ if (!isTypeOk) {
+ this.$message.error("鏂囦欢鏍煎紡涓嶆敮鎸�");
+ return false;
+ }
+
+ if (file.size > maxSize) {
+ this.$message.error("鏂囦欢澶у皬涓嶈兘瓒呰繃10MB");
+ return false;
+ }
+
+ return true;
+ },
+
+ // 鏂囦欢閫夋嫨鍙樺寲
+ handleFileChange(file, fileList) {
+ this.tempFileList = fileList;
+ },
+
+ // 鎻愪氦涓婁紶
+ submitUpload() {
+ if (this.tempFileList.length === 0) {
+ this.$message.warning("璇峰厛閫夋嫨瑕佷笂浼犵殑鏂囦欢");
+ return;
+ }
+
+ this.uploadLoading = true;
+
+ const uploadPromises = this.tempFileList.map(file => {
+ const formData = new FormData();
+ formData.append("file", file.raw);
+ formData.append("ethicsReviewId", this.form.id);
+
+ return uploadAttachment(formData);
+ });
+
+ Promise.all(uploadPromises)
+ .then(responses => {
+ this.$message.success("鏂囦欢涓婁紶鎴愬姛");
+ this.uploadDialogVisible = false;
+ this.tempFileList = [];
+ this.getAttachments(this.form.id);
+ })
+ .catch(error => {
+ console.error("涓婁紶澶辫触:", error);
+ this.$message.error("鏂囦欢涓婁紶澶辫触");
+ })
+ .finally(() => {
+ this.uploadLoading = false;
+ });
+ },
+
+ // 棰勮闄勪欢
+ handlePreviewAttachment(attachment) {
+ if (attachment.fileName.endsWith(".pdf")) {
+ window.open(attachment.fileUrl, "_blank");
+ } else if (attachment.fileName.match(/\.(jpg|jpeg|png)$/i)) {
+ this.$alert(
+ `<img src="${attachment.fileUrl}" style="max-width: 100%;" alt="${attachment.fileName}">`,
+ "鍥剧墖棰勮",
+ {
+ dangerouslyUseHTMLString: true,
+ customClass: "image-preview-dialog"
+ }
+ );
+ } else {
+ this.$message.info("璇ユ枃浠剁被鍨嬫殏涓嶆敮鎸佸湪绾块瑙堬紝璇蜂笅杞藉悗鏌ョ湅");
+ }
+ },
+
+ // 涓嬭浇闄勪欢
+ handleDownloadAttachment(attachment) {
+ const link = document.createElement("a");
+ link.href = attachment.fileUrl;
+ link.download = attachment.fileName;
+ link.click();
+ this.$message.success(`寮�濮嬩笅杞�: ${attachment.fileName}`);
+ },
+
+ // 鍒犻櫎闄勪欢
+ handleRemoveAttachment(attachment) {
+ this.$confirm("纭畾瑕佸垹闄よ繖涓檮浠跺悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ deleteAttachment(attachment.id)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("闄勪欢鍒犻櫎鎴愬姛");
+ this.getAttachments(this.form.id);
+ }
+ })
+ .catch(error => {
+ console.error("鍒犻櫎闄勪欢澶辫触:", error);
+ this.$message.error("鍒犻櫎闄勪欢澶辫触");
+ });
+ })
+ .catch(() => {});
+ },
+
+ // 鑾峰彇鏂囦欢绫诲瀷
+ getFileType(fileName) {
+ const ext = fileName
+ .split(".")
+ .pop()
+ .toLowerCase();
+ const typeMap = {
+ pdf: "PDF",
+ doc: "DOC",
+ docx: "DOCX",
+ xls: "XLS",
+ xlsx: "XLSX",
+ jpg: "JPG",
+ jpeg: "JPEG",
+ png: "PNG"
+ };
+ return typeMap[ext] || ext.toUpperCase();
+ },
+
+ // 鏂囦欢澶у皬鏍煎紡鍖�
+ formatFileSize(size) {
+ if (size === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(size) / Math.log(k));
+ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+ },
+
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time) {
+ if (!time) return "";
+ const date = new Date(time);
+ return `${date.getFullYear()}-${(date.getMonth() + 1)
+ .toString()
+ .padStart(2, "0")}-${date
+ .getDate()
+ .toString()
+ .padStart(2, "0")} ${date
+ .getHours()
+ .toString()
+ .padStart(2, "0")}:${date
+ .getMinutes()
+ .toString()
+ .padStart(2, "0")}`;
+ }
+ }
+};
+</script>
+<style scoped>
+.ethics-review-detail {
+ padding: 20px;
+ background-color: #f5f7fa;
+}
+
+.detail-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.expert-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.attachment-card {
+ margin-bottom: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.detail-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.expert-stats {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: rgb(43, 181, 245);
+ border-radius: 8px;
+ margin-bottom: 20px;
+}
+
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 10px;
+}
+
+.stat-label {
+ font-size: 12px;
+ opacity: 0.9;
+ margin-bottom: 5px;
+}
+
+.stat-value {
+ font-size: 18px;
+ font-weight: bold;
+}
+
+.upload-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding: 10px;
+ background-color: #f8f9fa;
+ border-radius: 4px;
+}
+
+.upload-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.file-info {
+ display: flex;
+ align-items: center;
+}
+
+.empty-attachment {
+ text-align: center;
+ padding: 40px 0;
+ color: #909399;
+}
+
+/* 琛ㄥ崟鏍峰紡浼樺寲 */
+:deep(.el-form-item__label) {
+ font-weight: 500;
+}
+
+:deep(.el-input__inner) {
+ border-radius: 4px;
+}
+
+:deep(.el-textarea__inner) {
+ border-radius: 4px;
+ resize: vertical;
+}
+
+/* 琛ㄦ牸鏍峰紡浼樺寲 */
+:deep(.el-table) {
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+:deep(.el-table th) {
+ background-color: #f5f7fa;
+ color: #606266;
+ font-weight: 500;
+}
+
+:deep(.el-table .cell) {
+ padding: 8px 12px;
+}
+
+/* 鎸夐挳鏍峰紡浼樺寲 */
+:deep(.el-button--primary) {
+ background: linear-gradient(135deg, #409eff 0%, #3375e0 100%);
+ border: none;
+ border-radius: 4px;
+}
+
+:deep(.el-button--success) {
+ background: linear-gradient(135deg, #67c23a 0%, #529b2f 100%);
+ border: none;
+ border-radius: 4px;
+}
+
+:deep(.el-button--warning) {
+ background: linear-gradient(135deg, #e6a23c 0%, #d18c2a 100%);
+ border: none;
+ border-radius: 4px;
+}
+
+:deep(.el-button--danger) {
+ background: linear-gradient(135deg, #f56c6c 0%, #e05b5b 100%);
+ border: none;
+ border-radius: 4px;
+}
+
+/* 鏍囩鏍峰紡 */
+:deep(.el-tag) {
+ border-radius: 12px;
+ border: none;
+ font-weight: 500;
+}
+
+/* 瀵硅瘽妗嗘牱寮忎紭鍖� */
+:deep(.el-dialog) {
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+:deep(.el-dialog__header) {
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
+ border-bottom: 1px solid #e4e7ed;
+ padding: 15px 20px;
+}
+
+:deep(.el-dialog__title) {
+ font-weight: 600;
+ color: #303133;
+}
+
+/* 涓婁紶缁勪欢鏍峰紡 */
+:deep(.el-upload-dragger) {
+ border: 2px dashed #dcdfe6;
+ border-radius: 6px;
+ background-color: #fafafa;
+ transition: all 0.3s ease;
+}
+
+:deep(.el-upload-dragger:hover) {
+ border-color: #409eff;
+ background-color: #f0f7ff;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .ethics-review-detail {
+ padding: 10px;
+ }
+
+ .expert-stats .el-col {
+ margin-bottom: 10px;
+ }
+
+ .upload-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+}
+
+/* 鍔ㄧ敾鏁堟灉 */
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.3s ease;
+}
+
+.fade-enter,
+.fade-leave-to {
+ opacity: 0;
+}
+
+/* 鍔犺浇鐘舵�� */
+.loading-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 200px;
+}
+/* 涓撳绫诲瀷鏍峰紡 */
+.normal-expert {
+ color: #409eff;
+ font-weight: 500;
+}
+
+.chief-expert {
+ color: #f56c6c;
+ font-weight: 600;
+}
+
+/* 涓撳琛屾牱寮� */
+:deep(.normal-expert-row) {
+ background-color: #fafafa;
+}
+
+:deep(.chief-expert-row) {
+ background-color: #fff7e6;
+}
+
+:deep(.normal-expert-row:hover) {
+ background-color: #f0f7ff;
+}
+
+:deep(.chief-expert-row:hover) {
+ background-color: #ffecc2;
+}
+
+/* 鏃犳暟鎹牱寮� */
+.no-data {
+ color: #909399;
+ font-style: italic;
+}
+
+/* 涓撳鎰忚鏍峰紡 */
+.expert-opinion {
+ color: #303133;
+ line-height: 1.5;
+}
+
+/* 宸插彂閫佹寜閽牱寮� */
+.sent-button {
+ color: #67c23a !important;
+}
+
+/* 琛ㄦ牸琛屾偓鍋滄晥鏋� */
+:deep(.el-table__row:hover) {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s ease;
+}
+/* 鑷畾涔夋粴鍔ㄦ潯 */
+:deep(::-webkit-scrollbar) {
+ width: 6px;
+ height: 6px;
+}
+
+:deep(::-webkit-scrollbar-track) {
+ background: #f1f1f1;
+ border-radius: 3px;
+}
+
+:deep(::-webkit-scrollbar-thumb) {
+ background: #c1c1c1;
+ border-radius: 3px;
+}
+
+:deep(::-webkit-scrollbar-thumb:hover) {
+ background: #a8a8a8;
+}
+
+/* 涓撳瀹℃煡琛ㄦ牸鐗规畩鏍峰紡 */
+.expert-table-special :deep(.el-table__row) {
+ transition: all 0.3s ease;
+}
+
+.expert-table-special :deep(.el-table__row:hover) {
+ background-color: #f0f7ff;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 涓讳换涓撳琛岄珮浜� */
+:deep(.chief-expert-row) {
+ background-color: #fff7e6 !important;
+}
+
+:deep(.chief-expert-row:hover) {
+ background-color: #ffecc2 !important;
+}
+</style>
diff --git a/src/views/business/ethicalReview/ethicsReview.js b/src/views/business/ethicalReview/ethicsReview.js
new file mode 100644
index 0000000..3c38792
--- /dev/null
+++ b/src/views/business/ethicalReview/ethicsReview.js
@@ -0,0 +1,392 @@
+// 妯℃嫙浼︾悊瀹℃煡鏁版嵁
+const mockEthicsReviewData = [
+ {
+ id: 1,
+ hospitalNo: "D202312001",
+ donorName: "寮犱笁",
+ gender: "0",
+ age: 45,
+ diagnosis: "鑴戝浼�",
+ ethicsConclusion: "reviewing",
+ ethicsOpinion: "璇ョ梾渚嬬鍚堝櫒瀹樻崘鐚鸡鐞嗗鏌ュ熀鏈姹傦紝寤鸿鎻愪氦涓撳濮斿憳浼氬璁�",
+ reviewTime: "2023-12-01 14:30:00",
+ judgmentDoctor: "鐜嬪尰鐢�",
+ judgmentDescription: "缁忚繃澶氭鑴戞浜″垽瀹氾紝绗﹀悎鑴戞浜′复搴婅瘖鏂爣鍑�",
+ registrant: "鏉庡崗璋冨憳",
+ registrationTime: "2023-12-01 15:00:00",
+ createTime: "2023-12-01 10:00:00"
+ },
+ {
+ id: 2,
+ hospitalNo: "D202312002",
+ donorName: "鏉庡洓",
+ gender: "1",
+ age: 38,
+ diagnosis: "蹇冭剰楠ゅ仠",
+ ethicsConclusion: "approved",
+ ethicsOpinion: "瀹跺睘鎰忔効鏄庣‘锛屽尰鐤楃▼搴忓悎瑙勶紝鍚屾剰杩涜鍣ㄥ畼鎹愮尞",
+ reviewTime: "2023-12-02 09:15:00",
+ judgmentDoctor: "鍒樺尰鐢�",
+ judgmentDescription: "蹇冩浜″垽瀹氾紝蹇冪數鍥惧憟鐩寸嚎锛屾棤鑷富鍛煎惛",
+ registrant: "寮犲崗璋冨憳",
+ registrationTime: "2023-12-02 10:00:00",
+ createTime: "2023-12-02 08:30:00"
+ }
+];
+
+// 妯℃嫙涓撳瀹℃煡鏁版嵁
+const mockExpertReviewData = [
+ {
+ id: 1,
+ ethicsReviewId: 1,
+ expertName: "寮犳暀鎺�",
+ isChief: false,
+ reviewStatus: "submitted",
+ expertConclusion: "approved",
+ expertOpinion: "鐥呬緥璧勬枡瀹屾暣锛岀鍚堜鸡鐞嗗鏌ヨ姹�",
+ reviewTime: "2023-12-01 16:30:00"
+ },
+ {
+ id: 2,
+ ethicsReviewId: 1,
+ expertName: "鏉庢暀鎺�",
+ isChief: false,
+ reviewStatus: "submitted",
+ expertConclusion: "approved",
+ expertOpinion: "鎹愮尞娴佺▼瑙勮寖锛屽悓鎰忓鏌�",
+ reviewTime: "2023-12-01 17:20:00"
+ },
+ {
+ id: 19,
+ ethicsReviewId: 1,
+ expertName: "璧典富浠�",
+ isChief: true,
+ reviewStatus: "applying",
+ expertConclusion: "",
+ expertOpinion: "",
+ reviewTime: ""
+ }
+];
+
+// 妯℃嫙闄勪欢鏁版嵁
+const mockAttachmentData = [
+ {
+ id: 1,
+ ethicsReviewId: 1,
+ fileName: "浼︾悊瀹℃煡鐢宠琛�.pdf",
+ fileSize: 2048576,
+ uploadTime: "2023-12-01 09:30:00",
+ uploader: "寮犲尰鐢�",
+ fileUrl: "/attachments/ethics_application_1.pdf"
+ },
+ {
+ id: 2,
+ ethicsReviewId: 1,
+ fileName: "涓撳璇勫鎰忚姹囨��.docx",
+ fileSize: 512345,
+ uploadTime: "2023-12-01 14:20:00",
+ uploader: "鏉庡尰鐢�",
+ fileUrl: "/attachments/expert_review_1.docx"
+ }
+];
+
+// 妯℃嫙API鍝嶅簲寤惰繜
+const delay = (ms = 500) => new Promise(resolve => setTimeout(resolve, ms));
+
+// 鏌ヨ浼︾悊瀹℃煡鍒楄〃
+export const listEthicsReview = async (queryParams = {}) => {
+ await delay();
+
+ const {
+ pageNum = 1,
+ pageSize = 10,
+ hospitalNo,
+ donorName,
+ ethicsConclusion,
+ reviewTimeRange = []
+ } = queryParams;
+
+ // 杩囨护鏁版嵁
+ let filteredData = mockEthicsReviewData.filter(item => {
+ let match = true;
+
+ if (hospitalNo && !item.hospitalNo.includes(hospitalNo)) {
+ match = false;
+ }
+
+ if (donorName && !item.donorName.includes(donorName)) {
+ match = false;
+ }
+
+ if (ethicsConclusion && item.ethicsConclusion !== ethicsConclusion) {
+ match = false;
+ }
+
+ if (reviewTimeRange.length === 2) {
+ const reviewTime = new Date(item.reviewTime);
+ const startTime = new Date(reviewTimeRange[0]);
+ const endTime = new Date(reviewTimeRange[1]);
+ endTime.setDate(endTime.getDate() + 1);
+
+ if (reviewTime < startTime || reviewTime >= endTime) {
+ match = false;
+ }
+ }
+
+ return match;
+ });
+
+ // 鍒嗛〉
+ const startIndex = (pageNum - 1) * pageSize;
+ const endIndex = startIndex + parseInt(pageSize);
+ const paginatedData = filteredData.slice(startIndex, endIndex);
+
+ return {
+ code: 200,
+ message: "success",
+ data: {
+ rows: paginatedData,
+ total: filteredData.length,
+ pageNum: parseInt(pageNum),
+ pageSize: parseInt(pageSize)
+ }
+ };
+};
+
+// 鑾峰彇浼︾悊瀹℃煡璇︾粏淇℃伅
+export const getEthicsReviewDetail = async (id) => {
+ await delay();
+
+ const detail = mockEthicsReviewData.find(item => item.id == id);
+
+ if (detail) {
+ return {
+ code: 200,
+ message: "success",
+ data: detail
+ };
+ } else {
+ return {
+ code: 404,
+ message: "浼︾悊瀹℃煡璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鏂板浼︾悊瀹℃煡
+export const addEthicsReview = async (data) => {
+ await delay();
+
+ const newId = Math.max(...mockEthicsReviewData.map(item => item.id), 0) + 1;
+ const hospitalNo = `D${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(newId).padStart(3, '0')}`;
+
+ const newRecord = {
+ ...data,
+ id: newId,
+ hospitalNo,
+ registrationTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ createTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ mockEthicsReviewData.unshift(newRecord);
+
+ return {
+ code: 200,
+ message: "鏂板鎴愬姛",
+ data: newRecord
+ };
+};
+
+// 淇敼浼︾悊瀹℃煡
+export const updateEthicsReview = async (data) => {
+ await delay();
+
+ const index = mockEthicsReviewData.findIndex(item => item.id == data.id);
+
+ if (index !== -1) {
+ mockEthicsReviewData[index] = {
+ ...mockEthicsReviewData[index],
+ ...data,
+ updateTime: new Date().toISOString().replace('T', ' ').substring(0, 19)
+ };
+
+ return {
+ code: 200,
+ message: "淇敼鎴愬姛",
+ data: mockEthicsReviewData[index]
+ };
+ } else {
+ return {
+ code: 404,
+ message: "浼︾悊瀹℃煡璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 鍒犻櫎浼︾悊瀹℃煡
+export const delEthicsReview = async (ids) => {
+ await delay();
+
+ const idArray = Array.isArray(ids) ? ids : [ids];
+
+ idArray.forEach(id => {
+ const index = mockEthicsReviewData.findIndex(item => item.id == id);
+ if (index !== -1) {
+ mockEthicsReviewData.splice(index, 1);
+ }
+ });
+
+ return {
+ code: 200,
+ message: "鍒犻櫎鎴愬姛"
+ };
+};
+
+// 缁撴潫浼︾悊瀹℃煡
+export const endEthicsReview = async (id) => {
+ await delay();
+
+ const index = mockEthicsReviewData.findIndex(item => item.id == id);
+
+ if (index !== -1) {
+ mockEthicsReviewData[index].ethicsConclusion = 'terminated';
+ mockEthicsReviewData[index].reviewTime = new Date().toISOString().replace('T', ' ').substring(0, 19);
+
+ return {
+ code: 200,
+ message: "瀹℃煡宸茬粨鏉�",
+ data: mockEthicsReviewData[index]
+ };
+ } else {
+ return {
+ code: 404,
+ message: "浼︾悊瀹℃煡璁板綍涓嶅瓨鍦�"
+ };
+ }
+};
+
+// 瀵煎嚭浼︾悊瀹℃煡
+export const exportEthicsReview = async (queryParams) => {
+ await delay(1000);
+
+ const { data } = await listEthicsReview(queryParams);
+
+ return {
+ code: 200,
+ message: "瀵煎嚭鎴愬姛",
+ data: {
+ fileName: `浼︾悊瀹℃煡鏁版嵁_${new Date().getTime()}.xlsx`,
+ downloadUrl: "/api/export/ethicsReview"
+ }
+ };
+};
+
+// 鑾峰彇涓撳瀹℃煡鍒楄〃
+export const getExpertReviews = async (ethicsReviewId) => {
+ await delay();
+
+ const reviews = mockExpertReviewData.filter(item => item.ethicsReviewId == ethicsReviewId);
+
+ return {
+ code: 200,
+ message: "success",
+ data: reviews
+ };
+};
+
+// 鍙戦�佷笓瀹跺鏌�
+export const sendExpertReview = async (data) => {
+ await delay(1500);
+
+ const { ethicsReviewId, expertIds, content } = data;
+
+ // 妯℃嫙鍙戦�佺煭淇$粰涓撳
+ expertIds.forEach(expertId => {
+ const expert = mockExpertReviewData.find(item => item.id === expertId);
+ if (expert) {
+ expert.reviewStatus = 'submitted';
+ expert.reviewTime = new Date().toISOString().replace('T', ' ').substring(0, 19);
+ }
+ });
+
+ return {
+ code: 200,
+ message: "涓撳瀹℃煡閭�璇峰彂閫佹垚鍔�",
+ data: {
+ sentCount: expertIds.length,
+ content: content
+ }
+ };
+};
+
+// 鑾峰彇闄勪欢鍒楄〃
+export const getAttachments = async (ethicsReviewId) => {
+ await delay();
+
+ const attachments = mockAttachmentData.filter(item => item.ethicsReviewId == ethicsReviewId);
+
+ return {
+ code: 200,
+ message: "success",
+ data: attachments
+ };
+};
+
+// 涓婁紶闄勪欢
+export const uploadAttachment = async (formData) => {
+ await delay(2000);
+
+ const newAttachment = {
+ id: Math.max(...mockAttachmentData.map(item => item.id), 0) + 1,
+ ethicsReviewId: parseInt(formData.get('ethicsReviewId')),
+ fileName: `闄勪欢_${new Date().getTime()}.pdf`,
+ fileSize: 1024000,
+ uploadTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
+ uploader: "褰撳墠鐢ㄦ埛",
+ fileUrl: `/attachments/ethics_${formData.get('ethicsReviewId')}_${new Date().getTime()}.pdf`
+ };
+
+ mockAttachmentData.push(newAttachment);
+
+ return {
+ code: 200,
+ message: "涓婁紶鎴愬姛",
+ data: newAttachment
+ };
+};
+
+// 鍒犻櫎闄勪欢
+export const deleteAttachment = async (attachmentId) => {
+ await delay();
+
+ const index = mockAttachmentData.findIndex(item => item.id == attachmentId);
+
+ if (index !== -1) {
+ mockAttachmentData.splice(index, 1);
+
+ return {
+ code: 200,
+ message: "鍒犻櫎鎴愬姛"
+ };
+ } else {
+ return {
+ code: 404,
+ message: "闄勪欢涓嶅瓨鍦�"
+ };
+ }
+};
+
+export default {
+ listEthicsReview,
+ getEthicsReviewDetail,
+ addEthicsReview,
+ updateEthicsReview,
+ delEthicsReview,
+ endEthicsReview,
+ exportEthicsReview,
+ getExpertReviews,
+ sendExpertReview,
+ getAttachments,
+ uploadAttachment,
+ deleteAttachment
+};
diff --git a/src/views/business/ethicalReview/index.vue b/src/views/business/ethicalReview/index.vue
new file mode 100644
index 0000000..f1e75a8
--- /dev/null
+++ b/src/views/business/ethicalReview/index.vue
@@ -0,0 +1,480 @@
+<template>
+ <div class="ethics-review-list">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-card class="search-card">
+ <el-form
+ :model="queryParams"
+ ref="queryForm"
+ :inline="true"
+ label-width="100px"
+ >
+ <el-form-item label="浣忛櫌鍙�" prop="hospitalNo">
+ <el-input
+ v-model="queryParams.hospitalNo"
+ 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="ethicsConclusion">
+ <el-select
+ v-model="queryParams.ethicsConclusion"
+ placeholder="璇烽�夋嫨浼︾悊缁撹"
+ clearable
+ style="width: 200px"
+ >
+ <el-option label="瀹℃煡涓�" value="reviewing" />
+ <el-option label="鍚屾剰" value="approved" />
+ <el-option label="淇敼鍚庡悓鎰�" value="approved_with_modifications" />
+ <el-option label="淇敼鍚庨噸瀹�" value="re-review" />
+ <el-option label="涓嶅悓鎰�" value="disapproved" />
+ <el-option label="缁堟瀹℃煡" value="terminated" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="瀹℃煡鏃堕棿鑼冨洿" prop="reviewTimeRange">
+ <el-date-picker
+ v-model="queryParams.reviewTimeRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ style="width: 240px"
+ />
+ </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-card class="tool-card">
+ <el-row :gutter="10">
+ <el-col :span="16">
+ <el-button type="primary" icon="el-icon-plus" @click="handleCreate"
+ >鏂板缓瀹℃煡</el-button
+ >
+ <el-button
+ type="success"
+ icon="el-icon-edit"
+ :disabled="single"
+ @click="handleUpdate"
+ >淇敼</el-button
+ >
+ <el-button
+ type="danger"
+ icon="el-icon-delete"
+ :disabled="multiple"
+ @click="handleDelete"
+ >鍒犻櫎</el-button
+ >
+ <el-button
+ type="warning"
+ icon="el-icon-download"
+ @click="handleExport"
+ >瀵煎嚭</el-button
+ >
+ <el-button
+ type="info"
+ icon="el-icon-check"
+ :disabled="multiple"
+ @click="handleEndReview"
+ >缁撴潫瀹℃煡</el-button
+ >
+ </el-col>
+ <el-col :span="8" style="text-align: right">
+ <el-tooltip content="鍒锋柊" placement="top">
+ <el-button icon="el-icon-refresh" circle @click="getList" />
+ </el-tooltip>
+ </el-col>
+ </el-row>
+ </el-card>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-card>
+ <el-table
+ v-loading="loading"
+ :data="ethicsReviewList"
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column
+ label="浣忛櫌鍙�"
+ align="center"
+ prop="hospitalNo"
+ width="120"
+ />
+ <el-table-column
+ label="鎹愮尞鑰呭鍚�"
+ align="center"
+ prop="donorName"
+ width="120"
+ />
+ <el-table-column label="鎬у埆" align="center" prop="gender" width="80">
+ <template slot-scope="scope">
+ <dict-tag
+ :options="dict.type.sys_user_sex"
+ :value="parseInt(scope.row.gender)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column label="骞撮緞" align="center" prop="age" width="80" />
+ <el-table-column
+ label="鐤剧梾璇婃柇"
+ align="center"
+ prop="diagnosis"
+ min-width="180"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="浼︾悊缁撹"
+ align="center"
+ prop="ethicsConclusion"
+ width="120"
+ >
+ <template slot-scope="scope">
+ <el-tag :type="conclusionFilter(scope.row.ethicsConclusion)">
+ {{ conclusionTextFilter(scope.row.ethicsConclusion) }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="浼︾悊鎰忚"
+ align="center"
+ prop="ethicsOpinion"
+ min-width="200"
+ show-overflow-tooltip
+ />
+ <el-table-column
+ label="瀹℃煡鏃堕棿"
+ align="center"
+ prop="reviewTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.reviewTime
+ ? parseTime(scope.row.reviewTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鐧昏鏃堕棿"
+ align="center"
+ prop="registrationTime"
+ width="160"
+ >
+ <template slot-scope="scope">
+ <span>{{
+ scope.row.registrationTime
+ ? parseTime(scope.row.registrationTime, "{y}-{m}-{d} {h}:{i}")
+ : "-"
+ }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鐧昏浜�"
+ align="center"
+ prop="registrant"
+ width="100"
+ />
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="200"
+ class-name="small-padding fixed-width"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-view"
+ @click="handleView(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="handleEndReview(scope.row)"
+ :disabled="scope.row.ethicsConclusion === 'terminated'"
+ >缁撴潫</el-button
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C"
+ @click="handleDelete(scope.row)"
+ >鍒犻櫎</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-card>
+ </div>
+</template>
+
+<script>
+import { listEthicsReview, delEthicsReview, exportEthicsReview, endEthicsReview } from "./ethicsReview";
+import Pagination from "@/components/Pagination";
+
+export default {
+ name: "EthicsReviewList",
+ components: { Pagination },
+ dicts: ["sys_user_sex"],
+ data() {
+ return {
+ // 閬僵灞�
+ loading: true,
+ // 閫変腑鏁扮粍
+ ids: [],
+ // 闈炲崟涓鐢�
+ single: true,
+ // 闈炲涓鐢�
+ multiple: true,
+ // 鎬绘潯鏁�
+ total: 0,
+ // 浼︾悊瀹℃煡琛ㄦ牸鏁版嵁
+ ethicsReviewList: [],
+ // 鏌ヨ鍙傛暟
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ hospitalNo: undefined,
+ donorName: undefined,
+ ethicsConclusion: undefined,
+ reviewTimeRange: []
+ }
+ };
+ },
+ created() {
+ this.getList();
+ },
+ methods: {
+ // 浼︾悊缁撹杩囨护鍣�
+ conclusionFilter(conclusion) {
+ const conclusionMap = {
+ "reviewing": "warning", // 瀹℃煡涓�
+ "approved": "success", // 鍚屾剰
+ "approved_with_modifications": "primary", // 淇敼鍚庡悓鎰�
+ "re-review": "info", // 淇敼鍚庨噸瀹�
+ "disapproved": "danger", // 涓嶅悓鎰�
+ "terminated": "info" // 缁堟瀹℃煡
+ };
+ return conclusionMap[conclusion] || "info";
+ },
+ conclusionTextFilter(conclusion) {
+ const conclusionMap = {
+ "reviewing": "瀹℃煡涓�",
+ "approved": "鍚屾剰",
+ "approved_with_modifications": "淇敼鍚庡悓鎰�",
+ "re-review": "淇敼鍚庨噸瀹�",
+ "disapproved": "涓嶅悓鎰�",
+ "terminated": "缁堟瀹℃煡"
+ };
+ return conclusionMap[conclusion] || "鏈煡";
+ },
+ // 鏌ヨ浼︾悊瀹℃煡鍒楄〃
+ getList() {
+ this.loading = true;
+ listEthicsReview(this.queryParams)
+ .then(response => {
+ if (response.code === 200) {
+ this.ethicsReviewList = response.data.rows;
+ this.total = response.data.total;
+ } else {
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ }
+ this.loading = false;
+ })
+ .catch(error => {
+ console.error("鑾峰彇浼︾悊瀹℃煡鍒楄〃澶辫触:", error);
+ this.loading = false;
+ this.$message.error("鑾峰彇鏁版嵁澶辫触");
+ });
+ },
+ // 鎼滅储鎸夐挳鎿嶄綔
+ handleQuery() {
+ this.queryParams.pageNum = 1;
+ this.getList();
+ },
+ // 閲嶇疆鎸夐挳鎿嶄綔
+ resetQuery() {
+ this.$refs.queryForm.resetFields();
+ this.handleQuery();
+ },
+ // 澶氶�夋閫変腑鏁版嵁
+ handleSelectionChange(selection) {
+ this.ids = selection.map(item => item.id);
+ this.single = selection.length !== 1;
+ this.multiple = !selection.length;
+ },
+ // 鏌ョ湅璇︽儏
+ handleView(row) {
+ this.$router.push({
+ path: "/case/ethicalReviewInfo",
+ query: { id: row.id }
+ });
+ },
+ // 鏂板鎸夐挳鎿嶄綔
+ handleCreate() {
+ this.$router.push("/case/ethicalReviewInfo");
+ },
+ // 淇敼鎸夐挳鎿嶄綔
+ handleUpdate(row) {
+ const id = row.id || this.ids[0];
+ this.$router.push({
+ path: "/case/ethicalReviewInfo",
+ query: { id: id }
+ });
+ },
+ // 鍒犻櫎鎸夐挳鎿嶄綔
+ handleDelete(row) {
+ const ids = row.id ? [row.id] : this.ids;
+ this.$confirm("鏄惁纭鍒犻櫎閫変腑鐨勬暟鎹」锛�", "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return delEthicsReview(ids);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒犻櫎鎴愬姛");
+ this.getList();
+ }
+ })
+ .catch(() => {});
+ },
+ // 缁撴潫瀹℃煡鎿嶄綔
+ handleEndReview(row) {
+ const ids = row.id ? [row.id] : this.ids;
+ this.$confirm("鏄惁纭缁撴潫閫変腑鐨勫鏌ラ」鐩紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ return endEthicsReview(ids);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("瀹℃煡宸茬粨鏉�");
+ this.getList();
+ }
+ })
+ .catch(() => {});
+ },
+ // 瀵煎嚭鎸夐挳鎿嶄綔
+ handleExport() {
+ const queryParams = this.queryParams;
+ this.$confirm("鏄惁纭瀵煎嚭鎵�鏈変鸡鐞嗗鏌ユ暟鎹紵", "璀﹀憡", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ this.loading = true;
+ return exportEthicsReview(queryParams);
+ })
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success("瀵煎嚭鎴愬姛");
+ }
+ this.loading = false;
+ })
+ .catch(() => {
+ this.loading = false;
+ });
+ },
+ // 鏃堕棿鏍煎紡鍖�
+ parseTime(time, pattern) {
+ if (!time) return "";
+ const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
+ let date;
+ if (typeof time === "object") {
+ date = time;
+ } else {
+ if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+ time = parseInt(time);
+ }
+ if (typeof time === "number" && time.toString().length === 10) {
+ time = time * 1000;
+ }
+ date = new Date(time);
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ };
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key];
+ if (key === "a") {
+ return ["鏃�", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�"][value];
+ }
+ if (result.length > 0 && value < 10) {
+ value = "0" + value;
+ }
+ return value || 0;
+ });
+ return time_str;
+ }
+ }
+};
+</script>
+
+<style scoped>
+.ethics-review-list {
+ padding: 20px;
+}
+
+.search-card {
+ margin-bottom: 20px;
+}
+
+.tool-card {
+ margin-bottom: 20px;
+}
+
+.fixed-width .el-button {
+ margin: 0 5px;
+}
+</style>
diff --git a/src/views/business/maintain/components/BloodRoutinePanel.vue b/src/views/business/maintain/components/BloodRoutinePanel.vue
new file mode 100644
index 0000000..a43e2fd
--- /dev/null
+++ b/src/views/business/maintain/components/BloodRoutinePanel.vue
@@ -0,0 +1,693 @@
+<template>
+ <div class="medical-panel">
+ <div class="panel-header">
+ <h3>琛�甯歌璁板綍琛�</h3>
+ <div class="panel-actions">
+ <el-button
+ v-if="isEditing"
+ type="primary"
+ size="small"
+ @click="addColumn"
+ icon="el-icon-plus"
+ >
+ 鏂板璁板綍鍒�
+ </el-button>
+ <el-tooltip content="鍒锋柊琛ㄦ牸甯冨眬" placement="top">
+ <el-button
+ size="small"
+ @click="forceTableLayout"
+ icon="el-icon-refresh"
+ >
+ 鍒锋柊甯冨眬
+ </el-button>
+ </el-tooltip>
+ </div>
+ </div>
+
+ <el-table
+ :data="tableData"
+ border
+ style="width: 100%"
+ class="medical-table"
+ v-fit-columns
+ :key="tableKey"
+ @header-dragend="handleHeaderDragEnd"
+ v-loading="tableLoading"
+ >
+ <el-table-column
+ prop="itemName"
+ label="妫�娴嬮」鐩�"
+ width="200"
+ fixed
+ class-name="leave-alone"
+ >
+ <template #default="scope">
+ <div class="item-name-cell">
+ <span :class="{'required-item': scope.row.required}">
+ {{ scope.row.itemName }}
+ </span>
+ <el-tooltip
+ v-if="scope.row.reference"
+ :content="`鍙傝�冭寖鍥�: ${scope.row.reference}`"
+ placement="top"
+ >
+ <i class="el-icon-info reference-icon"></i>
+ </el-tooltip>
+ </div>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ v-for="(col, index) in dynamicColumns"
+ :key="col.key"
+ :label="col.label"
+ :min-width="140"
+ :resizable="isEditing"
+ header-align="center"
+ align="center"
+ >
+ <template #default="scope">
+ <div class="cell-content-wrapper">
+ <el-input
+ v-if="isEditing"
+ v-model="scope.row.values[index]"
+ size="small"
+ :placeholder="getPlaceholder(scope.row)"
+ @blur="handleValueChange(scope.row, index)"
+ class="value-input"
+ :title="scope.row.values[index]"
+ />
+ <div v-else class="value-display-container">
+ <span class="value-text" :title="scope.row.values[index]">
+ {{ scope.row.values[index] || '-' }}
+ </span>
+ <span v-if="scope.row.values[index] && scope.row.unit" class="unit-text">
+ {{ scope.row.unit }}
+ </span>
+ </div>
+ <div v-if="scope.row.reference && scope.row.values[index]" class="validation-indicator">
+ <i
+ v-if="isValueValid(scope.row, scope.row.values[index])"
+ class="el-icon-success valid-icon"
+ title="鏁板�煎湪姝e父鑼冨洿鍐�"
+ ></i>
+ <i
+ v-else
+ class="el-icon-warning invalid-icon"
+ title="鏁板�艰秴鍑烘甯歌寖鍥�"
+ ></i>
+ </div>
+ </div>
+ </template>
+ </el-table-column>
+
+ <!-- <el-table-column
+ v-if="isEditing"
+ label="鎿嶄綔"
+ width="120"
+ fixed="right"
+ class-name="leave-alone"
+ >
+ <template #default>
+ <el-button link type="primary" @click="addColumn" size="small">
+ 鏂板鍒�
+ </el-button>
+ </template>
+ </el-table-column> -->
+ </el-table>
+
+ <!-- 缁熻淇℃伅 -->
+ <div v-if="showStatistics" class="statistics-section">
+ <el-card shadow="never">
+ <div class="stats-content">
+ <span class="stats-title">鏁版嵁缁熻:</span>
+ <span class="stats-item">鎬昏褰曟暟: {{ dynamicColumns.length }} 涓椂闂寸偣</span>
+ <span class="stats-item">宸插~鍐�: {{ filledCount }} 椤�</span>
+ <span class="stats-item">瀹屾垚搴�: {{ completionRate }}%</span>
+ </div>
+ </el-card>
+ </div>
+
+ <!-- 闄勪欢涓婁紶鍖哄煙 -->
+ <div class="attachment-section">
+ <div class="attachment-header">
+ <i class="el-icon-paperclip"></i>
+ <span class="attachment-title">闄勪欢涓婁紶</span>
+ <span class="attachment-tip">鏀寔涓婁紶妫�楠屾姤鍛婂崟绛夋枃浠� (鏈�澶�10涓�)</span>
+ </div>
+ <upload-attachment
+ :file-list="attachments"
+ @change="handleAttachmentChange"
+ :limit="10"
+ :accept="'.pdf,.jpg,.jpeg,.png,.doc,.docx'"
+ />
+ </div>
+
+ <!-- 鍒楃紪杈戝璇濇 -->
+ <el-dialog
+ :title="`${editingColumnIndex !== null ? '缂栬緫' : '鏂板'}鏃堕棿鐐筦"
+ :visible.sync="columnDialogVisible"
+ width="450px"
+ @closed="handleDialogClosed"
+ >
+ <el-form
+ :model="columnForm"
+ label-width="80px"
+ ref="columnForm"
+ :rules="columnRules"
+ >
+ <el-form-item label="鏃ユ湡" prop="date">
+ <el-date-picker
+ v-model="columnForm.date"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ :disabled-date="disableFutureDates"
+ />
+ </el-form-item>
+ <el-form-item label="鏃堕棿" prop="time">
+ <el-time-picker
+ v-model="columnForm.time"
+ placeholder="閫夋嫨鏃堕棿"
+ value-format="HH:mm"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ v-model="columnForm.remark"
+ type="textarea"
+ :rows="2"
+ placeholder="鍙�夊~鍐欐椂闂寸偣澶囨敞淇℃伅"
+ maxlength="100"
+ show-word-limit
+ />
+ </el-form-item>
+ </el-form>
+ <span slot="footer" class="dialog-footer">
+ <el-button @click="columnDialogVisible = false">鍙栨秷</el-button>
+ <el-button
+ v-if="editingColumnIndex !== null"
+ type="danger"
+ @click="handleDeleteColumn"
+ >
+ 鍒犻櫎
+ </el-button>
+ <el-button type="primary" @click="confirmAddColumn" :loading="saveLoading">
+ 纭畾
+ </el-button>
+ </span>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import UploadAttachment from "@/components/UploadAttachment";
+
+export default {
+ name: 'BloodRoutinePanel',
+ components: {
+ UploadAttachment,
+ },
+ props: {
+ isEditing: {
+ type: Boolean,
+ default: false
+ },
+ initialData: {
+ type: Array,
+ default: () => []
+ },
+ showStatistics: {
+ type: Boolean,
+ default: true
+ }
+ },
+ data() {
+ return {
+ tableData: [],
+ dynamicColumns: [
+ {
+ label: '2024-12-27\n08:00',
+ key: 'time1',
+ date: '2024-12-27',
+ time: '08:00',
+ remark: '鏅ㄨ捣妫�娴�'
+ }
+ ],
+ attachments: [],
+ columnDialogVisible: false,
+ columnForm: {
+ date: '',
+ time: '',
+ remark: ''
+ },
+ editingColumnIndex: null,
+ tableKey: 0,
+ tableLoading: false,
+ saveLoading: false,
+ columnRules: {
+ date: [
+ { required: true, message: '璇烽�夋嫨鏃ユ湡', trigger: 'change' }
+ ],
+ time: [
+ { required: true, message: '璇烽�夋嫨鏃堕棿', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ computed: {
+ filledCount() {
+ let count = 0;
+ this.tableData.forEach(row => {
+ row.values.forEach(value => {
+ if (value && value.toString().trim() !== '') {
+ count++;
+ }
+ });
+ });
+ return count;
+ },
+ completionRate() {
+ const total = this.tableData.length * this.dynamicColumns.length;
+ return total > 0 ? Math.round((this.filledCount / total) * 100) : 0;
+ }
+ },
+ watch: {
+ isEditing(newVal) {
+ if (!newVal) {
+ this.$emit('data-change', {
+ type: 'blood_routine',
+ data: this.tableData,
+ columns: this.dynamicColumns,
+ attachments: this.attachments
+ });
+ }
+ this.$nextTick(() => {
+ this.forceTableLayout();
+ });
+ },
+ dynamicColumns: {
+ handler() {
+ this.$nextTick(() => {
+ this.forceTableLayout();
+ });
+ },
+ deep: true,
+ immediate: true
+ }
+ },
+ methods: {
+ initTableData() {
+ const medicalItems = [
+ {
+ itemName: 'WBC',
+ unit: '脳10鈦�/L',
+ required: true,
+ reference: '3.5-9.5',
+ min: 3.5,
+ max: 9.5,
+ type: 'number'
+ },
+ {
+ itemName: 'NEUT%',
+ unit: '%',
+ required: true,
+ reference: '40-75',
+ min: 40,
+ max: 75,
+ type: 'number'
+ },
+ {
+ itemName: 'Hb',
+ unit: 'g/L',
+ required: true,
+ reference: '130-175',
+ min: 130,
+ max: 175,
+ type: 'number'
+ },
+ {
+ itemName: '琛�灏忔澘',
+ unit: '脳10鈦�/L',
+ required: true,
+ reference: '125-350',
+ min: 125,
+ max: 350,
+ type: 'number'
+ }
+ ];
+
+ this.tableData = medicalItems.map(item => ({
+ ...item,
+ values: new Array(this.dynamicColumns.length).fill('')
+ }));
+ },
+
+ getPlaceholder(row) {
+ return row.reference ? `鍙傝��: ${row.reference}` : '璇疯緭鍏ユ暟鍊�';
+ },
+
+ isValueValid(row, value) {
+ if (!value || !row.min || !row.max) return true;
+ const numValue = parseFloat(value);
+ return !isNaN(numValue) && numValue >= row.min && numValue <= row.max;
+ },
+
+ addColumn() {
+ this.editingColumnIndex = null;
+ this.columnForm = {
+ date: new Date().toISOString().split('T')[0],
+ time: '08:00',
+ remark: ''
+ };
+ this.columnDialogVisible = true;
+ this.$nextTick(() => {
+ this.$refs.columnForm && this.$refs.columnForm.clearValidate();
+ });
+ },
+
+ editColumn(index) {
+ this.editingColumnIndex = index;
+ const column = this.dynamicColumns[index];
+ this.columnForm = {
+ date: column.date,
+ time: column.time,
+ remark: column.remark || ''
+ };
+ this.columnDialogVisible = true;
+ },
+
+ confirmAddColumn() {
+ this.$refs.columnForm.validate((valid) => {
+ if (!valid) {
+ this.$message.warning('璇峰畬鍠勬椂闂寸偣淇℃伅');
+ return;
+ }
+
+ this.saveLoading = true;
+
+ if (this.editingColumnIndex !== null) {
+ // 缂栬緫鐜版湁鍒�
+ const column = this.dynamicColumns[this.editingColumnIndex];
+ column.label = `${this.columnForm.date}\n${this.columnForm.time}`;
+ column.date = this.columnForm.date;
+ column.time = this.columnForm.time;
+ column.remark = this.columnForm.remark;
+ this.$message.success('鏃堕棿鐐逛慨鏀规垚鍔�');
+ } else {
+ // 鏂板鍒�
+ const newIndex = this.dynamicColumns.length + 1;
+ const newColumn = {
+ label: `${this.columnForm.date}\n${this.columnForm.time}`,
+ key: `time${Date.now()}`,
+ date: this.columnForm.date,
+ time: this.columnForm.time,
+ remark: this.columnForm.remark
+ };
+
+ this.dynamicColumns.push(newColumn);
+ this.tableData.forEach(row => {
+ row.values.push('');
+ });
+ this.$message.success('鏃堕棿鐐规坊鍔犳垚鍔�');
+ }
+
+ this.columnDialogVisible = false;
+ this.saveLoading = false;
+ this.tableKey += 1;
+ });
+ },
+
+ handleDeleteColumn() {
+ if (this.editingColumnIndex !== null) {
+ this.$confirm('纭畾瑕佸垹闄よ繖涓椂闂寸偣鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.dynamicColumns.splice(this.editingColumnIndex, 1);
+ this.tableData.forEach(row => {
+ row.values.splice(this.editingColumnIndex, 1);
+ });
+ this.columnDialogVisible = false;
+ this.tableKey += 1;
+ this.$message.success('鏃堕棿鐐瑰垹闄ゆ垚鍔�');
+ });
+ }
+ },
+
+ handleDialogClosed() {
+ this.editingColumnIndex = null;
+ this.columnForm = {
+ date: '',
+ time: '',
+ remark: ''
+ };
+ this.$refs.columnForm && this.$refs.columnForm.clearValidate();
+ },
+
+ disableFutureDates(time) {
+ return time.getTime() > Date.now();
+ },
+
+ handleValueChange(row, columnIndex) {
+ this.$emit('data-change', {
+ type: 'blood_routine',
+ data: this.tableData,
+ columns: this.dynamicColumns
+ });
+ },
+
+ handleAttachmentChange(fileList) {
+ this.attachments = fileList;
+ this.$emit('attachment-change', {
+ type: 'blood_routine',
+ attachments: fileList
+ });
+ },
+
+ forceTableLayout() {
+ this.$nextTick(() => {
+ const table = this.$el.querySelector('.el-table');
+ if (table) {
+ window.dispatchEvent(new Event('resize'));
+ }
+ });
+ },
+
+ handleHeaderDragEnd() {
+ this.forceTableLayout();
+ },
+
+ exportData() {
+ return {
+ tableData: this.tableData,
+ columns: this.dynamicColumns,
+ statistics: {
+ filledCount: this.filledCount,
+ completionRate: this.completionRate,
+ totalColumns: this.dynamicColumns.length
+ },
+ exportTime: new Date().toISOString(),
+ attachments: this.attachments
+ };
+ }
+ },
+ mounted() {
+ this.initTableData();
+ }
+};
+</script>
+
+<style scoped>
+.medical-panel {
+ padding: 20px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+ padding-bottom: 16px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.panel-header h3 {
+ margin: 0;
+ color: #303133;
+ font-size: 20px;
+ font-weight: 600;
+}
+
+.panel-actions {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.medical-table {
+ margin-bottom: 24px;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.medical-table ::v-deep .el-table__body-wrapper {
+ overflow-x: auto;
+}
+
+.item-name-cell {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.reference-icon {
+ color: #909399;
+ font-size: 14px;
+ cursor: help;
+ margin-left: 8px;
+}
+
+.required-item::before {
+ content: "*";
+ color: #f56c6c;
+ margin-right: 4px;
+}
+
+.cell-content-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-height: 32px;
+}
+
+.value-input {
+ flex: 1;
+ min-width: 80px;
+}
+
+.value-display-container {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.value-text {
+ font-weight: 500;
+ color: #303133;
+ max-width: 80px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.unit-text {
+ color: #909399;
+ font-size: 12px;
+ flex-shrink: 0;
+}
+
+.validation-indicator {
+ flex-shrink: 0;
+}
+
+.valid-icon {
+ color: #67c23a;
+ font-size: 14px;
+}
+
+.invalid-icon {
+ color: #e6a23c;
+ font-size: 14px;
+}
+
+.statistics-section {
+ margin-bottom: 20px;
+}
+
+.stats-content {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.stats-title {
+ font-weight: 600;
+ color: #303133;
+}
+
+.stats-item {
+ color: #606266;
+ font-size: 14px;
+}
+
+.attachment-section {
+ margin-top: 24px;
+ padding: 20px;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+ background: #fafafa;
+}
+
+.attachment-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.attachment-title {
+ font-weight: 600;
+ color: #409eff;
+}
+
+.attachment-tip {
+ font-size: 12px;
+ color: #909399;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .medical-panel {
+ padding: 12px;
+ }
+
+ .panel-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+ }
+
+ .panel-actions {
+ width: 100%;
+ justify-content: flex-end;
+ }
+
+ .stats-content {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ }
+
+ .cell-content-wrapper {
+ flex-direction: column;
+ gap: 4px;
+ }
+}
+
+/* 鍔ㄧ敾鏁堟灉 */
+.fade-enter-active, .fade-leave-active {
+ transition: opacity 0.3s;
+}
+.fade-enter, .fade-leave-to {
+ opacity: 0;
+}
+</style>
diff --git a/src/views/business/maintain/components/LiverKidneyPanel.vue b/src/views/business/maintain/components/LiverKidneyPanel.vue
new file mode 100644
index 0000000..a485fed
--- /dev/null
+++ b/src/views/business/maintain/components/LiverKidneyPanel.vue
@@ -0,0 +1,492 @@
+<template>
+ <div class="medical-panel">
+ <div class="panel-header">
+ <h3>鑲濆姛鑳借偩鍔熻兘璁板綍琛�</h3>
+ <el-button
+ v-if="isEditing"
+ type="primary"
+ size="small"
+ @click="addColumn"
+ icon="el-icon-plus"
+ >
+ 鏂板璁板綍鍒�
+ </el-button>
+ </div>
+
+ <el-table
+ :data="tableData"
+ border
+ style="width: 100%"
+ class="medical-table"
+ :key="tableKey"
+ v-fit-columns
+ @header-dragend="handleHeaderDragEnd"
+ >
+ <el-table-column
+ prop="itemName"
+ label="妫�娴嬮」鐩�"
+ width="200"
+ fixed
+ class-name="leave-alone"
+ >
+ <template #default="scope">
+ <span :class="{'required-item': scope.row.required}">
+ {{ scope.row.itemName }}
+ </span>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ v-for="(col, index) in dynamicColumns"
+ :key="col.key"
+ :label="col.label"
+ :min-width="120"
+ :resizable="isEditing"
+ >
+ <template #default="scope">
+ <div class="cell-content">
+ <el-input
+ v-if="isEditing"
+ v-model="scope.row.values[index]"
+ size="small"
+ :placeholder="`璇疯緭鍏�${scope.row.unit ? scope.row.unit : '鏁板��'}`"
+ @blur="handleValueChange(scope.row, index)"
+ class="value-input"
+ />
+ <span v-else class="value-display">
+ {{ scope.row.values[index] || '-' }}
+ <span v-if="scope.row.values[index] && scope.row.unit" class="unit">
+ {{ scope.row.unit }}
+ </span>
+ </span>
+ <span v-if="scope.row.reference" class="reference-range">
+ ({{ scope.row.reference }})
+ </span>
+ </div>
+ </template>
+ </el-table-column>
+
+
+ </el-table>
+
+ <!-- 闄勪欢涓婁紶鍖哄煙 -->
+ <div class="attachment-section">
+ <div class="attachment-title">
+ <i class="el-icon-paperclip"></i>
+ 闄勪欢涓婁紶
+ <span class="attachment-tip">鏀寔涓婁紶妫�楠屾姤鍛婂崟绛夋枃浠�</span>
+ </div>
+ <upload-attachment
+ :file-list="attachments"
+ @change="handleAttachmentChange"
+ :limit="10"
+ :accept="'.pdf,.jpg,.jpeg,.png,.doc,.docx'"
+ />
+ </div>
+
+ <!-- 鍒楃紪杈戝璇濇 -->
+ <el-dialog
+ title="缂栬緫鏃堕棿鐐�"
+ :visible.sync="columnDialogVisible"
+ width="400px"
+ @closed="handleDialogClosed"
+ >
+ <el-form :model="columnForm" label-width="80px" ref="columnForm">
+ <el-form-item
+ label="鏃ユ湡"
+ prop="date"
+ :rules="[{ required: true, message: '璇烽�夋嫨鏃ユ湡', trigger: 'change' }]"
+ >
+ <el-date-picker
+ v-model="columnForm.date"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item
+ label="鏃堕棿"
+ prop="time"
+ :rules="[{ required: true, message: '璇烽�夋嫨鏃堕棿', trigger: 'change' }]"
+ >
+ <el-time-picker
+ v-model="columnForm.time"
+ placeholder="閫夋嫨鏃堕棿"
+ value-format="HH:mm"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-form>
+ <span slot="footer" class="dialog-footer">
+ <el-button @click="columnDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="confirmAddColumn">纭畾</el-button>
+ </span>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import UploadAttachment from "@/components/UploadAttachment";
+
+export default {
+ name: 'LiverKidneyPanel',
+ components: {
+ UploadAttachment,
+ },
+ props: {
+ isEditing: {
+ type: Boolean,
+ default: false
+ },
+ initialData: {
+ type: Array,
+ default: () => []
+ }
+ },
+ data() {
+ return {
+ tableData: [],
+ dynamicColumns: [
+ {
+ label: '2024-12-27\n08:00',
+ key: 'time1',
+ date: '2024-12-27',
+ time: '08:00'
+ },
+ {
+ label: '2024-12-27\n14:00',
+ key: 'time2',
+ date: '2024-12-27',
+ time: '14:00'
+ }
+ ],
+ attachments: [],
+ columnDialogVisible: false,
+ columnForm: {
+ date: '',
+ time: ''
+ },
+ tableKey: 0 // 鐢ㄤ簬寮哄埗閲嶆柊娓叉煋琛ㄦ牸
+ };
+ },
+ watch: {
+ isEditing(newVal) {
+ if (!newVal) {
+ // 淇濆瓨鏁版嵁
+ this.$emit('data-change', {
+ type: 'liver_kidney',
+ data: this.tableData,
+ columns: this.dynamicColumns,
+ attachments: this.attachments
+ });
+ }
+ // 缂栬緫妯″紡鍒囨崲鏃堕噸鏂拌绠楀垪瀹�
+ this.$nextTick(() => {
+ this.forceTableLayout();
+ });
+ },
+ dynamicColumns: {
+ handler() {
+ // 鍒楀彉鍖栨椂閲嶆柊璁$畻甯冨眬
+ this.$nextTick(() => {
+ this.forceTableLayout();
+ });
+ },
+ deep: true
+ }
+ },
+ methods: {
+ initTableData() {
+ const medicalItems = [
+ {
+ itemName: '琛�閽�',
+ unit: 'mmol/L',
+ required: true,
+ reference: '135-145'
+ },
+ {
+ itemName: '琛�閽�',
+ unit: 'mmol/L',
+ required: true,
+ reference: '3.5-5.5'
+ },
+ {
+ itemName: 'BUN',
+ unit: 'mg/dL',
+ required: true,
+ reference: '<20'
+ },
+ {
+ itemName: '鑲岄厫',
+ unit: '渭mol/L',
+ required: true,
+ reference: '<100'
+ },
+ {
+ itemName: '鎬昏儐绾㈢礌',
+ unit: '渭mol/L',
+ required: true,
+ reference: '<21'
+ },
+ {
+ itemName: 'ALT',
+ unit: 'U/L',
+ required: true,
+ reference: '<50'
+ },
+ {
+ itemName: 'AST',
+ unit: 'U/L',
+ required: true,
+ reference: '<40'
+ },
+ {
+ itemName: 'GGT',
+ unit: 'U/L',
+ required: true,
+ reference: '<57'
+ },
+ {
+ itemName: 'ALP',
+ unit: 'U/L',
+ required: true,
+ reference: '<120'
+ },
+ {
+ itemName: 'PT',
+ unit: '绉�',
+ required: true,
+ reference: '9.4-12.5'
+ },
+ {
+ itemName: 'INR',
+ unit: '',
+ required: true,
+ reference: '0.85-1.15'
+ }
+ ];
+
+ this.tableData = medicalItems.map(item => ({
+ ...item,
+ values: new Array(this.dynamicColumns.length).fill('')
+ }));
+ },
+
+ addColumn() {
+ this.columnForm = {
+ date: new Date().toISOString().split('T')[0],
+ time: '08:00'
+ };
+ this.columnDialogVisible = true;
+ },
+
+ confirmAddColumn() {
+ this.$refs.columnForm.validate((valid) => {
+ if (!valid) {
+ this.$message.warning('璇峰畬鍠勬椂闂寸偣淇℃伅');
+ return;
+ }
+
+ const newIndex = this.dynamicColumns.length + 1;
+ const newColumn = {
+ label: `${this.columnForm.date}\n${this.columnForm.time}`,
+ key: `time${newIndex}`,
+ date: this.columnForm.date,
+ time: this.columnForm.time
+ };
+
+ this.dynamicColumns.push(newColumn);
+
+ // 涓烘墍鏈夎鏂板涓�涓┖鍊�
+ this.tableData.forEach(row => {
+ row.values.push('');
+ });
+
+ this.columnDialogVisible = false;
+ this.$message.success('鏃堕棿鐐规坊鍔犳垚鍔�');
+
+ // 寮哄埗琛ㄦ牸閲嶆柊娓叉煋
+ this.tableKey += 1;
+ });
+ },
+
+ handleDialogClosed() {
+ this.columnForm = {
+ date: '',
+ time: ''
+ };
+ this.$refs.columnForm && this.$refs.columnForm.clearValidate();
+ },
+
+ handleValueChange(row, columnIndex) {
+ this.$emit('data-change', {
+ type: 'liver_kidney',
+ data: this.tableData,
+ columns: this.dynamicColumns
+ });
+ },
+
+ handleAttachmentChange(fileList) {
+ this.attachments = fileList;
+ this.$emit('attachment-change', {
+ type: 'liver_kidney',
+ attachments: fileList
+ });
+ },
+
+ // 寮哄埗琛ㄦ牸閲嶆柊甯冨眬[1,3](@ref)
+ forceTableLayout() {
+ this.$nextTick(() => {
+ const table = this.$el.querySelector('.el-table');
+ if (table && table.querySelector('colgroup')) {
+ // 瑙﹀彂琛ㄦ牸閲嶆柊璁$畻甯冨眬
+ this.$nextTick(() => {
+ window.dispatchEvent(new Event('resize'));
+ });
+ }
+ });
+ },
+
+ handleHeaderDragEnd() {
+ // 鍒楀鎷栨嫿缁撴潫鍚庨噸鏂拌绠楀竷灞�
+ this.forceTableLayout();
+ },
+
+ exportData() {
+ return {
+ tableData: this.tableData,
+ columns: this.dynamicColumns,
+ exportTime: new Date().toISOString(),
+ attachments: this.attachments
+ };
+ }
+ },
+ mounted() {
+ this.initTableData();
+ // 鍒濆娓叉煋鍚庤绠楀垪瀹�
+ this.$nextTick(() => {
+ this.forceTableLayout();
+ });
+ }
+};
+</script>
+
+<style scoped>
+.medical-panel {
+ padding: 20px;
+ background: #fff;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.panel-header h3 {
+ margin: 0;
+ color: #303133;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.medical-table {
+ margin-bottom: 30px;
+ min-width: 100%;
+}
+
+.medical-table ::v-deep .el-table__body-wrapper {
+ overflow-x: auto;
+}
+
+.cell-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 32px;
+}
+
+.value-input {
+ flex: 1;
+ margin-right: 8px;
+}
+
+.value-display {
+ font-weight: 500;
+ color: #303133;
+}
+
+.unit {
+ color: #909399;
+ font-size: 12px;
+ margin-left: 4px;
+}
+
+.reference-range {
+ color: #67c23a;
+ font-size: 12px;
+ font-style: italic;
+ margin-left: 8px;
+}
+
+.required-item::before {
+ content: "*";
+ color: #f56c6c;
+ margin-right: 4px;
+}
+
+.attachment-section {
+ margin-top: 30px;
+ padding: 20px;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+ background: #fafafa;
+}
+
+.attachment-title {
+ font-weight: bold;
+ margin-bottom: 15px;
+ color: #409eff;
+ display: flex;
+ align-items: center;
+}
+
+.attachment-tip {
+ font-size: 12px;
+ color: #909399;
+ margin-left: 10px;
+ font-weight: normal;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .medical-panel {
+ padding: 10px;
+ }
+
+ .panel-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .panel-header h3 {
+ margin-bottom: 10px;
+ }
+
+ .cell-content {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .reference-range {
+ margin-left: 0;
+ margin-top: 4px;
+ }
+}
+</style>
diff --git a/src/views/business/maintain/components/UrineRoutinePanel.vue b/src/views/business/maintain/components/UrineRoutinePanel.vue
new file mode 100644
index 0000000..4d62072
--- /dev/null
+++ b/src/views/business/maintain/components/UrineRoutinePanel.vue
@@ -0,0 +1,751 @@
+<template>
+ <div class="medical-panel">
+ <div class="panel-header">
+ <h3>灏垮父瑙勮褰曡〃</h3>
+ <div class="panel-actions">
+ <el-button
+ v-if="isEditing"
+ type="primary"
+ size="small"
+ @click="addColumn"
+ icon="el-icon-plus"
+ >
+ 鏂板璁板綍鍒�
+ </el-button>
+ <el-tooltip content="鍒锋柊琛ㄦ牸甯冨眬" placement="top">
+ <el-button
+ size="small"
+ @click="forceTableLayout"
+ icon="el-icon-refresh"
+ >
+ 鍒锋柊甯冨眬
+ </el-button>
+ </el-tooltip>
+ </div>
+ </div>
+
+ <el-table
+ :data="tableData"
+ border
+ style="width: 100%"
+ class="medical-table"
+ v-fit-columns
+ :key="tableKey"
+ @header-dragend="handleHeaderDragEnd"
+ v-loading="tableLoading"
+ >
+ <el-table-column
+ prop="itemName"
+ label="妫�娴嬮」鐩�"
+ width="220"
+ fixed
+ class-name="leave-alone"
+ >
+ <template #default="scope">
+ <div class="item-name-cell">
+ <span :class="{'required-item': scope.row.required}">
+ {{ scope.row.itemName }}
+ </span>
+ <el-tooltip
+ v-if="scope.row.reference"
+ :content="`鍙傝�冭寖鍥�: ${scope.row.reference}`"
+ placement="top"
+ >
+ <i class="el-icon-info reference-icon"></i>
+ </el-tooltip>
+ </div>
+ </template>
+ </el-table-column>
+
+ <el-table-column
+ v-for="(col, index) in dynamicColumns"
+ :key="col.key"
+ :label="col.label"
+ :min-width="160"
+ :resizable="isEditing"
+ header-align="center"
+ align="center"
+ >
+ <template #default="scope">
+ <div class="cell-content-wrapper">
+ <template v-if="scope.row.type === 'select'">
+ <el-select
+ v-if="isEditing"
+ v-model="scope.row.values[index]"
+ placeholder="璇烽�夋嫨"
+ size="small"
+ style="width: 100%"
+ @change="handleValueChange(scope.row, index)"
+ :title="getSelectLabel(scope.row, index)"
+ >
+ <el-option
+ v-for="option in scope.row.options"
+ :key="option.value"
+ :label="option.label"
+ :value="option.value"
+ />
+ </el-select>
+ <span v-else class="value-text" :title="getSelectLabel(scope.row, index)">
+ {{ getSelectLabel(scope.row, index) || '-' }}
+ </span>
+ </template>
+
+ <template v-else>
+ <el-input
+ v-if="isEditing"
+ v-model="scope.row.values[index]"
+ size="small"
+ :placeholder="scope.row.placeholder || '璇疯緭鍏�'"
+ @blur="handleValueChange(scope.row, index)"
+ class="value-input"
+ :title="scope.row.values[index]"
+ />
+ <div v-else class="value-display-container">
+ <span class="value-text" :title="scope.row.values[index]">
+ {{ scope.row.values[index] || '-' }}
+ </span>
+ <span v-if="scope.row.values[index] && scope.row.unit" class="unit-text">
+ {{ scope.row.unit }}
+ </span>
+ </div>
+ </template>
+ </div>
+ </template>
+ </el-table-column>
+
+ <!-- <el-table-column
+ v-if="isEditing"
+ label="鎿嶄綔"
+ width="120"
+ fixed="right"
+ class-name="leave-alone"
+ >
+ <template #default>
+ <el-button link type="primary" @click="addColumn" size="small">
+ 鏂板鍒�
+ </el-button>
+ </template>
+ </el-table-column> -->
+ </el-table>
+
+ <!-- 缁熻淇℃伅 -->
+ <div v-if="showStatistics" class="statistics-section">
+ <el-card shadow="never">
+ <div class="stats-content">
+ <span class="stats-title">鏁版嵁缁熻:</span>
+ <span class="stats-item">鎬昏褰曟暟: {{ dynamicColumns.length }} 涓椂闂寸偣</span>
+ <span class="stats-item">宸插~鍐�: {{ filledCount }} 椤�</span>
+ <span class="stats-item">瀹屾垚搴�: {{ completionRate }}%</span>
+ </div>
+ </el-card>
+ </div>
+
+ <!-- 闄勪欢涓婁紶鍖哄煙 -->
+ <div class="attachment-section">
+ <div class="attachment-header">
+ <i class="el-icon-paperclip"></i>
+ <span class="attachment-title">闄勪欢涓婁紶</span>
+ <span class="attachment-tip">鏀寔涓婁紶灏垮父瑙勬楠屾姤鍛婂崟绛夋枃浠� (鏈�澶�10涓�)</span>
+ </div>
+ <upload-attachment
+ :file-list="attachments"
+ @change="handleAttachmentChange"
+ :limit="10"
+ :accept="'.pdf,.jpg,.jpeg,.png,.doc,.docx'"
+ />
+ </div>
+
+ <!-- 鍒楃紪杈戝璇濇 -->
+ <el-dialog
+ :title="`${editingColumnIndex !== null ? '缂栬緫' : '鏂板'}鏃堕棿鐐筦"
+ :visible.sync="columnDialogVisible"
+ width="450px"
+ @closed="handleDialogClosed"
+ >
+ <el-form
+ :model="columnForm"
+ label-width="80px"
+ ref="columnForm"
+ :rules="columnRules"
+ >
+ <el-form-item label="鏃ユ湡" prop="date">
+ <el-date-picker
+ v-model="columnForm.date"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="yyyy-MM-dd"
+ style="width: 100%"
+ :disabled-date="disableFutureDates"
+ />
+ </el-form-item>
+ <el-form-item label="鏃堕棿" prop="time">
+ <el-time-picker
+ v-model="columnForm.time"
+ placeholder="閫夋嫨鏃堕棿"
+ value-format="HH:mm"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ v-model="columnForm.remark"
+ type="textarea"
+ :rows="2"
+ placeholder="鍙�夊~鍐欐椂闂寸偣澶囨敞淇℃伅"
+ maxlength="100"
+ show-word-limit
+ />
+ </el-form-item>
+ </el-form>
+ <span slot="footer" class="dialog-footer">
+ <el-button @click="columnDialogVisible = false">鍙栨秷</el-button>
+ <el-button
+ v-if="editingColumnIndex !== null"
+ type="danger"
+ @click="handleDeleteColumn"
+ >
+ 鍒犻櫎
+ </el-button>
+ <el-button type="primary" @click="confirmAddColumn" :loading="saveLoading">
+ 纭畾
+ </el-button>
+ </span>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import UploadAttachment from "@/components/UploadAttachment";
+
+export default {
+ name: 'UrineRoutinePanel',
+ components: {
+ UploadAttachment,
+ },
+ props: {
+ isEditing: {
+ type: Boolean,
+ default: false
+ },
+ initialData: {
+ type: Array,
+ default: () => []
+ },
+ showStatistics: {
+ type: Boolean,
+ default: true
+ }
+ },
+ data() {
+ return {
+ tableData: [],
+ dynamicColumns: [
+ {
+ label: '2024-12-27\n08:00',
+ key: 'time1',
+ date: '2024-12-27',
+ time: '08:00',
+ remark: '鏅ㄥ翱妫�娴�'
+ }
+ ],
+ attachments: [],
+ columnDialogVisible: false,
+ columnForm: {
+ date: '',
+ time: '',
+ remark: ''
+ },
+ editingColumnIndex: null,
+ tableKey: 0,
+ tableLoading: false,
+ saveLoading: false,
+ columnRules: {
+ date: [
+ { required: true, message: '璇烽�夋嫨鏃ユ湡', trigger: 'change' }
+ ],
+ time: [
+ { required: true, message: '璇烽�夋嫨鏃堕棿', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ computed: {
+ filledCount() {
+ let count = 0;
+ this.tableData.forEach(row => {
+ row.values.forEach(value => {
+ if (value && value.toString().trim() !== '') {
+ count++;
+ }
+ });
+ });
+ return count;
+ },
+ completionRate() {
+ const total = this.tableData.length * this.dynamicColumns.length;
+ return total > 0 ? Math.round((this.filledCount / total) * 100) : 0;
+ }
+ },
+ watch: {
+ isEditing(newVal) {
+ if (!newVal) {
+ this.$emit('data-change', {
+ type: 'urine_routine',
+ data: this.tableData,
+ columns: this.dynamicColumns,
+ attachments: this.attachments
+ });
+ }
+ this.$nextTick(() => {
+ this.forceTableLayout();
+ });
+ },
+ dynamicColumns: {
+ handler() {
+ this.$nextTick(() => {
+ this.forceTableLayout();
+ });
+ },
+ deep: true,
+ immediate: true
+ }
+ },
+ methods: {
+ initTableData() {
+ const medicalItems = [
+
+ {
+ itemName: '灏块噺',
+ type: 'number',
+ required: true,
+ unit: 'ml/h',
+ reference: '姝e父鑼冨洿瑙嗘儏鍐佃�屽畾'
+ },
+ {
+ itemName: '棰滆壊',
+ type: 'select',
+ required: true,
+ options: [
+ { value: '娣¢粍鑹�', label: '娣¢粍鑹�' },
+ { value: '榛勮壊', label: '榛勮壊' },
+ { value: '娣遍粍鑹�', label: '娣遍粍鑹�' },
+ { value: '绾㈣壊', label: '绾㈣壊' },
+ { value: '鐧借壊', label: '鐧借壊' },
+ { value: '鍏朵粬', label: '鍏朵粬' }
+ ]
+ },
+ {
+ itemName: '澶栬',
+ type: 'select',
+ required: false,
+ options: [
+ { value: '娓呬寒', label: '娓呬寒' },
+ { value: '寰祳', label: '寰祳' },
+ { value: '娴戞祳', label: '娴戞祳' },
+ { value: '娌夋穩', label: '娌夋穩' },
+ { value: '鍏朵粬', label: '鍏朵粬' }
+ ]
+ },
+ {
+ itemName: '灏胯泲鐧�',
+ type: 'select',
+ required: true,
+ options: [
+ { value: '-', label: '闃存��(-)' },
+ { value: '卤', label: '寰噺(卤)' },
+ { value: '+', label: '闃虫��(+)' },
+ { value: '++', label: '闃虫��(++)' },
+ { value: '+++', label: '闃虫��(+++)' }
+ ],
+ reference: '姝e父涓洪槾鎬�(-)'
+ },
+ {
+ itemName: 'pH鍊�',
+ type: 'number',
+ required: true,
+ placeholder: '4.5-8.0',
+ unit: '',
+ reference: '4.5-8.0',
+ min: 4.5,
+ max: 8.0
+ },
+ {
+ itemName: '鐧界粏鑳�',
+ type: 'select',
+ required: true,
+ options: [
+ { value: '-', label: '闃存��(-)' },
+ { value: '+', label: '闃虫��(+)' },
+ { value: '++', label: '闃虫��(++)' },
+ { value: '+++', label: '闃虫��(+++)' }
+ ],
+ reference: '姝e父涓洪槾鎬�(-)'
+ },
+ {
+ itemName: '绾㈢粏鑳�',
+ type: 'number',
+ required: true,
+ unit: '/渭L',
+ reference: '0-9.2',
+ min: 0,
+ max: 9.2
+ },
+ {
+ itemName: '缁嗚弻',
+ type: 'number',
+ required: true,
+ unit: '/渭L',
+ reference: '0-385',
+ min: 0,
+ max: 385
+ }
+ ];
+
+ this.tableData = medicalItems.map(item => ({
+ ...item,
+ values: new Array(this.dynamicColumns.length).fill('')
+ }));
+ },
+
+ getSelectLabel(row, columnIndex) {
+ if (!row.options || !row.values[columnIndex]) return row.values[columnIndex];
+ const option = row.options.find(opt => opt.value === row.values[columnIndex]);
+ return option ? option.label : row.values[columnIndex];
+ },
+
+ addColumn() {
+ this.editingColumnIndex = null;
+ this.columnForm = {
+ date: new Date().toISOString().split('T')[0],
+ time: '08:00',
+ remark: ''
+ };
+ this.columnDialogVisible = true;
+ this.$nextTick(() => {
+ this.$refs.columnForm && this.$refs.columnForm.clearValidate();
+ });
+ },
+
+ editColumn(index) {
+ this.editingColumnIndex = index;
+ const column = this.dynamicColumns[index];
+ this.columnForm = {
+ date: column.date,
+ time: column.time,
+ remark: column.remark || ''
+ };
+ this.columnDialogVisible = true;
+ },
+
+ confirmAddColumn() {
+ this.$refs.columnForm.validate((valid) => {
+ if (!valid) {
+ this.$message.warning('璇峰畬鍠勬椂闂寸偣淇℃伅');
+ return;
+ }
+
+ this.saveLoading = true;
+
+ if (this.editingColumnIndex !== null) {
+ // 缂栬緫鐜版湁鍒�
+ const column = this.dynamicColumns[this.editingColumnIndex];
+ column.label = `${this.columnForm.date}\n${this.columnForm.time}`;
+ column.date = this.columnForm.date;
+ column.time = this.columnForm.time;
+ column.remark = this.columnForm.remark;
+ this.$message.success('鏃堕棿鐐逛慨鏀规垚鍔�');
+ } else {
+ // 鏂板鍒�
+ const newIndex = this.dynamicColumns.length + 1;
+ const newColumn = {
+ label: `${this.columnForm.date}\n${this.columnForm.time}`,
+ key: `time${Date.now()}`,
+ date: this.columnForm.date,
+ time: this.columnForm.time,
+ remark: this.columnForm.remark
+ };
+
+ this.dynamicColumns.push(newColumn);
+ this.tableData.forEach(row => {
+ row.values.push('');
+ });
+ this.$message.success('鏃堕棿鐐规坊鍔犳垚鍔�');
+ }
+
+ this.columnDialogVisible = false;
+ this.saveLoading = false;
+ this.tableKey += 1;
+ });
+ },
+
+ handleDeleteColumn() {
+ if (this.editingColumnIndex !== null) {
+ this.$confirm('纭畾瑕佸垹闄よ繖涓椂闂寸偣鍚楋紵', '鎻愮ず', {
+ confirmButtonText: '纭畾',
+ cancelButtonText: '鍙栨秷',
+ type: 'warning'
+ }).then(() => {
+ this.dynamicColumns.splice(this.editingColumnIndex, 1);
+ this.tableData.forEach(row => {
+ row.values.splice(this.editingColumnIndex, 1);
+ });
+ this.columnDialogVisible = false;
+ this.tableKey += 1;
+ this.$message.success('鏃堕棿鐐瑰垹闄ゆ垚鍔�');
+ });
+ }
+ },
+
+ handleDialogClosed() {
+ this.editingColumnIndex = null;
+ this.columnForm = {
+ date: '',
+ time: '',
+ remark: ''
+ };
+ this.$refs.columnForm && this.$refs.columnForm.clearValidate();
+ },
+
+ disableFutureDates(time) {
+ return time.getTime() > Date.now();
+ },
+
+ handleValueChange(row, columnIndex) {
+ this.$emit('data-change', {
+ type: 'urine_routine',
+ data: this.tableData,
+ columns: this.dynamicColumns
+ });
+ },
+
+ handleAttachmentChange(fileList) {
+ this.attachments = fileList;
+ this.$emit('attachment-change', {
+ type: 'urine_routine',
+ attachments: fileList
+ });
+ },
+
+ forceTableLayout() {
+ this.$nextTick(() => {
+ const table = this.$el.querySelector('.el-table');
+ if (table) {
+ window.dispatchEvent(new Event('resize'));
+ }
+ });
+ },
+
+ handleHeaderDragEnd() {
+ this.forceTableLayout();
+ },
+
+ exportData() {
+ return {
+ tableData: this.tableData,
+ columns: this.dynamicColumns,
+ statistics: {
+ filledCount: this.filledCount,
+ completionRate: this.completionRate,
+ totalColumns: this.dynamicColumns.length
+ },
+ exportTime: new Date().toISOString(),
+ attachments: this.attachments
+ };
+ }
+ },
+ mounted() {
+ this.initTableData();
+ }
+};
+</script>
+
+<style scoped>
+.medical-panel {
+ padding: 20px;
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+ padding-bottom: 16px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.panel-header h3 {
+ margin: 0;
+ color: #303133;
+ font-size: 20px;
+ font-weight: 600;
+}
+
+.panel-actions {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.medical-table {
+ margin-bottom: 24px;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.medical-table ::v-deep .el-table__body-wrapper {
+ overflow-x: auto;
+}
+
+.item-name-cell {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.reference-icon {
+ color: #909399;
+ font-size: 14px;
+ cursor: help;
+ margin-left: 8px;
+}
+
+.required-item::before {
+ content: "*";
+ color: #f56c6c;
+ margin-right: 4px;
+}
+
+.cell-content-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-height: 32px;
+}
+
+.value-input {
+ flex: 1;
+ min-width: 80px;
+}
+
+.value-display-container {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.value-text {
+ font-weight: 500;
+ color: #303133;
+ max-width: 80px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.unit-text {
+ color: #909399;
+ font-size: 12px;
+ flex-shrink: 0;
+}
+
+.validation-indicator {
+ flex-shrink: 0;
+}
+
+.valid-icon {
+ color: #67c23a;
+ font-size: 14px;
+}
+
+.invalid-icon {
+ color: #e6a23c;
+ font-size: 14px;
+}
+
+.statistics-section {
+ margin-bottom: 20px;
+}
+
+.stats-content {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.stats-title {
+ font-weight: 600;
+ color: #303133;
+}
+
+.stats-item {
+ color: #606266;
+ font-size: 14px;
+}
+
+.attachment-section {
+ margin-top: 24px;
+ padding: 20px;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+ background: #fafafa;
+}
+
+.attachment-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.attachment-title {
+ font-weight: 600;
+ color: #409eff;
+}
+
+.attachment-tip {
+ font-size: 12px;
+ color: #909399;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+ .medical-panel {
+ padding: 12px;
+ }
+
+ .panel-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 12px;
+ }
+
+ .panel-actions {
+ width: 100%;
+ justify-content: flex-end;
+ }
+
+ .stats-content {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ }
+
+ .cell-content-wrapper {
+ flex-direction: column;
+ gap: 4px;
+ }
+}
+
+/* 鍔ㄧ敾鏁堟灉 */
+.fade-enter-active, .fade-leave-active {
+ transition: opacity 0.3s;
+}
+.fade-enter, .fade-leave-to {
+ opacity: 0;
+}
+</style>
diff --git a/src/views/business/maintain/maintainInfo.vue b/src/views/business/maintain/maintainInfo.vue
index 3f88144..3bfd5bb 100644
--- a/src/views/business/maintain/maintainInfo.vue
+++ b/src/views/business/maintain/maintainInfo.vue
@@ -28,7 +28,11 @@
</el-col>
<el-col :span="8">
<el-form-item label="鎬у埆" prop="gender">
- <el-select v-model="form.gender" :disabled="!isEdit" style="width: 100%">
+ <el-select
+ v-model="form.gender"
+ :disabled="!isEdit"
+ style="width: 100%"
+ >
<el-option label="鐢�" value="0" />
<el-option label="濂�" value="1" />
</el-select>
@@ -57,7 +61,11 @@
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="鎮h�呯姸鎬�" prop="patientStatus">
- <el-select v-model="form.patientStatus" :disabled="!isEdit" style="width: 100%">
+ <el-select
+ v-model="form.patientStatus"
+ :disabled="!isEdit"
+ style="width: 100%"
+ >
<el-option label="DCD" value="1" />
<el-option label="DBD" value="2" />
<el-option label="DBCD" value="3" />
@@ -67,7 +75,11 @@
</el-form-item>
</el-col>
<el-col :span="8">
- <el-form-item label="鏈畬鎴愬師鍥�" prop="incompleteReason" v-if="form.patientStatus === '5'">
+ <el-form-item
+ label="鏈畬鎴愬師鍥�"
+ prop="incompleteReason"
+ v-if="form.patientStatus === '5'"
+ >
<el-input
v-model="form.incompleteReason"
:readonly="!isEdit"
@@ -110,7 +122,11 @@
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="琛�鍨�" prop="bloodType">
- <el-select v-model="form.bloodType" :disabled="!isEdit" style="width: 100%">
+ <el-select
+ v-model="form.bloodType"
+ :disabled="!isEdit"
+ style="width: 100%"
+ >
<el-option label="A鍨�" value="A" />
<el-option label="B鍨�" value="B" />
<el-option label="O鍨�" value="O" />
@@ -120,7 +136,11 @@
</el-col>
<el-col :span="8">
<el-form-item label="RH鍥犲瓙" prop="rhFactor">
- <el-select v-model="form.rhFactor" :disabled="!isEdit" style="width: 100%">
+ <el-select
+ v-model="form.rhFactor"
+ :disabled="!isEdit"
+ style="width: 100%"
+ >
<el-option label="闃虫��" value="positive" />
<el-option label="闃存��" value="negative" />
</el-select>
@@ -139,75 +159,146 @@
</el-form-item>
</el-form>
</el-card>
-
- <!-- 鍩瑰吇缁撴灉璁板綍 -->
- <!-- 鍩瑰吇缁撴灉璁板綍 -->
- <el-card class="culture-card">
+ <el-card class="assessment-card">
<div slot="header" class="clearfix">
- <span class="detail-title">鍩瑰吇缁撴灉璁板綍</span>
+ <span class="detail-title">渚涜�呰瘎浼板悇椤硅褰�</span>
<el-button
type="primary"
size="mini"
- icon="el-icon-plus"
- @click="handleAddCulture"
+ @click="toggleEditMode"
+ style="float: right;"
>
- 鏂板鍩瑰吇璁板綍
+ {{ isEdit ? "瀹屾垚缂栬緫" : "寮�濮嬬紪杈�" }}
</el-button>
</div>
- <el-table :data="cultureList" v-loading="cultureLoading">
- <el-table-column label="鍩瑰吇绫诲瀷" align="center" prop="cultureType" width="120">
- <template slot-scope="scope">
- <dict-tag :options="cultureTypeOptions" :value="scope.row.cultureType" />
- </template>
- </el-table-column>
- <el-table-column label="閲囨牱鏃堕棿" align="center" prop="sampleTime" width="160" />
- <el-table-column label="鍩瑰吇缁撴灉" align="center" prop="result" width="100">
- <template slot-scope="scope">
- <el-tag :type="scope.row.result === '闃存��' ? 'success' : 'danger'" effect="plain">
- {{ scope.row.result }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="鑿岀" align="center" prop="bacteria" width="120" />
- <el-table-column label="鑽晱缁撴灉" align="center" prop="drugSensitivity" min-width="150" show-overflow-tooltip />
- <el-table-column label="妫�娴嬫満鏋�" align="center" prop="testingInstitution" width="120" />
- <el-table-column label="鎿嶄綔" align="center" width="180" class-name="small-padding fixed-width">
- <template slot-scope="scope">
- <el-button
- size="mini"
- type="text"
- icon="el-icon-edit"
- @click="handleEditCulture(scope.row)"
- >缂栬緫</el-button>
- <el-button
- size="mini"
- type="text"
- icon="el-icon-delete"
- style="color: #F56C6C;"
- @click="handleDeleteCulture(scope.row)"
- >鍒犻櫎</el-button>
- <el-button
- size="mini"
- type="text"
- icon="el-icon-view"
- @click="handleViewCulture(scope.row)"
- >璇︽儏</el-button>
- </template>
- </el-table-column>
- </el-table>
+ <el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
+ <!-- 鍩瑰吇缁撴灉璁板綍 -->
+ <el-tab-pane label="鍩瑰吇缁撴灉" name="culture">
+ <el-card class="culture-card">
+ <div slot="header" class="clearfix">
+ <span class="detail-title">鍩瑰吇缁撴灉璁板綍</span>
+ <el-button
+ type="primary"
+ size="mini"
+ icon="el-icon-plus"
+ @click="handleAddCulture"
+ >
+ 鏂板鍩瑰吇璁板綍
+ </el-button>
+ </div>
- <!-- 鍒嗛〉缁勪欢 -->
- <pagination
- v-show="cultureTotal > 0"
- :total="cultureTotal"
- :page.sync="cultureQueryParams.pageNum"
- :limit.sync="cultureQueryParams.pageSize"
- @pagination="getCultureList"
- />
+ <el-table :data="cultureList" v-loading="cultureLoading">
+ <el-table-column
+ label="鍩瑰吇绫诲瀷"
+ align="center"
+ prop="cultureType"
+ >
+ <!-- <template slot-scope="scope">
+ <dict-tag
+ :options="cultureTypeOptions"
+ :value="scope.row.cultureType"
+ />
+ </template> -->
+ </el-table-column>
+ <el-table-column
+ label="閲囨牱鏃堕棿"
+ align="center"
+ prop="sampleTime"
+ />
+ <el-table-column label="鍩瑰吇缁撴灉" align="center" prop="result">
+ <template slot-scope="scope">
+ <el-tag
+ :type="scope.row.result === '闃存��' ? 'success' : 'danger'"
+ effect="plain"
+ >
+ {{ scope.row.result }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <!-- 闄勪欢鍒� -->
+ <el-table-column label="闄勪欢" align="center">
+ <template slot-scope="scope">
+ <el-button
+ v-if="
+ scope.row.attachments && scope.row.attachments.length > 0
+ "
+ size="mini"
+ type="text"
+ @click="handleViewCultureAttachments(scope.row)"
+ >
+ 鏌ョ湅闄勪欢({{ scope.row.attachments.length }})
+ </el-button>
+ <span v-else>鏃犻檮浠�</span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="200"
+ class-name="small-padding fixed-width"
+ >
+ <template slot-scope="scope">
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-edit"
+ @click="handleEditCulture(scope.row)"
+ >缂栬緫</el-button
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C;"
+ @click="handleDeleteCulture(scope.row)"
+ >鍒犻櫎</el-button
+ >
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ v-show="cultureTotal > 0"
+ :total="cultureTotal"
+ :page.sync="cultureQueryParams.pageNum"
+ :limit.sync="cultureQueryParams.pageSize"
+ @pagination="getCultureList"
+ />
+ </el-card>
+ </el-tab-pane>
+
+ <!-- 鑲濆姛鑳借偩鍔熻兘 -->
+ <el-tab-pane label="鑲濆姛鑳借偩鍔熻兘" name="liverKidney">
+ <liver-kidney-panel
+ ref="liverKidney"
+ :is-editing="isEdit && activeTab === 'liverKidney'"
+ @data-change="handleLiverKidneyDataChange"
+ />
+ </el-tab-pane>
+
+ <!-- 琛�甯歌 -->
+ <el-tab-pane label="琛�甯歌" name="bloodRoutine">
+ <blood-routine-panel
+ ref="bloodRoutine"
+ :is-editing="isEdit && activeTab === 'bloodRoutine'"
+ @data-change="handleBloodRoutineDataChange"
+ />
+ </el-tab-pane>
+
+ <!-- 灏垮父瑙� -->
+ <el-tab-pane label="灏垮父瑙�" name="urineRoutine">
+ <urine-routine-panel
+ ref="urineRoutine"
+ :is-editing="isEdit && activeTab === 'urineRoutine'"
+ @data-change="handleUrineRoutineDataChange"
+ />
+ </el-tab-pane>
+ </el-tabs>
</el-card>
+ <!-- 鍩瑰吇缁撴灉璁板綍 -->
- <!-- 鎶ょ悊鏍告煡璁板綍 -->
+ <!-- 绠�鍖栧悗鐨勬姢鐞嗘牳鏌ヨ褰� -->
<el-card class="record-card">
<div slot="header" class="clearfix">
<span class="detail-title">鎶ょ悊鏍告煡璁板綍</span>
@@ -222,40 +313,63 @@
</div>
<el-table :data="recordList" v-loading="recordLoading">
- <el-table-column label="鏍告煡鏃堕棿" align="center" prop="recordTime" width="160" />
- <el-table-column label="鏍告煡浜�" align="center" prop="recorder" width="100" />
- <el-table-column label="浣撴俯(鈩�)" align="center" prop="temperature" />
- <el-table-column label="蹇冪巼(娆�/鍒�)" align="center" prop="heartRate" />
- <el-table-column label="琛�鍘�(mmHg)" align="center" prop="bloodPressure" width="160" />
- <el-table-column label="鍛煎惛(娆�/鍒�)" align="center" prop="respirationRate" />
- <el-table-column label="琛�姘чケ鍜屽害(%)" align="center" prop="oxygenSaturation" width="160" />
- <el-table-column label="灏块噺(ml/h)" align="center" prop="urineOutput" />
- <el-table-column label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+ <el-table-column
+ label="鏍告煡鏃堕棿"
+ align="center"
+ prop="recordTime"
+ width="160"
+ />
+ <el-table-column
+ label="鏍告煡浜�"
+ align="center"
+ prop="recorder"
+ width="100"
+ />
+ <el-table-column
+ label="鏍告煡璁板綍"
+ align="center"
+ prop="checkRecord"
+ min-width="200"
+ show-overflow-tooltip
+ />
+ <!-- 闄勪欢鍒� -->
+ <el-table-column label="闄勪欢" align="center" width="120">
<template slot-scope="scope">
<el-button
+ v-if="scope.row.attachments && scope.row.attachments.length > 0"
size="mini"
type="text"
- icon="el-icon-edit"
- @click="handleEditRecord(scope.row)"
- >缂栬緫</el-button>
- <el-button
- size="mini"
- type="text"
- icon="el-icon-delete"
- style="color: #F56C6C;"
- @click="handleDeleteRecord(scope.row)"
- >鍒犻櫎</el-button>
- <el-button
- size="mini"
- type="text"
- icon="el-icon-view"
- @click="handleViewRecord(scope.row)"
- >璇︽儏</el-button>
+ @click="handleViewRecordAttachments(scope.row)"
+ >
+ 鏌ョ湅闄勪欢({{ scope.row.attachments.length }})
+ </el-button>
+ <span v-else>鏃犻檮浠�</span>
</template>
+ </el-table-column>
+ <el-table-column
+ label="鎿嶄綔"
+ align="center"
+ width="180"
+ class-name="small-padding fixed-width"
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-edit"
+ @click="handleEditRecord(scope.row)"
+ >缂栬緫</el-button
+ >
+ <el-button
+ size="mini"
+ type="text"
+ icon="el-icon-delete"
+ style="color: #F56C6C;"
+ @click="handleDeleteRecord(scope.row)"
+ >鍒犻櫎</el-button
+ >
</el-table-column>
</el-table>
- <!-- 鍒嗛〉缁勪欢 -->
<pagination
v-show="recordTotal > 0"
:total="recordTotal"
@@ -264,7 +378,6 @@
@pagination="getRecordList"
/>
</el-card>
-
<!-- 鍩瑰吇璁板綍缂栬緫瀵硅瘽妗� -->
<el-dialog
:title="cultureDialogTitle"
@@ -272,16 +385,25 @@
width="700px"
:close-on-click-modal="false"
>
- <el-form :model="cultureForm" ref="cultureForm" :rules="cultureRules" label-width="120px">
+ <el-form
+ :model="cultureForm"
+ ref="cultureForm"
+ :rules="cultureRules"
+ label-width="120px"
+ >
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="鍩瑰吇绫诲瀷" prop="cultureType">
- <el-select v-model="cultureForm.cultureType" placeholder="璇烽�夋嫨鍩瑰吇绫诲瀷" style="width: 100%">
+ <el-select
+ v-model="cultureForm.cultureType"
+ placeholder="璇烽�夋嫨鍩瑰吇绫诲瀷"
+ style="width: 100%"
+ >
<el-option
v-for="item in cultureTypeOptions"
:key="item.value"
:label="item.label"
- :value="item.value"
+ :value="item.label"
/>
</el-select>
</el-form-item>
@@ -302,54 +424,35 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="鍩瑰吇缁撴灉" prop="result">
- <el-select v-model="cultureForm.result" placeholder="璇烽�夋嫨鍩瑰吇缁撴灉" style="width: 100%">
+ <el-select
+ v-model="cultureForm.result"
+ placeholder="璇烽�夋嫨鍩瑰吇缁撴灉"
+ style="width: 100%"
+ >
<el-option label="闃存��" value="闃存��" />
<el-option label="闃虫��" value="闃虫��" />
</el-select>
</el-form-item>
</el-col>
- <el-col :span="12">
- <el-form-item label="鑿岀" prop="bacteria">
- <el-input v-model="cultureForm.bacteria" placeholder="璇疯緭鍏ユ娴嬪埌鐨勮弻绉�" />
- </el-form-item>
- </el-col>
</el-row>
- <el-form-item label="鑽晱缁撴灉" prop="drugSensitivity">
- <el-input
- type="textarea"
- :rows="3"
- v-model="cultureForm.drugSensitivity"
- placeholder="璇疯緭鍏ヨ嵂鏁忚瘯楠岀粨鏋�"
- />
- </el-form-item>
-
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="妫�娴嬫満鏋�" prop="testingInstitution">
- <el-input v-model="cultureForm.testingInstitution" placeholder="璇疯緭鍏ユ娴嬫満鏋�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏍囨湰绫诲瀷" prop="specimenType">
- <el-input v-model="cultureForm.specimenType" placeholder="璇疯緭鍏ユ爣鏈被鍨�" />
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-form-item label="澶囨敞" prop="remarks">
- <el-input
- type="textarea"
- :rows="2"
- v-model="cultureForm.remarks"
- placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�"
+ <!-- 闄勪欢涓婁紶 -->
+ <el-form-item label="闄勪欢">
+ <upload-attachment
+ :file-list="cultureForm.attachments"
+ @change="handleCultureAttachmentChange"
/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="cultureDialogVisible = false">鍙栨秷</el-button>
- <el-button type="primary" @click="handleSaveCulture" :loading="cultureSaveLoading">淇濆瓨</el-button>
+ <el-button
+ type="primary"
+ @click="handleSaveCulture"
+ :loading="cultureSaveLoading"
+ >淇濆瓨</el-button
+ >
</span>
</el-dialog>
@@ -357,10 +460,15 @@
<el-dialog
:title="recordDialogTitle"
:visible.sync="recordDialogVisible"
- width="800px"
+ width="700px"
:close-on-click-modal="false"
>
- <el-form :model="recordForm" ref="recordForm" :rules="recordRules" label-width="120px">
+ <el-form
+ :model="recordForm"
+ ref="recordForm"
+ :rules="recordRules"
+ label-width="120px"
+ >
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="鏍告煡鏃堕棿" prop="recordTime">
@@ -375,203 +483,374 @@
</el-col>
<el-col :span="12">
<el-form-item label="鏍告煡浜�" prop="recorder">
- <el-input v-model="recordForm.recorder" placeholder="璇疯緭鍏ユ牳鏌ヤ汉濮撳悕" />
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="8">
- <el-form-item label="浣撴俯(鈩�)" prop="temperature">
- <el-input-number
- v-model="recordForm.temperature"
- :min="30" :max="45" :step="0.1"
- controls-position="right"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="蹇冪巼(娆�/鍒�)" prop="heartRate">
- <el-input-number
- v-model="recordForm.heartRate"
- :min="0" :max="200"
- controls-position="right"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="鍛煎惛(娆�/鍒�)" prop="respirationRate">
- <el-input-number
- v-model="recordForm.respirationRate"
- :min="0" :max="60"
- controls-position="right"
- style="width: 100%"
+ <el-input
+ v-model="recordForm.recorder"
+ placeholder="璇疯緭鍏ユ牳鏌ヤ汉濮撳悕"
/>
</el-form-item>
</el-col>
</el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="琛�鍘�(mmHg)" prop="bloodPressure">
- <el-input v-model="recordForm.bloodPressure" placeholder="鏍煎紡锛氭敹缂╁帇/鑸掑紶鍘�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="琛�姘чケ鍜屽害(%)" prop="oxygenSaturation">
- <el-input-number
- v-model="recordForm.oxygenSaturation"
- :min="0" :max="100"
- controls-position="right"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="灏块噺(ml/h)" prop="urineOutput">
- <el-input-number
- v-model="recordForm.urineOutput"
- :min="0" :max="1000"
- controls-position="right"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="涓績闈欒剦鍘�" prop="cvp">
- <el-input-number
- v-model="recordForm.cvp"
- :min="0" :max="20" :step="0.1"
- controls-position="right"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-form-item label="澶囨敞" prop="remarks">
+ <el-form-item label="鏍告煡璁板綍" prop="checkRecord">
<el-input
type="textarea"
- :rows="3"
- v-model="recordForm.remarks"
- placeholder="璇疯緭鍏ユ牳鏌ュ娉ㄤ俊鎭�"
+ :rows="4"
+ v-model="recordForm.checkRecord"
+ placeholder="璇疯緭鍏ユ牳鏌ヨ褰曞唴瀹�"
+ />
+ </el-form-item>
+
+ <!-- 闄勪欢涓婁紶 -->
+ <el-form-item label="闄勪欢">
+ <upload-attachment
+ :file-list="recordForm.attachments"
+ @change="handleRecordAttachmentChange"
/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="recordDialogVisible = false">鍙栨秷</el-button>
- <el-button type="primary" @click="handleSaveRecord" :loading="recordSaveLoading">淇濆瓨</el-button>
+ <el-button
+ type="primary"
+ @click="handleSaveRecord"
+ :loading="recordSaveLoading"
+ >淇濆瓨</el-button
+ >
</span>
</el-dialog>
+
+ <!-- 闄勪欢棰勮瀵硅瘽妗� -->
+ <attachment-preview
+ :visible="attachmentPreviewVisible"
+ :file-list="currentAttachmentList"
+ :title="attachmentPreviewTitle"
+ @close="attachmentPreviewVisible = false"
+ />
</div>
</template>
<script>
import { getMaintenanceDetail, updateMaintenance } from "./mockMaintenanceApi";
-import { listCultureResults, addCultureResult, updateCultureResult, deleteCultureResult } from "./mockMaintenanceApi";
-import { listNursingRecords, addNursingRecord, updateNursingRecord, deleteNursingRecord } from "./mockMaintenanceApi";
+import {
+ listCultureResults,
+ addCultureResult,
+ updateCultureResult,
+ deleteCultureResult
+} from "./mockMaintenanceApi";
+import {
+ listNursingRecords,
+ addNursingRecord,
+ updateNursingRecord,
+ deleteNursingRecord
+} from "./mockMaintenanceApi";
import Pagination from "@/components/Pagination";
+import UploadAttachment from "@/components/UploadAttachment";
+import AttachmentPreview from "@/components/AttachmentPreview";
+import LiverKidneyPanel from "./components/LiverKidneyPanel.vue";
+import BloodRoutinePanel from "./components/BloodRoutinePanel.vue";
+import UrineRoutinePanel from "./components/UrineRoutinePanel.vue";
export default {
- name: "MaintenanceDetail",
- components: { Pagination },
+ name: "MaintenanceDetail",
+ components: {
+ Pagination,
+ UploadAttachment,
+ AttachmentPreview,
+ LiverKidneyPanel,
+ BloodRoutinePanel,
+ UrineRoutinePanel
+ },
data() {
return {
- isEdit: false,
+ isEdit: true,
form: {
id: undefined,
- caseNo: '',
- donorName: '',
- gender: '',
- age: '',
- diagnosis: '',
- hospitalName: '',
- patientStatus: '1',
- admissionTime: '',
- dischargeTime: '',
- coordinator: '',
- bloodType: '',
- rhFactor: '',
- specialMedicalHistory: '',
- incompleteReason: ''
+ caseNo: "",
+ donorName: "",
+ gender: "",
+ age: "",
+ diagnosis: "",
+ hospitalName: "",
+ patientStatus: "1",
+ admissionTime: "",
+ dischargeTime: "",
+ coordinator: "",
+ bloodType: "",
+ rhFactor: "",
+ specialMedicalHistory: "",
+ incompleteReason: ""
},
-
+ activeTab: "culture",
// 鍩瑰吇缁撴灉鐩稿叧鏁版嵁
- cultureList: [],
+ // 鍩瑰吇缁撴灉鐩稿叧鏁版嵁
+ cultureList: [
+ {
+ id: 1,
+ cultureType: "琛�鍩瑰吇",
+ sampleTime: "2024-12-19 10:30:00",
+ result: "闃存��",
+ bacteria: "",
+ drugSensitivity: "",
+ testingInstitution: "鍖椾含鍖诲妫�楠屼腑蹇�",
+ specimenType: "琛�娑�",
+ remarks: "甯歌妫�娴�",
+ attachments: [
+ {
+ id: 1,
+ fileName: "琛�鍩瑰吇鎶ュ憡.pdf",
+ fileUrl:
+ "http://localhost:8080/profile/upload/2025/12/27/1.COPO渚涗綋璇勪及琛�.pdf",
+ fileSize: 1024000,
+ uploadTime: "2024-12-19 11:00:00"
+ }
+ ]
+ },
+ {
+ id: 3,
+ cultureType: "琛�鍩瑰吇",
+ sampleTime: "2024-12-20 09:15:00",
+ result: "闃存��",
+ bacteria: "",
+ drugSensitivity: "",
+ testingInstitution: "骞垮窞妫�娴嬩腑蹇�",
+ specimenType: "灏挎恫",
+ remarks: "娓呮磥涓灏挎爣鏈�",
+ attachments: []
+ },
+ {
+ id: 2,
+ cultureType: "鐥板煿鍏荤粨鏋�",
+ sampleTime: "2024-12-19 14:20:00",
+ result: "闃虫��",
+ bacteria: "閲戦粍鑹茶憽钀勭悆鑿�",
+ drugSensitivity: "瀵归潚闇夌礌鏁忔劅锛屽澶村绫讳腑浠�",
+ testingInstitution: "涓婃捣鍖诲妫�楠屾墍",
+ specimenType: "鐥版恫",
+ remarks: "鑽晱璇曢獙瀹屾垚",
+ attachments: [
+ {
+ id: 2,
+ fileName: "鐥板煿鍏荤粨鏋�.jpg",
+ fileUrl:
+ "https://img95.699pic.com/photo/40142/8262.jpg_wh860.jpg",
+ fileSize: 512000,
+ uploadTime: "2024-12-19 15:30:00"
+ },
+ {
+ id: 3,
+ fileName: "鑽晱鎶ュ憡.pdf",
+ fileUrl:
+ "http://localhost:8080/profile/upload/2025/12/27/(鍚撮緳8.7)姣忔棩宸ヤ綔鎬荤粨.pdf",
+ fileSize: 768000,
+ uploadTime: "2024-12-19 16:00:00"
+ }
+ ]
+ },
+
+ {
+ id: 4,
+ cultureType: "鐪熻弻鍩瑰吇",
+ sampleTime: "2024-12-20 11:45:00",
+ result: "闃虫��",
+ bacteria: "澶ц偁鏉嗚弻",
+ drugSensitivity: "瀵瑰乏姘ф盁娌欐槦鏁忔劅",
+ testingInstitution: "娣卞湷浜烘皯鍖婚櫌",
+ specimenType: "浼ゅ彛鍒嗘硨鐗�",
+ remarks: "鏈悗浼ゅ彛鎰熸煋鐩戞祴",
+ attachments: [
+ {
+ id: 4,
+ fileName: "鐪熻弻鍩瑰吇.pdf",
+ fileUrl: "/reports/culture4.pdf",
+ fileSize: 890000,
+ uploadTime: "2024-12-20 13:20:00"
+ }
+ ]
+ }
+ ],
cultureLoading: false,
- cultureTotal: 0,
+ cultureTotal: 5,
cultureQueryParams: {
pageNum: 1,
pageSize: 10
},
cultureDialogVisible: false,
- cultureDialogTitle: '',
+ cultureDialogTitle: "",
cultureSaveLoading: false,
cultureForm: {
id: undefined,
- cultureType: '',
- sampleTime: '',
- result: '闃存��',
- bacteria: '',
- drugSensitivity: '',
- testingInstitution: '',
- specimenType: '',
- remarks: ''
+ cultureType: "",
+ sampleTime: "",
+ result: "闃存��",
+ attachments: [] // 鏂板闄勪欢瀛楁
},
cultureRules: {
- cultureType: [{ required: true, message: '璇烽�夋嫨鍩瑰吇绫诲瀷', trigger: 'change' }],
- sampleTime: [{ required: true, message: '璇烽�夋嫨閲囨牱鏃堕棿', trigger: 'change' }],
- result: [{ required: true, message: '璇烽�夋嫨鍩瑰吇缁撴灉', trigger: 'change' }]
+ cultureType: [
+ { required: true, message: "璇烽�夋嫨鍩瑰吇绫诲瀷", trigger: "change" }
+ ],
+ sampleTime: [
+ { required: true, message: "璇烽�夋嫨閲囨牱鏃堕棿", trigger: "change" }
+ ],
+ result: [
+ { required: true, message: "璇烽�夋嫨鍩瑰吇缁撴灉", trigger: "change" }
+ ]
},
cultureTypeOptions: [
- { value: '1', label: '琛�鍩瑰吇' },
- { value: '2', label: '鐥板煿鍏�' },
- { value: '3', label: '灏垮煿鍏�' },
- { value: '4', label: '浼ゅ彛鍒嗘硨鐗�' },
- { value: '5', label: '鑴戣剨娑插煿鍏�' },
- { value: '6', label: '鍏朵粬' }
+ { value: "1", label: "琛�鍩瑰吇" },
+ { value: "2", label: "鐥板煿鍏�" },
+ { value: "3", label: "灏垮煿鍏�" },
+ { value: "4", label: "浼ゅ彛鍒嗘硨鐗�" },
+ { value: "5", label: "鑴戣剨娑插煿鍏�" },
+ { value: "6", label: "鍏朵粬" }
],
// 鎶ょ悊鏍告煡璁板綍鐩稿叧鏁版嵁
- recordList: [],
+ recordList: [
+ {
+ id: 1,
+ recordTime: "2024-12-19 08:30:00",
+ recorder: "寮犳姢澹�",
+ temperature: 36.8,
+ heartRate: 78,
+ bloodPressure: "120/80",
+ respirationRate: 18,
+ oxygenSaturation: 98,
+ urineOutput: 60,
+ cvp: 8,
+ checkRecord: "鎮h�呯敓鍛戒綋寰佸钩绋筹紝鎰忚瘑娓呮锛岄厤鍚堟不鐤�",
+ remarks: "澶滈棿鐫$湢鑹ソ",
+ attachments: [
+ {
+ id: 1,
+ fileName: "鏃╃彮鎶ょ悊璁板綍.jpg",
+ fileUrl: "/records/nursing1.jpg",
+ fileSize: 1024000,
+ uploadTime: "2024-12-19 09:00:00"
+ }
+ ]
+ },
+ {
+ id: 2,
+ recordTime: "2024-12-19 14:30:00",
+ recorder: "鏉庢姢澹�",
+ temperature: 37.2,
+ heartRate: 82,
+ bloodPressure: "118/76",
+ respirationRate: 16,
+ oxygenSaturation: 97,
+ urineOutput: 45,
+ cvp: 7.5,
+ checkRecord: "鎮h�呭崍鍚庝綋娓╃暐鏈夊崌楂橈紝瑙傚療涓�",
+ remarks: "寤鸿澧炲姞姘村垎鎽勫叆",
+ attachments: []
+ },
+ {
+ id: 3,
+ recordTime: "2024-12-19 20:30:00",
+ recorder: "鐜嬫姢澹�",
+ temperature: 36.9,
+ heartRate: 75,
+ bloodPressure: "122/78",
+ respirationRate: 17,
+ oxygenSaturation: 98,
+ urineOutput: 55,
+ cvp: 8.2,
+ checkRecord: "鏅氶棿鐢熷懡浣撳緛绋冲畾锛屾偅鑰呬紤鎭壇濂�",
+ remarks: "澶滈棿鐩戞祴鏃犲紓甯�",
+ attachments: [
+ {
+ id: 2,
+ fileName: "鏅氱彮鎶ょ悊璁板綍.pdf",
+ fileUrl: "/records/nursing3.pdf",
+ fileSize: 890000,
+ uploadTime: "2024-12-19 21:00:00"
+ },
+ {
+ id: 3,
+ fileName: "浣撳緛鐩戞祴琛�.xlsx",
+ fileUrl: "/records/monitoring3.xlsx",
+ fileSize: 256000,
+ uploadTime: "2024-12-19 21:15:00"
+ }
+ ]
+ },
+ {
+ id: 4,
+ recordTime: "2024-12-20 08:30:00",
+ recorder: "璧垫姢澹�",
+ temperature: 36.7,
+ heartRate: 80,
+ bloodPressure: "119/77",
+ respirationRate: 18,
+ oxygenSaturation: 99,
+ urineOutput: 65,
+ cvp: 7.8,
+ checkRecord: "鏅ㄩ棿鐢熷懡浣撳緛姝e父锛屾偅鑰呯簿绁炵姸鎬佽壇濂�",
+ remarks: "鍑嗗浠婃棩妫�鏌�",
+ attachments: []
+ },
+ {
+ id: 5,
+ recordTime: "2024-12-20 12:30:00",
+ recorder: "鍒樻姢澹�",
+ temperature: 37.1,
+ heartRate: 85,
+ bloodPressure: "121/79",
+ respirationRate: 19,
+ oxygenSaturation: 96,
+ urineOutput: 40,
+ cvp: 8.5,
+ checkRecord: "鍗堥棿浣撴俯鐣ユ湁娉㈠姩锛岀户缁瀵�",
+ remarks: "宸查�氱煡鍖荤敓",
+ attachments: [
+ {
+ id: 4,
+ fileName: "鍗堥棿鎶ょ悊璁板綍.jpg",
+ fileUrl: "/records/nursing5.jpg",
+ fileSize: 765000,
+ uploadTime: "2024-12-20 13:00:00"
+ }
+ ]
+ }
+ ],
recordLoading: false,
- recordTotal: 0,
+ recordTotal: 4,
recordQueryParams: {
pageNum: 1,
pageSize: 10
},
recordDialogVisible: false,
- recordDialogTitle: '',
+ recordDialogTitle: "",
recordSaveLoading: false,
recordForm: {
id: undefined,
- recordTime: '',
- recorder: '',
- temperature: 36.5,
- heartRate: 80,
- bloodPressure: '120/80',
- respirationRate: 18,
- oxygenSaturation: 98,
- urineOutput: 50,
- cvp: 8,
- remarks: ''
+ recordTime: "",
+ recorder: "",
+ checkRecord: "", // 鏀逛负鍗曞瓧娈佃褰�
+ attachments: [] // 鏂板闄勪欢瀛楁
},
recordRules: {
- recordTime: [{ required: true, message: '璇烽�夋嫨鏍告煡鏃堕棿', trigger: 'change' }],
- recorder: [{ required: true, message: '璇疯緭鍏ユ牳鏌ヤ汉', trigger: 'blur' }],
- temperature: [{ required: true, message: '璇疯緭鍏ヤ綋娓�', trigger: 'blur' }]
- }
+ recordTime: [
+ { required: true, message: "璇烽�夋嫨鏍告煡鏃堕棿", trigger: "change" }
+ ],
+ recorder: [
+ { required: true, message: "璇疯緭鍏ユ牳鏌ヤ汉", trigger: "blur" }
+ ],
+ checkRecord: [
+ { required: true, message: "璇疯緭鍏ユ牳鏌ヨ褰�", trigger: "blur" }
+ ]
+ },
+
+ // 闄勪欢棰勮鐩稿叧
+ attachmentPreviewVisible: false,
+ currentAttachmentList: [],
+ attachmentPreviewTitle: ""
};
},
- created() {
+ created() {
const id = this.$route.query.id;
- this.isEdit = this.$route.query.edit === 'true';
+ // this.isEdit = this.$route.query.edit === "true";
if (id) {
this.getDetail(id);
this.getCultureList();
@@ -579,7 +858,7 @@
}
},
methods: {
- // 鑾峰彇璇︽儏
+ // 鑾峰彇璇︽儏
getDetail(id) {
getMaintenanceDetail(id).then(response => {
if (response.code === 200) {
@@ -587,33 +866,78 @@
}
});
},
+ // 鍩瑰吇璁板綍闄勪欢鍙樻洿
+ handleCultureAttachmentChange(fileList) {
+ this.cultureForm.attachments = fileList;
+ },
+ // 鎶ょ悊璁板綍闄勪欢鍙樻洿
+ handleRecordAttachmentChange(fileList) {
+ this.recordForm.attachments = fileList;
+ },
+
+ // 鏌ョ湅鍩瑰吇璁板綍闄勪欢
+ handleViewCultureAttachments(row) {
+ console.log(22, row.attachments);
+
+ this.currentAttachmentList = row.attachments || [];
+ this.attachmentPreviewTitle = `鍩瑰吇璁板綍闄勪欢 - ${row.cultureType}`;
+ this.attachmentPreviewVisible = true;
+ },
+ handleTabClick(tab) {
+ this.$nextTick(() => {
+ console.log(tab.name, 88);
+ const tableRef=null;
+ if (tab.name == "liverKidney") {
+ tableRef = this.$refs.liverKidney; // 璇锋浛鎹负鎮ㄧ殑琛ㄦ牸 ref
+ } else if (tab.name == "bloodRoutine") {
+ tableRef = this.$refs.bloodRoutine; // 璇锋浛鎹负鎮ㄧ殑琛ㄦ牸 ref
+ } else if (tab.name == "bloodRoutine") {
+ tableRef = this.$refs.bloodRoutine; // 璇锋浛鎹负鎮ㄧ殑琛ㄦ牸 ref
+ }
+ // 濡傛灉鏄� el-table锛屽皾璇曡皟鐢ㄥ叾 doLayout 鏂规硶
+ if (tableRef && tableRef.doLayout) {
+ tableRef.doLayout();
+ }
+
+ // 鎴栬�咃紝鏇撮�氱敤鐨勫己鍒堕噸鏂版覆鏌撴柟寮�
+ this.$forceUpdate(); // 鎱庣敤锛屽彲鑳藉紩鍙戝叾浠栭棶棰榌1](@ref)
+ });
+ },
+ // 鏌ョ湅鎶ょ悊璁板綍闄勪欢
+ handleViewRecordAttachments(row) {
+ this.currentAttachmentList = row.attachments || [];
+ this.attachmentPreviewTitle = `鎶ょ悊鏍告煡璁板綍闄勪欢 - ${row.recorder}`;
+ this.attachmentPreviewVisible = true;
+ },
// 鍩瑰吇璁板綍鐩稿叧鏂规硶
getCultureList() {
this.cultureLoading = true;
- listCultureResults(this.form.id, this.cultureQueryParams).then(response => {
- if (response.code === 200) {
- this.cultureList = response.data.rows;
- this.cultureTotal = response.data.total;
- }
- this.cultureLoading = false;
- }).catch(() => {
- this.cultureLoading = false;
- });
+ listCultureResults(this.form.id, this.cultureQueryParams)
+ .then(response => {
+ if (response.code === 200) {
+ // this.cultureList = response.data.rows;
+ // this.cultureTotal = response.data.total;
+ }
+ this.cultureLoading = false;
+ })
+ .catch(() => {
+ this.cultureLoading = false;
+ });
},
handleAddCulture() {
- this.cultureDialogTitle = '鏂板鍩瑰吇璁板綍';
+ this.cultureDialogTitle = "鏂板鍩瑰吇璁板綍";
this.cultureForm = {
id: undefined,
- cultureType: '',
- sampleTime: '',
- result: '闃存��',
- bacteria: '',
- drugSensitivity: '',
- testingInstitution: '',
- specimenType: '',
- remarks: ''
+ cultureType: "",
+ sampleTime: "",
+ result: "闃存��",
+ bacteria: "",
+ drugSensitivity: "",
+ testingInstitution: "",
+ specimenType: "",
+ remarks: ""
};
this.cultureDialogVisible = true;
this.$nextTick(() => {
@@ -622,7 +946,7 @@
},
handleEditCulture(row) {
- this.cultureDialogTitle = '缂栬緫鍩瑰吇璁板綍';
+ this.cultureDialogTitle = "缂栬緫鍩瑰吇璁板綍";
this.cultureForm = { ...row };
this.cultureDialogVisible = true;
this.$nextTick(() => {
@@ -631,93 +955,128 @@
},
handleViewCulture(row) {
- this.$alert(`
+ this.$alert(
+ `
<div>
- <p><strong>鍩瑰吇绫诲瀷锛�</strong>${this.getCultureTypeLabel(row.cultureType)}</p>
+ <p><strong>鍩瑰吇绫诲瀷锛�</strong>${this.getCultureTypeLabel(
+ row.cultureType
+ )}</p>
<p><strong>閲囨牱鏃堕棿锛�</strong>${row.sampleTime}</p>
<p><strong>鍩瑰吇缁撴灉锛�</strong>${row.result}</p>
- <p><strong>鑿岀锛�</strong>${row.bacteria || '鏃�'}</p>
- <p><strong>鑽晱缁撴灉锛�</strong>${row.drugSensitivity || '鏃�'}</p>
<p><strong>妫�娴嬫満鏋勶細</strong>${row.testingInstitution}</p>
</div>
- `, '鍩瑰吇璁板綍璇︽儏', {
- dangerouslyUseHTMLString: true,
- customClass: 'detail-dialog'
- });
+ `,
+ "鍩瑰吇璁板綍璇︽儏",
+ {
+ dangerouslyUseHTMLString: true,
+ customClass: "detail-dialog"
+ }
+ );
},
-
+ toggleEditMode() {
+ this.isEdit = !this.isEdit;
+ // if (!this.isEdit) {
+ // this.saveAllData();
+ // }
+ },
+ handleLiverKidneyDataChange(data) {
+ console.log("鑲濆姛鑳借偩鍔熻兘鏁版嵁鍙樻洿:", data);
+ // 澶勭悊鏁版嵁淇濆瓨鎴栦复鏃跺瓨鍌�
+ },
+ handleBloodRoutineDataChange(data) {
+ console.log("琛�甯歌鍔熻兘鏁版嵁鍙樻洿:", data);
+ // 澶勭悊鏁版嵁淇濆瓨鎴栦复鏃跺瓨鍌�
+ },
+ handleUrineRoutineDataChange(data) {
+ console.log("灏垮父瑙勫姛鑳芥暟鎹彉鏇�:", data);
+ // 澶勭悊鏁版嵁淇濆瓨鎴栦复鏃跺瓨鍌�
+ },
+ // 淇濆瓨鍩瑰吇璁板綍
handleSaveCulture() {
this.$refs.cultureForm.validate(valid => {
if (valid) {
this.cultureSaveLoading = true;
- const api = this.cultureForm.id ? updateCultureResult : addCultureResult;
+ const api = this.cultureForm.id
+ ? updateCultureResult
+ : addCultureResult;
const requestData = {
...this.cultureForm,
maintenanceId: this.form.id
};
- api(requestData).then(response => {
- if (response.code === 200) {
- this.$message.success(this.cultureForm.id ? '淇敼鎴愬姛' : '鏂板鎴愬姛');
- this.cultureDialogVisible = false;
- this.getCultureList();
- }
- this.cultureSaveLoading = false;
- }).catch(() => {
- this.cultureSaveLoading = false;
- });
+ api(requestData)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success(
+ this.cultureForm.id ? "淇敼鎴愬姛" : "鏂板鎴愬姛"
+ );
+ this.cultureDialogVisible = false;
+ this.getCultureList();
+ }
+ this.cultureSaveLoading = false;
+ })
+ .catch(() => {
+ this.cultureSaveLoading = false;
+ });
}
});
},
handleDeleteCulture(row) {
- this.$confirm('纭畾瑕佸垹闄よ繖鏉″煿鍏昏褰曞悧锛�', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).then(() => {
- deleteCultureResult(row.id).then(response => {
- if (response.code === 200) {
- this.$message.success('鍒犻櫎鎴愬姛');
- this.getCultureList();
- }
- });
- }).catch(() => {});
+ this.$confirm("纭畾瑕佸垹闄よ繖鏉″煿鍏昏褰曞悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ deleteCultureResult(row.id).then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒犻櫎鎴愬姛");
+ this.getCultureList();
+ }
+ });
+ })
+ .catch(() => {});
},
getCultureTypeLabel(value) {
const type = this.cultureTypeOptions.find(item => item.value === value);
- return type ? type.label : '鏈煡';
+ return type ? type.label : "鏈煡";
},
// 鎶ょ悊鏍告煡璁板綍鐩稿叧鏂规硶
getRecordList() {
this.recordLoading = true;
- listNursingRecords(this.form.id, this.recordQueryParams).then(response => {
- if (response.code === 200) {
- this.recordList = response.data.rows;
- this.recordTotal = response.data.total;
- }
- this.recordLoading = false;
- }).catch(() => {
- this.recordLoading = false;
- });
+ listNursingRecords(this.form.id, this.recordQueryParams)
+ .then(response => {
+ if (response.code === 200) {
+ // this.recordList = response.data.rows;
+ // this.recordTotal = response.data.total;
+ }
+ this.recordLoading = false;
+ })
+ .catch(() => {
+ this.recordLoading = false;
+ });
},
handleAddRecord() {
- this.recordDialogTitle = '鏂板鎶ょ悊鏍告煡璁板綍';
+ this.recordDialogTitle = "鏂板鎶ょ悊鏍告煡璁板綍";
this.recordForm = {
id: undefined,
- recordTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
- recorder: '褰撳墠鐢ㄦ埛', // 瀹為檯椤圭洰涓粠鐢ㄦ埛淇℃伅鑾峰彇
+ recordTime: new Date()
+ .toISOString()
+ .replace("T", " ")
+ .substring(0, 19),
+ recorder: "褰撳墠鐢ㄦ埛", // 瀹為檯椤圭洰涓粠鐢ㄦ埛淇℃伅鑾峰彇
temperature: 36.5,
heartRate: 80,
- bloodPressure: '120/80',
+ bloodPressure: "120/80",
respirationRate: 18,
oxygenSaturation: 98,
urineOutput: 50,
cvp: 8,
- remarks: ''
+ remarks: ""
};
this.recordDialogVisible = true;
this.$nextTick(() => {
@@ -726,7 +1085,7 @@
},
handleEditRecord(row) {
- this.recordDialogTitle = '缂栬緫鎶ょ悊鏍告煡璁板綍';
+ this.recordDialogTitle = "缂栬緫鎶ょ悊鏍告煡璁板綍";
this.recordForm = { ...row };
this.recordDialogVisible = true;
this.$nextTick(() => {
@@ -735,7 +1094,8 @@
},
handleViewRecord(row) {
- this.$alert(`
+ this.$alert(
+ `
<div>
<p><strong>鏍告煡鏃堕棿锛�</strong>${row.recordTime}</p>
<p><strong>鏍告煡浜猴細</strong>${row.recorder}</p>
@@ -748,51 +1108,62 @@
<li>琛�姘чケ鍜屽害锛�${row.oxygenSaturation}%</li>
<li>灏块噺锛�${row.urineOutput}ml/h</li>
</ul>
- <p><strong>澶囨敞锛�</strong>${row.remarks || '鏃�'}</p>
+ <p><strong>澶囨敞锛�</strong>${row.remarks || "鏃�"}</p>
</div>
- `, '鎶ょ悊鏍告煡璁板綍璇︽儏', {
- dangerouslyUseHTMLString: true,
- customClass: 'detail-dialog'
- });
+ `,
+ "鎶ょ悊鏍告煡璁板綍璇︽儏",
+ {
+ dangerouslyUseHTMLString: true,
+ customClass: "detail-dialog"
+ }
+ );
},
handleSaveRecord() {
this.$refs.recordForm.validate(valid => {
if (valid) {
this.recordSaveLoading = true;
- const api = this.recordForm.id ? updateNursingRecord : addNursingRecord;
+ const api = this.recordForm.id
+ ? updateNursingRecord
+ : addNursingRecord;
const requestData = {
...this.recordForm,
maintenanceId: this.form.id
};
- api(requestData).then(response => {
- if (response.code === 200) {
- this.$message.success(this.recordForm.id ? '淇敼鎴愬姛' : '鏂板鎴愬姛');
- this.recordDialogVisible = false;
- this.getRecordList();
- }
- this.recordSaveLoading = false;
- }).catch(() => {
- this.recordSaveLoading = false;
- });
+ api(requestData)
+ .then(response => {
+ if (response.code === 200) {
+ this.$message.success(
+ this.recordForm.id ? "淇敼鎴愬姛" : "鏂板鎴愬姛"
+ );
+ this.recordDialogVisible = false;
+ this.getRecordList();
+ }
+ this.recordSaveLoading = false;
+ })
+ .catch(() => {
+ this.recordSaveLoading = false;
+ });
}
});
},
handleDeleteRecord(row) {
- this.$confirm('纭畾瑕佸垹闄よ繖鏉℃姢鐞嗘牳鏌ヨ褰曞悧锛�', '鎻愮ず', {
- confirmButtonText: '纭畾',
- cancelButtonText: '鍙栨秷',
- type: 'warning'
- }).then(() => {
- deleteNursingRecord(row.id).then(response => {
- if (response.code === 200) {
- this.$message.success('鍒犻櫎鎴愬姛');
- this.getRecordList();
- }
- });
- }).catch(() => {});
+ this.$confirm("纭畾瑕佸垹闄よ繖鏉℃姢鐞嗘牳鏌ヨ褰曞悧锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning"
+ })
+ .then(() => {
+ deleteNursingRecord(row.id).then(response => {
+ if (response.code === 200) {
+ this.$message.success("鍒犻櫎鎴愬姛");
+ this.getRecordList();
+ }
+ });
+ })
+ .catch(() => {});
},
// 淇濆瓨鍩烘湰淇℃伅
@@ -853,7 +1224,41 @@
font-size: 16px;
font-weight: bold;
}
+.medical-panel {
+ padding: 20px;
+}
+.attachment-section {
+ margin-top: 20px;
+ padding: 15px;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+}
+
+.attachment-title {
+ font-weight: bold;
+ margin-bottom: 10px;
+ color: #409eff;
+}
+
+.required-item::before {
+ content: "*";
+ color: #f56c6c;
+ margin-right: 4px;
+}
+
+.assessment-card {
+ margin-bottom: 20px;
+}
+
+.medical-table {
+ width: 100%;
+ margin-bottom: 20px;
+}
+
+.dynamic-column {
+ min-width: 120px;
+}
.fixed-width .el-button {
margin: 0 2px;
}
diff --git a/src/views/project/donatebaseinfo/index.vue b/src/views/project/donatebaseinfo/index.vue
index bd603bd..bb2da0d 100644
--- a/src/views/project/donatebaseinfo/index.vue
+++ b/src/views/project/donatebaseinfo/index.vue
@@ -1850,7 +1850,7 @@
handleUpdate(row) {
this.$router.push({
- path: "/organ/donationdetails/",
+ path: "/case/course",
query: {
id: row.id,
organType: "edit"
--
Gitblit v1.9.3