Przeglądaj źródła

feat(bx-mobile): show attachment grid picker on reimburse apply

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 miesięcy temu
rodzic
commit
0e8cefe89f
1 zmienionych plików z 226 dodań i 35 usunięć
  1. 226 35
      bx-mobile/src/views/reimburse/ReimburseApplyPage.vue

+ 226 - 35
bx-mobile/src/views/reimburse/ReimburseApplyPage.vue

@@ -161,37 +161,82 @@
           <button type="button" class="btn btn-primary btn-sm" @click="openInvoicePicker">
           <button type="button" class="btn btn-primary btn-sm" @click="openInvoicePicker">
             从发票夹选择
             从发票夹选择
           </button>
           </button>
-          <div v-if="selectedInvoicePicksSorted.length" class="invoice-picked">
+          <div v-if="selectedInvoicePicksSorted.length" class="invoice-picked-row">
             <div
             <div
               v-for="pick in selectedInvoicePicksSorted"
               v-for="pick in selectedInvoicePicksSorted"
               :key="pick.key"
               :key="pick.key"
-              class="invoice-picked__item"
+              class="invoice-picked-card"
             >
             >
-              <span>¥{{ money(pick.amount) }} · {{ pick.fileName }}</span>
-              <button type="button" class="del" @click="removeInvoicePick(pick.key)">×</button>
+              <button
+                type="button"
+                class="invoice-picked-card__remove"
+                aria-label="移除"
+                @click="removeInvoicePick(pick.key)"
+              >
+                ×
+              </button>
+              <div class="invoice-picked-card__preview">
+                <img
+                  v-if="isImageFileType(pick.fileType) && pick.previewUrl"
+                  :src="pick.previewUrl"
+                  :alt="pick.fileName"
+                />
+                <div v-else class="invoice-picked-card__icon">
+                  {{ isImageFileType(pick.fileType) ? '图片' : 'PDF' }}
+                </div>
+              </div>
+              <div class="invoice-picked-card__meta">
+                <span class="invoice-picked-card__name">{{ pick.fileName }}</span>
+                <div class="invoice-picked-card__amount">¥{{ money(pick.amount) }}</div>
+              </div>
             </div>
             </div>
           </div>
           </div>
           <p class="form-tip">请先在「我的发票夹」上传发票;提交时按合计扣减余额。选择对象为附件文件。</p>
           <p class="form-tip">请先在「我的发票夹」上传发票;提交时按合计扣减余额。选择对象为附件文件。</p>
         </div>
         </div>
 
 
-        <van-popup v-model:show="showInvoicePicker" position="bottom" round :style="{ height: '70%' }">
+        <van-popup v-model:show="showInvoicePicker" position="bottom" round :style="{ height: '72%' }">
           <div class="invoice-picker">
           <div class="invoice-picker">
             <div class="invoice-picker__title">选择发票附件</div>
             <div class="invoice-picker__title">选择发票附件</div>
-            <van-cell-group>
-              <van-cell
-                v-for="item in attachItems"
-                :key="item.key"
-                clickable
-                @click="togglePickerKey(item.key)"
+            <div class="invoice-picker__body">
+              <p v-if="!pickerMonthGroups.length" class="invoice-picker__empty">暂无发票附件</p>
+              <section
+                v-for="group in pickerMonthGroups"
+                :key="group.monthKey"
+                class="invoice-month"
               >
               >
-                <template #title>
-                  ¥{{ money(item.amount) }} · {{ item.fileName }}
-                </template>
-                <template #right-icon>
-                  <span v-if="pickerDraftKeys.includes(item.key)">✓</span>
-                </template>
-              </van-cell>
-            </van-cell-group>
+                <h4 class="invoice-month__title">{{ group.monthLabel }}</h4>
+                <div class="invoice-attach-grid">
+                  <button
+                    v-for="item in group.items"
+                    :key="item.key"
+                    type="button"
+                    class="invoice-attach-card"
+                    :class="{ 'is-selected': pickerDraftKeys.includes(item.key) }"
+                    @click="togglePickerKey(item.key)"
+                  >
+                    <span class="invoice-attach-card__index">{{ item.serial }}</span>
+                    <span
+                      v-if="pickerDraftKeys.includes(item.key)"
+                      class="invoice-attach-card__check"
+                    >✓</span>
+                    <div class="invoice-attach-card__preview">
+                      <img
+                        v-if="isImageFileType(item.fileType) && item.previewUrl"
+                        :src="item.previewUrl"
+                        :alt="item.fileName"
+                      />
+                      <div v-else class="invoice-attach-card__doc">
+                        {{ isImageFileType(item.fileType) ? '图片' : 'PDF' }}
+                      </div>
+                    </div>
+                    <div class="invoice-attach-card__meta">
+                      <span class="invoice-attach-card__name">{{ item.fileName }}</span>
+                      <div class="invoice-attach-card__amount">¥{{ money(item.amount) }}</div>
+                    </div>
+                  </button>
+                </div>
+              </section>
+            </div>
             <div class="invoice-picker__footer">
             <div class="invoice-picker__footer">
               <button type="button" class="btn btn-primary btn-block" @click="confirmInvoicePicker">
               <button type="button" class="btn btn-primary btn-block" @click="confirmInvoicePicker">
                 确定(已选 {{ pickerDraftKeys.length }})
                 确定(已选 {{ pickerDraftKeys.length }})
