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