Bladeren bron

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

Co-authored-by: Cursor <cursoragent@cursor.com>
wuzilong 1 maand geleden
bovenliggende
commit
d6fddc666c
1 gewijzigde bestanden met toevoegingen van 96 en 3 verwijderingen
  1. 96 3
      miniprogram/pages/bx/reimburse/detail.vue

+ 96 - 3
miniprogram/pages/bx/reimburse/detail.vue

@@ -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}`