@@ -569,7 +614,8 @@ function confirmInvoicePicker() {
   const set = new Set(pickerDraftKeys.value)
   const set = new Set(pickerDraftKeys.value)
   selectedInvoicePicks.value = attachItems.value
   selectedInvoicePicks.value = attachItems.value
     .filter((i) => set.has(i.key))
     .filter((i) => set.has(i.key))
-    .map((i) => ({ ...i }))
+    .map((i) => ({ ...i, previewUrl: i.previewUrl }))
+  kickAttachPreviews(selectedInvoicePicks.value)
   showInvoicePicker.value = false
   showInvoicePicker.value = false
 }
 }
 
 
@@ -919,26 +965,69 @@ onMounted(async () => {
   color: #dc2626;
   color: #dc2626;
   margin-left: 4px;
   margin-left: 4px;
 }
 }
-.invoice-picked {
-  margin-top: 10px;
+.invoice-picked-row {
+  margin-top: 12px;
   display: flex;
   display: flex;
-  flex-direction: column;
-  gap: 8px;
+  gap: 10px;
+  overflow-x: auto;
+  padding-bottom: 4px;
+  -webkit-overflow-scrolling: touch;
 }
 }
-.invoice-picked__item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 8px 10px;
-  background: #f8fafc;
+.invoice-picked-card {
+  position: relative;
+  flex: 0 0 120px;
+  width: 120px;
+  border: 1px solid var(--border);
   border-radius: 8px;
   border-radius: 8px;
-  font-size: 13px;
+  background: #fff;
+  overflow: hidden;
 }
 }
-.invoice-picked__item .del {
+.invoice-picked-card__remove {
+  position: absolute;
+  top: 4px;
+  right: 4px;
+  z-index: 2;
+  width: 22px;
+  height: 22px;
   border: none;
   border: none;
-  background: transparent;
-  font-size: 18px;
-  color: #94a3b8;
+  border-radius: 50%;
+  background: rgba(15, 23, 42, 0.55);
+  color: #fff;
+  font-size: 14px;
+  line-height: 1;
+}
+.invoice-picked-card__preview {
+  height: 80px;
+  background: #f1f5f9;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+.invoice-picked-card__preview img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+.invoice-picked-card__icon {
+  font-size: 12px;
+  color: var(--text-muted);
+}
+.invoice-picked-card__meta {
+  padding: 8px;
+}
+.invoice-picked-card__name {
+  display: block;
+  font-size: 11px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.invoice-picked-card__amount {
+  margin-top: 4px;
+  font-size: 12px;
+  font-weight: 600;
+  color: var(--primary-dark);
 }
 }
 .form-tip {
 .form-tip {
   margin-top: 8px;
   margin-top: 8px;
@@ -956,6 +1045,108 @@ onMounted(async () => {
   font-weight: 600;
   font-weight: 600;
   text-align: center;
   text-align: center;
 }
 }
+.invoice-picker__body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 12px;
+  -webkit-overflow-scrolling: touch;
+}
+.invoice-picker__empty {
+  text-align: center;
+  color: var(--text-muted);
+  padding: 40px 0;
+  font-size: 13px;
+}
+.invoice-month {
+  margin-bottom: 18px;
+}
+.invoice-month__title {
+  margin: 0 0 10px 4px;
+  font-size: 13px;
+  font-weight: 600;
+}
+.invoice-attach-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 10px;
+}
+.invoice-attach-card {
+  position: relative;
+  padding: 0;
+  border: 1.5px solid var(--border);
+  border-radius: 8px;
+  background: #fff;
+  text-align: left;
+  overflow: hidden;
+}
+.invoice-attach-card.is-selected {
+  border-color: var(--primary);
+  box-shadow: 0 0 0 1px var(--primary);
+}
+.invoice-attach-card__index {
+  position: absolute;
+  top: 4px;
+  left: 4px;
+  z-index: 1;
+  min-width: 18px;
+  height: 18px;
+  padding: 0 4px;
+  border-radius: 9px;
+  background: rgba(15, 23, 42, 0.5);
+  color: #fff;
+  font-size: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.invoice-attach-card__check {
+  position: absolute;
+  top: 4px;
+  right: 4px;
+  z-index: 1;
+  width: 20px;
+  height: 20px;
+  border-radius: 50%;
+  background: var(--primary);
+  color: #fff;
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.invoice-attach-card__preview {
+  height: 72px;
+  background: #f1f5f9;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+.invoice-attach-card__preview img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+.invoice-attach-card__doc {
+  font-size: 11px;
+  color: var(--text-muted);
+}
+.invoice-attach-card__meta {
+  padding: 6px 8px 8px;
+}
+.invoice-attach-card__name {
+  display: block;
+  font-size: 11px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.invoice-attach-card__amount {
+  margin-top: 2px;
+  font-size: 12px;
+  font-weight: 600;
+  color: var(--primary-dark);
+}
 .invoice-picker__footer {
 .invoice-picker__footer {
   margin-top: auto;
   margin-top: auto;
   padding: 12px 16px;
   padding: 12px 16px;