feat(miniprogram): show linked invoice thumbnails on reimburse detail.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-09-16 21:22:09 +08:00
1 parent 0c269b6e73
commit d6fddc666c
1 file changed
+96 -3
+96 -3
View File
@@ -133,6 +133,34 @@
</view>
</view>
<view class="detail-info-card">
<view class="detail-info-card__title">关联发票</view>
<view v-if="linkedInvoices.length" class="invoice-thumb-grid" style="margin-top: 0">
<view
v-for="pick in linkedInvoices"
:key="pick.key"
class="invoice-thumb"
@click="previewInvoicePick(pick)"
>
<view class="invoice-thumb__media">
<image
v-if="isImageFileType(pick.fileType) && pick.previewUrl"
:src="pick.previewUrl"
mode="aspectFill"
/>
<view v-else class="invoice-thumb__doc">
<text class="invoice-thumb__doc-ext">{{ fileExtLabel(pick.fileName) }}</text>
</view>
</view>
<view class="invoice-thumb__meta">
<text class="invoice-thumb__amt">¥{{ formatAmount(pick.amount) }}</text>
<text class="invoice-thumb__date">{{ formatInvoiceDateYmd(pick.createTime) }}</text>
</view>
</view>
</view>
<view v-else class="detail-section-empty">暂无关联发票</view>
</view>
<view class="detail-info-card">
<view class="detail-info-card__title">申请附件</view>
<view v-if="attachments.length" class="att-grid">
@@ -187,6 +215,7 @@
<view v-if="showActions" class="bottom-action-bar">
<template v-if="detail?.status === 'draft'">
<button class="btn btn-primary" style="flex: 1" @click="handleEdit">编辑</button>
<button
class="btn btn-primary"
style="flex: 1"
@@ -205,8 +234,14 @@
</button>
</template>
<template v-if="detail?.status === 'rejected'">
<button class="btn btn-primary" style="flex: 1" @click="handleResubmit">
重新提交
<button class="btn btn-primary" style="flex: 1" @click="handleEdit">编辑</button>
<button
class="btn btn-primary"
style="flex: 1"
:disabled="actionLoading.submit"
@click="handleSubmit"
>
提交审批
</button>
</template>
<template v-if="canWithdraw">
@@ -238,6 +273,11 @@ import {
flattenExpenseTree,
resolveExpenseDisplay
} from '@/utils/bxFormat'
import {
isImageFileType,
fileExtLabel,
formatInvoiceDateYmd
} from '@/utils/bxInvoiceThumb'
import { getSafeAreaStyle } from '@/utils/safeArea'
import { useUserStore } from '@/stores/user'
import { buildApplyAttachmentDisplayName } from '@/utils/bxAttachmentDisplayName'
@@ -250,6 +290,7 @@ const detail = ref(null)
const detailTab = ref(0)
const approvals = ref([])
const attachments = ref([])
const linkedInvoices = ref([])
const flatList = ref([])
const actionLoading = reactive({ submit: false, delete: false, withdraw: false })
@@ -300,6 +341,26 @@ function previewAtt(file) {
uni.previewImage({ urls: [file.previewUrl], current: file.previewUrl })
}
async function previewInvoicePick(pick) {
if (!isImageFileType(pick.fileType)) {
uni.showToast({ title: '非图片附件请在管理端查看', icon: 'none' })
return
}
let url = pick.previewUrl
if (!url && pick.attachmentId) {
try {
const pRes = await attachmentApi.preview(pick.attachmentId)
url = pRes?.data?.url || ''
if (url) pick.previewUrl = url
} catch (e) {
uni.showToast({ title: '暂无法预览', icon: 'none' })
return
}
}
if (url) uni.previewImage({ urls: [url], current: url })
else uni.showToast({ title: '暂无法预览', icon: 'none' })
}
function attachmentDisplayName(file, index) {
return buildApplyAttachmentDisplayName({
remark: detail.value?.remark || '',
@@ -348,6 +409,38 @@ async function loadDetail() {
}
}
const invs = detail.value?.invoices || []
linkedInvoices.value = invs
.map((inv) => {
const attachmentId = String(inv.attachmentId || inv.attachment_id || '')
if (!attachmentId) return null
return {
key: `${inv.id || inv.invoiceId}:${attachmentId}`,
invoiceId: String(inv.id || inv.invoiceId || ''),
attachmentId,
amount: inv.amount,
createTime: inv.createTime || inv.create_time || '',
fileName: inv.fileName || inv.file_name || '发票附件',
fileType: inv.fileType || inv.file_type || '',
previewUrl: '',
previewLoading: false
}
})
.filter(Boolean)
for (const item of linkedInvoices.value) {
if (!isImageFileType(item.fileType)) continue
item.previewLoading = true
try {
const pRes = await attachmentApi.preview(item.attachmentId)
item.previewUrl = pRes?.data?.url || ''
} catch (e) {
// keep empty
} finally {
item.previewLoading = false
}
}
try {
const attRes = await attachmentApi.listByBiz('reimburse_apply', id)
const list = (attRes.data || []).map((a) => ({
@@ -410,7 +503,7 @@ function handleDelete() {
})
}
function handleResubmit() {
function handleEdit() {
if (!detail.value) return
uni.navigateTo({
url: `${PAGE_PATHS.bxReimburseApplyForm}?id=${detail.value.id}`