From 12ed29b3cd8471e6145cd847c6e33d6aa2f535ba Mon Sep 17 00:00:00 2001
From: sinake <sinake1@qq.com>
Date: 星期二, 10 十一月 2026 10:32:44 +0800
Subject: [PATCH] 器官费用,发票管理修改

---
 src/views/project/invoice/index.vue           |  387 ++++++++++++++++++++++++++++++++++++++++++------
 src/api/project/donorcharge.js                |    9 +
 src/views/project/unitCost/organfee/index.vue |   59 +++++++
 3 files changed, 405 insertions(+), 50 deletions(-)

diff --git a/src/api/project/donorcharge.js b/src/api/project/donorcharge.js
index 0eef1e1..1695eb0 100644
--- a/src/api/project/donorcharge.js
+++ b/src/api/project/donorcharge.js
@@ -152,6 +152,15 @@
   })
 }
 
+// 鍣ㄥ畼璐圭敤姹囨�伙紙搴旀敹/瀹炴敹/鏈洖娆撅級
+export function getOrganfeeSummary(query) {
+  return request({
+    url: '/project/donorchargeorgan/organfeeSummary',
+    method: 'get',
+    params: query
+  })
+}
+
 
 //鏂板鎴栦慨鏀规崘鐚櫒瀹樺垎閰�
 export function saveDonorchargeorgan(data) {
diff --git a/src/views/project/invoice/index.vue b/src/views/project/invoice/index.vue
index 343b217..7f6f47f 100644
--- a/src/views/project/invoice/index.vue
+++ b/src/views/project/invoice/index.vue
@@ -71,7 +71,87 @@
       ></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" border :data="invoiceList">
+    <!-- 鎵嬫満绔細鍗$墖鍒楄〃 -->
+    <div v-if="isMobile" class="invoice-cards" v-loading="loading">
+      <div
+        v-for="row in invoiceList"
+        :key="row.id"
+        class="invoice-card"
+      >
+        <div class="invoice-card__head">
+          <span
+            class="invoice-card__title"
+            @click="handleUpdate(row)"
+            >{{ row.invoiceName || "鏈懡鍚嶅彂绁�" }}</span
+          >
+          <el-tag
+            size="mini"
+            :type="
+              String(row.reimburseState) === '1' ? 'success' : 'info'
+            "
+          >
+            {{ String(row.reimburseState) === "1" ? "宸叉姤閿�" : "鏈姤閿�" }}
+          </el-tag>
+        </div>
+        <div class="invoice-card__row">
+          <span class="label">妗堜緥</span>
+          <span class="value">{{ row.caseId || "-" }}</span>
+        </div>
+        <div class="invoice-card__row">
+          <span class="label">閲戦</span>
+          <span class="value">{{ amountText(row.invoiceAmount) }}</span>
+        </div>
+        <div class="invoice-card__row">
+          <span class="label">鏃ユ湡</span>
+          <span class="value">{{
+            normalizeDate(row.occurDate) || row.occurDate || "-"
+          }}</span>
+        </div>
+        <div class="invoice-card__row">
+          <span class="label">鍙风爜</span>
+          <span class="value">{{ row.invoiceNo || "-" }}</span>
+        </div>
+        <div class="invoice-card__row">
+          <span class="label">鎶ラ攢ID</span>
+          <span class="value">{{ row.reimburseId || "-" }}</span>
+        </div>
+        <div class="invoice-card__actions">
+          <el-button
+            type="text"
+            icon="el-icon-view"
+            @click="openPreview(row.invoiceUrl, row.invoiceName)"
+            >棰勮</el-button
+          >
+          <el-button
+            v-if="String(row.reimburseState) !== '1'"
+            type="text"
+            icon="el-icon-check"
+            @click="handleReimburse(row)"
+            v-hasPermi="['project:invoice:edit']"
+            >宸叉姤閿�</el-button
+          >
+          <el-button
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(row)"
+            v-hasPermi="['project:invoice:edit']"
+            >淇敼</el-button
+          >
+          <el-button
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(row)"
+            v-hasPermi="['project:invoice:remove']"
+            >鍒犻櫎</el-button
+          >
+        </div>
+      </div>
+      <div v-if="!invoiceList.length && !loading" class="invoice-empty">
+        鏆傛棤鏁版嵁
+      </div>
+    </div>
+
+    <el-table v-else v-loading="loading" border :data="invoiceList">
       <el-table-column type="index" label="搴忓彿" width="55" align="center" />
       <el-table-column label="妗堜緥" align="center" prop="caseId"  width="100">
         
@@ -121,16 +201,23 @@
         </template>
       </el-table-column>
       <el-table-column
+        prop="reimburseId"
+        label="鎶ラ攢ID"
+        width="150"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
         label="鎿嶄綔"
         width="200"
         align="center"
-        fixed="right"
+        :fixed="isMobile ? false : 'right'"
       >
         <template slot-scope="scope">
           <el-button
             type="text"
             icon="el-icon-view"
-            @click="openPreview(scope.row.invoiceUrl)"
+            @click="openPreview(scope.row.invoiceUrl, scope.row.invoiceName)"
             >鏂囦欢棰勮</el-button
           >
           <el-button
@@ -173,7 +260,9 @@
       :visible.sync="open"
       width="760px"
       append-to-body
+      :fullscreen="isMobile"
       :close-on-click-modal="false"
+      @closed="revokeThumbObjectUrl"
     >
       <!-- 绗竴姝ワ細閫夋渚嬨�佺敤鎴凤紝涓婁紶鍙戠エ -->
       <div v-if="mode === 'upload'">
@@ -368,6 +457,15 @@
               </el-select>
             </el-form-item>
           </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎶ラ攢ID" prop="reimburseId">
+              <el-input
+                v-model="form.reimburseId"
+                maxlength="20"
+                placeholder="璇疯緭鍏ユ姤閿�ID"
+              />
+            </el-form-item>
+          </el-col>
           <el-col :span="24">
             <el-form-item label="澶囨敞" prop="remark">
               <el-input
@@ -386,17 +484,18 @@
                 type="text"
                 icon="el-icon-view"
                 :disabled="!form.invoiceUrl"
-                @click="openPreview(form.invoiceUrl)"
+                @click="openPreview(form.invoiceUrl, form.invoiceName)"
                 >鏂囦欢棰勮</el-button
               >
-              <el-image
+              <img
                 v-if="
                   form.invoiceUrl && getFileType(form.invoiceUrl) === 'image'
                 "
-                :src="form.invoiceUrl"
-                :preview-src-list="[form.invoiceUrl]"
-                fit="contain"
+                :src="thumbObjectUrl || form.invoiceUrl"
                 class="invoice-thumb"
+                title="鐐瑰嚮棰勮"
+                @click="openPreview(form.invoiceUrl, form.invoiceName)"
+                style="width: 400px;height: 240px;"
               />
             </el-form-item>
           </el-col>
@@ -424,27 +523,18 @@
     <el-dialog
       title="鍙戠エ鏂囦欢棰勮"
       :visible.sync="previewOpen"
-      width="900px"
+      width="1000px"
       append-to-body
+      :fullscreen="isMobile"
       :destroy-on-close="true"
+      @closed="handlePreviewClosed"
     >
-      <div class="invoice-preview">
-        <img
-          v-if="previewType === 'image'"
-          :src="previewUrl"
-          class="preview-image"
+      <div class="invoice-preview-wrap" v-loading="previewLoading">
+        <attachment-preview-pane
+          v-if="invoiceEmbeddedFile"
+          :file="invoiceEmbeddedFile"
+          style="flex: 1; min-width: 0;"
         />
-        <iframe
-          v-else-if="previewType === 'pdf'"
-          :src="previewUrl"
-          class="preview-frame"
-        ></iframe>
-        <div v-else class="preview-tip">
-          璇ユ枃浠剁被鍨嬩笉鏀寔鍦ㄧ嚎棰勮锛岃
-          <el-link type="primary" :href="previewUrl" target="_blank"
-            >鍦ㄦ柊绐楀彛鎵撳紑</el-link
-          >
-        </div>
       </div>
     </el-dialog>
   </div>
@@ -503,11 +593,14 @@
       rules: {
        
       },
-      // 鏂囦欢棰勮
+      // 鏂囦欢棰勮锛圓ttachmentPreviewPane 鎵�闇�鐨� file 瀵硅薄锛�
       previewOpen: false,
-      previewUrl: "",
-      // image / pdf / other
-      previewType: "",
+      previewLoading: false,
+      invoiceEmbeddedFile: null,
+      // 棰勮鐢ㄧ殑 blob 鍦板潃锛屽叧闂椂閲婃斁
+      previewObjectUrl: "",
+      // 缂栬緫寮圭獥缂╃暐鍥剧殑 blob 鍦板潃
+      thumbObjectUrl: "",
       // 鏌ヨ鍙傛暟
       queryParams: {
         pageNum: 1,
@@ -521,6 +614,10 @@
     };
   },
   computed: {
+    // 鏄惁鎵嬫満绔紙寮圭獥鍏ㄥ睆銆佹搷浣滃垪涓嶅浐瀹氱瓑閫傞厤锛�
+    isMobile() {
+      return this.$store.state.app.device === "mobile";
+    },
     // 涓婁紶鏃舵惡甯︾殑鐢ㄦ埛ID锛堝綋鍓嶇櫥褰曠敤鎴凤級銆佹渚婭D锛堝悗绔� uploadOCR 蹇呬紶锛�
     uploadData() {
       return {
@@ -670,7 +767,7 @@
       this.afterUploadBatch();
     },
     /** 鎵撳紑鍙戠エ淇℃伅纭寮圭獥 */
-    openEditForm(record) {
+    async openEditForm(record) {
       this.form = {
         id: record.id,
         caseId: record.caseId,
@@ -685,11 +782,33 @@
         invoiceEndDate: this.normalizeDate(record.invoiceEndDate),
         sellerName: record.sellerName,
         reimburseState: record.reimburseState || "0",
+        reimburseId: record.reimburseId,
         remark: record.remark
       };
       this.mode = "edit";
       this.title = "鍙戠エ淇℃伅纭";
       this.open = true;
+      this.refreshThumb();
+    },
+    /** 缂╃暐鍥惧悓鏍峰甫 token 鎷夊彇锛岄伩鍏嶇洿鎺ュ紩鐢ㄩ壌鏉冨湴鍧�鏄剧ず涓嶅嚭鏉� */
+    async refreshThumb() {
+      this.revokeThumbObjectUrl();
+      const url = this.form.invoiceUrl;
+      if (!url || this.getFileType(url) !== "image") {
+        return;
+      }
+      const objectUrl = await this.loadPreviewBlob(url);
+      if (objectUrl && this.form.invoiceUrl === url) {
+        this.thumbObjectUrl = objectUrl;
+      } else if (objectUrl) {
+        URL.revokeObjectURL(objectUrl);
+      }
+    },
+    revokeThumbObjectUrl() {
+      if (this.thumbObjectUrl) {
+        URL.revokeObjectURL(this.thumbObjectUrl);
+        this.thumbObjectUrl = "";
+      }
     },
     /** 淇敼鎸夐挳鎿嶄綔 */
     handleUpdate(row) {
@@ -697,15 +816,86 @@
         this.openEditForm(response.data);
       });
     },
-    /** 鏂囦欢棰勮锛氬浘鐗囥�丳DF 寮圭獥鍐呴瑙堬紝鍏跺畠绫诲瀷鎻愮ず鏂扮獥鍙f墦寮� */
-    openPreview(url) {
+    /** 鏂囦欢棰勮锛氬甫 token 鎷夊彇鎴� blob 鍐嶉瑙堬紝鍏煎闇�瑕侀壌鏉冪殑 /profile 璧勬簮 */
+    async openPreview(url, name) {
       if (!url) {
         this.$modal.msgWarning("璇ヨ褰曟殏鏃犲彂绁ㄦ枃浠�");
         return;
       }
-      this.previewUrl = url;
-      this.previewType = this.getFileType(url);
+      const type = this.getFileType(url);
+      const fileName = name || this.getFileName(url);
+      this.revokePreviewObjectUrl();
+      this.invoiceEmbeddedFile = null;
+      this.previewLoading = true;
       this.previewOpen = true;
+      const objectUrl = await this.loadPreviewBlob(url);
+      this.previewLoading = false;
+      if (objectUrl) {
+        this.previewObjectUrl = objectUrl;
+        this.invoiceEmbeddedFile = { name: fileName, url: objectUrl, type };
+      } else {
+        // 鎷夊彇澶辫触鏃堕��鍥炲師濮嬪湴鍧�锛屼氦缁欐祻瑙堝櫒鐩存帴鍔犺浇
+        this.invoiceEmbeddedFile = { name: fileName, url: url, type };
+      }
+    },
+    /** 渚濇灏濊瘯鍘熷鍦板潃銆佹帴鍙e墠缂�鍦板潃锛屽甫 token 鎷夊彇鎴� blob */
+    async loadPreviewBlob(url) {
+      const headers = { Authorization: "Bearer " + getToken() };
+      const candidates = this.buildPreviewCandidates(url);
+      for (let i = 0; i < candidates.length; i++) {
+        try {
+          const response = await fetch(candidates[i], { headers });
+          if (!response.ok) {
+            continue;
+          }
+          const blob = await response.blob();
+          if (blob && blob.size > 0) {
+            return URL.createObjectURL(blob);
+          }
+        } catch (e) {
+          // 璺ㄥ煙/缃戠粶寮傚父锛岀户缁皾璇曚笅涓�涓湴鍧�
+        }
+      }
+      return "";
+    },
+    /** 棰勮鍦板潃鍊欓�夛細鍘熷鍦板潃 + 璧版帴鍙e墠缂�锛�/dev-api銆�/prod-api锛夌殑鍦板潃 */
+    buildPreviewCandidates(url) {
+      const list = [url];
+      if (/^https?:\/\//i.test(url)) {
+        const base = (process.env.VUE_APP_BASE_API || "").replace(/\/$/, "");
+        let path = url.replace(/^https?:\/\/[^/]+/i, "");
+        if (base && (path === base || path.indexOf(base + "/") === 0)) {
+          path = path.slice(base.length);
+        }
+        if (base) {
+          list.push(base + path);
+        }
+      }
+      return list.filter((item, index) => list.indexOf(item) === index);
+    },
+    /** 鍏抽棴棰勮鏃堕噴鏀� blob 鍦板潃 */
+    handlePreviewClosed() {
+      this.revokePreviewObjectUrl();
+      this.invoiceEmbeddedFile = null;
+    },
+    revokePreviewObjectUrl() {
+      if (this.previewObjectUrl) {
+        URL.revokeObjectURL(this.previewObjectUrl);
+        this.previewObjectUrl = "";
+      }
+    },
+    /** 閲戦鏄剧ず锛氫繚鐣欎袱浣嶅皬鏁� */
+    amountText(value) {
+      if (value === null || value === undefined || value === "") {
+        return "0.00";
+      }
+      const num = Number(value);
+      return Number.isFinite(num) ? num.toFixed(2) : value;
+    },
+    /** 浠庡湴鍧�涓彇鏂囦欢鍚嶏紙鏃犲彂绁ㄥ悕绉版椂鍏滃簳锛� */
+    getFileName(url) {
+      const path = String(url || "").split("?")[0];
+      return path.substring(path.lastIndexOf("/") + 1) || "鍙戠エ鏂囦欢";
     },
     /** 鏍规嵁鏂囦欢鍦板潃鍒ゆ柇棰勮绫诲瀷 */
     getFileType(url) {
@@ -783,28 +973,129 @@
   margin-left: 12px;
   border: 1px solid #ebeef5;
   vertical-align: middle;
+  object-fit: contain;
+  background: #f5f7fa;
+  cursor: pointer;
 }
 
-.invoice-preview {
-  min-height: 120px;
+.invoice-preview-wrap {
+  display: flex;
+  width: 100%;
+  height: 70vh;
+}
 
-  .preview-image {
-    display: block;
-    max-width: 100%;
-    max-height: 70vh;
-    margin: 0 auto;
+/* 鎵嬫満绔崱鐗囧垪琛� */
+.invoice-cards {
+  min-height: 60px;
+}
+
+.invoice-card {
+  padding: 12px;
+  margin-bottom: 10px;
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+
+  &__head {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 10px;
   }
 
-  .preview-frame {
+  &__title {
+    flex: 1;
+    min-width: 0;
+    margin-right: 8px;
+    font-size: 15px;
+    font-weight: 600;
+    color: #303133;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  &__row {
+    display: flex;
+    align-items: center;
+    margin-bottom: 6px;
+    font-size: 13px;
+    color: #606266;
+
+    .label {
+      flex: 0 0 40px;
+      color: #909399;
+    }
+
+    .value {
+      flex: 1;
+      min-width: 0;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
+  &__actions {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 8px;
+    padding-top: 6px;
+    border-top: 1px dashed #ebeef5;
+
+    ::v-deep .el-button + .el-button {
+      margin-left: 16px;
+    }
+  }
+}
+
+.invoice-empty {
+  padding: 40px 0;
+  text-align: center;
+  font-size: 14px;
+  color: #909399;
+}
+
+/* 鎵嬫満绔�傞厤 */
+@media (max-width: 992px) {
+  .app-container {
+    padding: 8px;
+  }
+
+  .opo-query-form {
+    ::v-deep .el-form-item {
+      display: flex;
+      width: 100%;
+      margin-right: 0;
+      margin-bottom: 10px;
+    }
+
+    ::v-deep .el-form-item__content {
+      flex: 1;
+      min-width: 0;
+    }
+
+    ::v-deep .el-select,
+    ::v-deep .el-input,
+    ::v-deep .el-date-editor {
+      width: 100% !important;
+    }
+  }
+
+  /* 寮圭獥鍐呬袱鍒楄〃鍗曞湪鎵嬫満绔彉鍗曞垪 */
+  .el-col-12 {
     width: 100%;
-    height: 70vh;
-    border: none;
   }
 
-  .preview-tip {
-    padding: 40px 0;
-    text-align: center;
-    color: #909399;
+  /* 鍏ㄥ睆寮圭獥閲岀殑棰勮鍖哄崰婊″墿浣欓珮搴� */
+  .invoice-preview-wrap {
+    height: calc(100vh - 120px);
+  }
+
+  .invoice-thumb {
+    display: block;
+    margin: 8px 0 0;
   }
 }
 </style>
diff --git a/src/views/project/unitCost/organfee/index.vue b/src/views/project/unitCost/organfee/index.vue
index c3afdba..7f64c2a 100644
--- a/src/views/project/unitCost/organfee/index.vue
+++ b/src/views/project/unitCost/organfee/index.vue
@@ -63,6 +63,16 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="鍣ㄥ畼鍚嶇О" prop="organname">
+        <el-input
+          v-model="queryParams.organname"
+          placeholder="璇疯緭鍏ュ櫒瀹樺悕绉�"
+          clearable
+          size="small"
+          style="width: 160px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
       <el-form-item label="鍒╃敤鐘舵��" prop="organState">
         <el-select
           v-model="queryParams.organState"
@@ -125,6 +135,8 @@
       v-loading="loading"
       border
       :data="organfeeList"
+      show-summary
+      :summary-method="getSummaries"
       highlight-current-row
       @current-change="handleCurrentChange"
     >
@@ -214,7 +226,7 @@
       />
       <el-table-column
         label="鎿嶄綔"
-        width="210"
+        width="230"
         align="center"
         fixed="right"
       >
@@ -539,7 +551,8 @@
   addDonorchargeorgan,
   updateDonorchargeorgan,
   delDonorchargeorgan,
-  exportOrganfee
+  exportOrganfee,
+  getOrganfeeSummary
 } from "@/api/project/donorcharge";
 import { listOrganization } from "@/api/project/organization";
 
@@ -574,6 +587,12 @@
       showSearch: true,
       // 鎬绘潯鏁�
       total: 0,
+      // 鍚堣锛堝簲鏀�/瀹炴敹/鏈洖娆撅級
+      summary: {
+        organchargeTotal: 0,
+        amountTotal: 0,
+        unreceivedTotal: 0
+      },
       // 鍣ㄥ畼璐圭敤鍒楄〃
       organfeeList: [],
       // 琛ㄦ牸褰撳墠閫変腑琛�
@@ -632,6 +651,7 @@
         hospitalno: null,
         hospitalnoIsNull: false,
         donorname: null,
+        organname: null,
         organState: null,
         payStatus: null,
         paymentid: null,
@@ -659,6 +679,40 @@
         .finally(() => {
           this.loading = false;
         });
+      this.getSummary();
+    },
+    /** 鏌ヨ鍚堣锛堝簲鏀�/瀹炴敹/鏈洖娆撅級锛屾寜褰撳墠鏌ヨ鏉′欢缁熻鍏ㄩ儴缁撴灉 */
+    getSummary() {
+      getOrganfeeSummary(this.queryParams)
+        .then(response => {
+          const data = response.data || {};
+          this.summary = {
+            organchargeTotal: Number(data.organchargeTotal || 0),
+            amountTotal: Number(data.amountTotal || 0),
+            unreceivedTotal: Number(data.unreceivedTotal || 0)
+          };
+        })
+        .catch(() => {});
+    },
+    /** 琛ㄦ牸搴曢儴鍚堣琛� */
+    getSummaries({ columns }) {
+      const sums = [];
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = "鍚堣";
+          return;
+        }
+        if (column.property === "organcharge") {
+          sums[index] = this.summary.organchargeTotal.toFixed(2);
+        } else if (column.property === "amount") {
+          sums[index] = this.summary.amountTotal.toFixed(2);
+        } else if (column.label === "鏈洖娆鹃噾棰�") {
+          sums[index] = this.summary.unreceivedTotal.toFixed(2);
+        } else {
+          sums[index] = "";
+        }
+      });
+      return sums;
     },
     /** 瀵煎嚭鎸夐挳鎿嶄綔 */
     handleExport() {
@@ -884,6 +938,7 @@
       this.queryParams.hospitalnoIsNull = false;
       this.queryParams.hospitalno = null;
       this.queryParams.donorname = null;
+      this.queryParams.organname = null;
       this.queryParams.organState = null;
       this.queryParams.payStatus = null;
       this.queryParams.starttime = null;

--
Gitblit v1.9.3