feat: 收票登记发票附件查看下载与查看态操作列(金蝶piaozone path附件代理)

- vehicle-attachment-table 新增 kingdee-invoice-no/show-view-actions props:piaozone path 型附件查看经 A801-2 代理取流进弹窗预览(objectURL 弹窗关闭回收),下载/批量下载走金蝶代理串行触发
- 查看态展示查看/下载操作列,编辑态为查看/下载/删除;未传 props 的页面行为不变
- 收票登记表单页接入(编辑/查看态均生效),kingdee-attachment.js 导出 requestKingdeeFile 供组件复用
This commit is contained in:
刘泉佳
2026-10-01 18:32:22 +08:00
parent 0484c2359d
commit b4ecc90470
3 changed files with 130 additions and 10 deletions
@@ -40,9 +40,23 @@
</el-table-column>
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
<el-table-column prop="uploadTime" label="上传时间" width="180" align="center" sortable />
<el-table-column v-if="!readonly" label="操作" width="100" align="center" fixed="right">
<template #default="{ $index }">
<el-link type="danger" @click="removeAttachment($index)">删除</el-link>
<el-table-column
v-if="!readonly || showViewActions"
label="操作"
:width="readonly ? 120 : 170"
align="center"
fixed="right"
>
<template #default="{ row, $index }">
<div class="vehicle-attachment-table__actions">
<el-link type="primary" :disabled="kingdeeBusy" @click="previewAttachment(row)"
>查看</el-link
>
<el-link type="primary" :disabled="kingdeeBusy" @click="downloadAttachment(row)"
>下载</el-link
>
<el-link v-if="!readonly" type="danger" @click="removeAttachment($index)">删除</el-link>
</div>
</template>
</el-table-column>
</el-table>
@@ -68,6 +82,7 @@
width="90%"
top="4vh"
class="vehicle-attachment-table__viewer-dialog"
@closed="handlePreviewClosed"
>
<pdf-preview
v-if="documentPreviewVisible && previewFile.url && previewFile.isPdf"
@@ -108,6 +123,11 @@ import '@open-file-viewer/core/style.css';
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
import PdfPreview from '@/components/pdf-preview/main.vue';
import { downloadFileByUrl } from '@/utils/util';
import {
downloadKingdeeInvoiceFile,
isKingdeePathAttachment,
requestKingdeeFile,
} from '@/utils/kingdee-attachment';
const attachmentFileTypes = [
'pdf',
@@ -153,6 +173,16 @@ export default {
type: Number,
default: 500,
},
// 金蝶侧附件(piaozone path 型)代理下载所依赖的发票号;行内自带 invoiceNo 时优先用行内值
kingdeeInvoiceNo: {
type: String,
default: '',
},
// 查看态也展示「查看/下载」操作列(编辑态始终展示,含删除)
showViewActions: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue'],
data() {
@@ -164,6 +194,10 @@ export default {
imagePreviewIndex: 0,
documentPreviewVisible: false,
previewFile: {},
// 金蝶侧附件代理请求进行中(行级按钮共享 loading 守卫,防重复触发)
kingdeeBusy: false,
// 金蝶附件预览用的 objectURL,弹窗关闭时回收
kingdeeObjectUrl: '',
viewerPlugins,
viewerToolbar: {
download: true,
@@ -198,6 +232,12 @@ export default {
attachmentUrl(row = {}) {
return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || row.src || '';
},
kingdeeInvoiceNoOf(row = {}) {
return row.invoiceNo || this.kingdeeInvoiceNo || '';
},
isKingdeeRow(row = {}) {
return isKingdeePathAttachment(row) && Boolean(this.kingdeeInvoiceNoOf(row));
},
attachmentExtension(row = {}) {
const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0];
const index = source.lastIndexOf('.');
@@ -245,6 +285,10 @@ export default {
return `${(value / 1024 / 1024).toFixed(1)}MB`;
},
downloadAttachment(row) {
// 金蝶侧附件(piaozone path 型)经后端代理 A801-2 下载,OSS 直链维持前端直连下载
if (this.isKingdeeRow(row)) {
return this.downloadKingdeeAttachment(row);
}
const url = this.attachmentUrl(row);
if (!url) {
this.$message.warning('附件地址为空,无法下载');
@@ -252,20 +296,47 @@ export default {
}
downloadFileByUrl(url, this.attachmentName(row));
},
async downloadKingdeeAttachment(row) {
if (this.kingdeeBusy) return;
this.kingdeeBusy = true;
try {
await downloadKingdeeInvoiceFile(this.kingdeeInvoiceNoOf(row), this.attachmentName(row));
} finally {
this.kingdeeBusy = false;
}
},
batchDownload() {
const rows = this.selectedRows.length ? this.selectedRows : this.rows;
const downloadableRows = rows.filter(row => this.attachmentUrl(row));
if (!downloadableRows.length) {
const kingdeeRows = rows.filter(row => this.isKingdeeRow(row));
const urlRows = rows.filter(row => !this.isKingdeeRow(row) && this.attachmentUrl(row));
if (!kingdeeRows.length && !urlRows.length) {
this.$message.warning(
this.selectedRows.length ? '所选附件暂无可下载地址' : '暂无可下载附件'
);
return;
}
downloadableRows.forEach((row, index) => {
urlRows.forEach((row, index) => {
window.setTimeout(() => this.downloadAttachment(row), index * 500);
});
// 金蝶侧附件走代理接口,串行触发避免并发重复回源
this.downloadKingdeeRows(kingdeeRows);
},
async downloadKingdeeRows(rows) {
if (this.kingdeeBusy) return;
this.kingdeeBusy = true;
try {
for (const row of rows) {
await downloadKingdeeInvoiceFile(this.kingdeeInvoiceNoOf(row), this.attachmentName(row));
}
} finally {
this.kingdeeBusy = false;
}
},
previewAttachment(row) {
// 金蝶侧附件经 A801-2 代理取流后进弹窗预览,OSS 直链维持原逻辑
if (this.isKingdeeRow(row)) {
return this.previewKingdeeAttachment(row);
}
const url = this.attachmentUrl(row);
if (!url) {
this.$message.warning('附件地址为空,无法预览');
@@ -288,6 +359,37 @@ export default {
};
this.documentPreviewVisible = true;
},
async previewKingdeeAttachment(row) {
if (this.kingdeeBusy) return;
this.kingdeeBusy = true;
try {
const blob = await requestKingdeeFile(this.kingdeeInvoiceNoOf(row));
this.releaseKingdeeObjectUrl();
const isPdf = this.isPdf(row) || String(blob.type || '').includes('application/pdf');
this.kingdeeObjectUrl = URL.createObjectURL(blob);
this.previewFile = {
name: this.attachmentName(row),
url: this.kingdeeObjectUrl,
mimeType: isPdf ? 'application/pdf' : blob.type || '',
isPdf,
};
this.documentPreviewVisible = true;
} catch (error) {
if (!error?.silent) this.$message.error(error?.message || '附件预览失败');
} finally {
this.kingdeeBusy = false;
}
},
handlePreviewClosed() {
this.releaseKingdeeObjectUrl();
this.previewFile = {};
},
releaseKingdeeObjectUrl() {
if (this.kingdeeObjectUrl) {
URL.revokeObjectURL(this.kingdeeObjectUrl);
this.kingdeeObjectUrl = '';
}
},
handlePreviewUnsupported() {
this.$message.warning('当前文件暂不支持在线预览');
},
@@ -318,6 +420,17 @@ export default {
text-decoration: underline;
}
&__actions {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
:deep(.el-link) {
font-size: 13px;
}
}
&__upload {
display: flex;
justify-content: flex-start;
+4 -3
View File
@@ -3,7 +3,7 @@
*
* 票池发票附件为 {source:'piaozone', path, name}:path 是金蝶侧文件 id(非 http 直链),
* 须由后端代理 A801-2 流式回源;OSS 直链附件(url/link/fileUrl)仍由前端直连下载/预览。
* 三处入口共用:票池弹窗、正式结算认领发票行、付款申请发票附件行。
* 入口共用:票池弹窗、正式结算认领发票行、付款申请发票附件行、收票登记附件表。
*/
import { ElMessage } from 'element-plus';
import { downloadFile } from '@/utils/util';
@@ -22,11 +22,12 @@ export const isKingdeePathAttachment = attachment => {
/**
* 请求金蝶文件流:成功返回 Blob;业务失败为 HTTP 200 + R.fail JSON(响应通道未写流时后端即失败),
* 按 Content-Type 识别并抛出带 msg 的错误;网络级失败拦截器已提示,标记 silent 避免重复提示
* 按 Content-Type 识别并抛出带 msg 的错误;网络级失败拦截器已提示,标记 silent 避免重复提示。
* 供查看(弹窗内预览)/下载两类入口复用
* @param {String} invoiceNo 发票号码
* @returns {Promise<Blob>}
*/
const requestKingdeeFile = async invoiceNo => {
export const requestKingdeeFile = async invoiceNo => {
let res;
try {
res = await downloadInvoicePoolAttachment(invoiceNo);
+7 -1
View File
@@ -118,7 +118,13 @@
</section-card>
<section-card title="附件信息">
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
<!-- 金蝶票池认领的发票附件为 piaozone path 型,需带发票号走 A801-2 代理查看/下载 -->
<vehicle-attachment-table
v-model="form.attachments"
:readonly="readonly"
show-view-actions
:kingdee-invoice-no="form.invoiceNo"
/>
</section-card>
<section-card title="备注">