|
|
@@ -127,21 +127,44 @@
|
|
|
</section>
|
|
|
|
|
|
<section
|
|
|
- v-if="linkedInvoices.length"
|
|
|
- class="detail-info-card"
|
|
|
+ v-if="invoiceDisplayList.length"
|
|
|
+ class="detail-info-card detail-info-card--media"
|
|
|
aria-label="关联发票"
|
|
|
>
|
|
|
<div class="detail-info-card__title">关联发票</div>
|
|
|
<div
|
|
|
- v-for="inv in linkedInvoices"
|
|
|
- :key="inv.id"
|
|
|
- class="list-card-kv"
|
|
|
- style="padding: 6px 0"
|
|
|
+ v-for="row in invoiceDisplayList"
|
|
|
+ :key="row.inv.id"
|
|
|
+ class="approve-invoice-item"
|
|
|
>
|
|
|
- <span class="list-card-kv__k">金额 / 备注</span>
|
|
|
- <span class="list-card-kv__v">
|
|
|
- ¥{{ Number(inv.amount || 0).toFixed(2) }} · {{ inv.remark || '-' }}
|
|
|
- </span>
|
|
|
+ <div class="list-card-kv" style="padding: 6px 0">
|
|
|
+ <span class="list-card-kv__k">金额 / 备注</span>
|
|
|
+ <span class="list-card-kv__v">
|
|
|
+ ¥{{ Number(row.inv.amount || 0).toFixed(2) }} · {{ row.inv.remark || '-' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <template v-if="row.file">
|
|
|
+ <div class="advance-detail-att-grid att-grid advance-att-grid">
|
|
|
+ <div
|
|
|
+ class="att-img advance-att-tile"
|
|
|
+ :title="row.file.fileName"
|
|
|
+ role="button"
|
|
|
+ tabindex="0"
|
|
|
+ @click="onPreviewFile(row.file, invoiceAttachments)"
|
|
|
+ @keydown.enter.prevent="onPreviewFile(row.file, invoiceAttachments)"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ v-if="isImageFile(row.file.fileType) && row.file.previewUrl"
|
|
|
+ :src="row.file.previewUrl"
|
|
|
+ :alt="row.file.fileName"
|
|
|
+ />
|
|
|
+ <div v-else class="att-file-icon att-doc-thumb">
|
|
|
+ <span class="att-doc-thumb__ext">{{ fileExt(row.file.fileName) }}</span>
|
|
|
+ <span class="att-doc-thumb__name">{{ row.file.fileName }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
@@ -499,6 +522,17 @@ const linkedInvoices = computed(() => {
|
|
|
const list = detail.value?.invoices
|
|
|
return Array.isArray(list) ? list : []
|
|
|
})
|
|
|
+const invoiceAttachments = ref<any[]>([])
|
|
|
+const invoiceDisplayList = computed(() => {
|
|
|
+ const attMap = new Map(invoiceAttachments.value.map((f) => [String(f.id), f]))
|
|
|
+ return linkedInvoices.value.map((inv: any) => {
|
|
|
+ const aid = String(inv.attachmentId || inv.attachment_id || '').trim()
|
|
|
+ return {
|
|
|
+ inv,
|
|
|
+ file: aid ? attMap.get(aid) || null : null,
|
|
|
+ }
|
|
|
+ })
|
|
|
+})
|
|
|
const detailTab = ref(0)
|
|
|
const approvals = ref<any[]>([])
|
|
|
const applyAttachments = ref<any[]>([])
|
|
|
@@ -519,6 +553,44 @@ function fileExt(name: string): string {
|
|
|
return ext ? ext.toUpperCase() : 'FILE'
|
|
|
}
|
|
|
|
|
|
+/** 从详情 invoices 组装可预览附件列表 */
|
|
|
+async function loadInvoiceAttachmentsFromDetail(invoices: any[]): Promise<any[]> {
|
|
|
+ const files = (invoices || [])
|
|
|
+ .map((inv: any) => {
|
|
|
+ const id = String(inv.attachmentId || inv.attachment_id || '').trim()
|
|
|
+ if (!id) return null
|
|
|
+ return {
|
|
|
+ id,
|
|
|
+ fileName: inv.fileName || inv.file_name || '发票附件',
|
|
|
+ fileType: inv.fileType || inv.file_type || '',
|
|
|
+ previewUrl: '',
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .filter(Boolean) as any[]
|
|
|
+
|
|
|
+ const seen = new Set<string>()
|
|
|
+ const unique = files.filter((f) => {
|
|
|
+ const key = String(f.id)
|
|
|
+ if (seen.has(key)) return false
|
|
|
+ seen.add(key)
|
|
|
+ return true
|
|
|
+ })
|
|
|
+
|
|
|
+ await Promise.all(
|
|
|
+ unique
|
|
|
+ .filter((a) => isImageFile(a.fileType || ''))
|
|
|
+ .map(async (a) => {
|
|
|
+ try {
|
|
|
+ const pRes: any = await attachmentApi.preview(a.id)
|
|
|
+ a.previewUrl = pRes.data?.url || pRes.url || ''
|
|
|
+ } catch {
|
|
|
+ /* skip */
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ return unique
|
|
|
+}
|
|
|
+
|
|
|
const totalAmount = computed(() => {
|
|
|
if (!detail.value?.details?.length) return formatAmount(detail.value?.amount || 0)
|
|
|
const total = detail.value.details.reduce((sum: number, d: any) => sum + Number(d.amount || 0), 0)
|
|
|
@@ -642,16 +714,19 @@ async function loadDetail() {
|
|
|
}
|
|
|
|
|
|
// Load all attachment categories in parallel + 冲抵明细(备用金订单)
|
|
|
- const [apply, pay, offset, offRes] = await Promise.all([
|
|
|
+ const invoices = Array.isArray(detail.value?.invoices) ? detail.value.invoices : []
|
|
|
+ const [apply, pay, offset, offRes, invoiceAtts] = await Promise.all([
|
|
|
loadAttachments('reimburse_apply', id),
|
|
|
loadAttachments('reimburse_pay', id),
|
|
|
loadAttachments('reimburse_offset', id),
|
|
|
paymentApi.reimburseOffsetRecords(id).catch(() => ({ data: [] })),
|
|
|
+ loadInvoiceAttachmentsFromDetail(invoices),
|
|
|
])
|
|
|
applyAttachments.value = apply
|
|
|
payAttachments.value = pay
|
|
|
offsetAttachments.value = offset
|
|
|
offsetRecords.value = (offRes as any)?.data ?? []
|
|
|
+ invoiceAttachments.value = invoiceAtts
|
|
|
} catch (e: any) {
|
|
|
showToast(e?.message || '加载失败')
|
|
|
}
|
|
|
@@ -719,9 +794,17 @@ async function handleWithdraw() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function onPreviewFile(file: any) {
|
|
|
+async function onPreviewFile(file: any, list?: any[]) {
|
|
|
if (isImageFile(file.fileType) && file.previewUrl) {
|
|
|
- showImagePreview({ images: [file.previewUrl] })
|
|
|
+ const source = Array.isArray(list) && list.length ? list : [file]
|
|
|
+ const images = source
|
|
|
+ .filter((f: any) => isImageFile(f.fileType) && f.previewUrl)
|
|
|
+ .map((f: any) => f.previewUrl)
|
|
|
+ const startPosition = Math.max(0, images.indexOf(file.previewUrl))
|
|
|
+ showImagePreview({
|
|
|
+ images: images.length ? images : [file.previewUrl],
|
|
|
+ startPosition,
|
|
|
+ })
|
|
|
} else {
|
|
|
await downloadFile(file)
|
|
|
}
|
|
|
@@ -780,6 +863,12 @@ onMounted(async () => {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+.approve-invoice-item + .approve-invoice-item {
|
|
|
+ margin-top: 8px;
|
|
|
+ padding-top: 8px;
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
+}
|
|
|
+
|
|
|
/* ===== 票券头 / Tab / 字段 / 附件网格:与备用金详情对齐 ===== */
|
|
|
.advance-ticket-shell {
|
|
|
--ticket-bg: var(--primary);
|