Просмотр исходного кода

feat(web-admin): 出入库现场页支持选择单据状态并作废只读

提交携带 status;作废需确认;作废后整页只读可查看。

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 месяцев назад
Родитель
Сommit
dee85f182e
1 измененных файлов с 51 добавлено и 3 удалено
  1. 51 3
      web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue

+ 51 - 3
web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue

@@ -5,8 +5,14 @@
 
       <van-loading v-if="loading" vertical class="page-loading">加载中...</van-loading>
       <van-empty v-else-if="errorMessage" :description="errorMessage" />
-      <van-empty v-else-if="page?.voided" description="单据已作废" />
       <template v-else-if="page">
+        <van-notice-bar
+          v-if="isReadOnly"
+          left-icon="info-o"
+          color="#92400e"
+          background="#fffbeb"
+          text="单据已作废,仅可查看"
+        />
         <van-cell-group inset class="section-card">
           <van-cell title="业务类型" :value="page.businessType || '—'" />
           <van-cell title="日期" :value="page.planDate || '—'" />
@@ -105,6 +111,7 @@
                   class="actual-box__field"
                   placeholder="请输入"
                   :border="false"
+                  :readonly="isReadOnly"
                 />
                 <span class="actual-box__unit">{{ item.unit || '吨' }}</span>
               </div>
@@ -118,12 +125,24 @@
               v-model="uploaderFiles"
               multiple
               :max-count="9"
+              :deletable="!isReadOnly"
+              :show-upload="!isReadOnly"
               :after-read="handleAfterRead"
               :before-delete="handleBeforeDelete"
             />
           </div>
         </van-cell-group>
 
+        <van-cell-group v-if="!isReadOnly" inset class="section-card" title="单据状态">
+          <div class="status-radio-wrap">
+            <van-radio-group v-model="selectedStatus" direction="horizontal">
+              <van-radio name="in_progress">进行中</van-radio>
+              <van-radio name="completed">已完成</van-radio>
+              <van-radio name="voided">已作废</van-radio>
+            </van-radio-group>
+          </div>
+        </van-cell-group>
+
         <van-cell-group inset class="section-card" title="现场备注">
           <van-field
             v-model="feedbackRemark"
@@ -133,6 +152,7 @@
             maxlength="500"
             show-word-limit
             placeholder="填写现场备注"
+            :readonly="isReadOnly"
           />
         </van-cell-group>
 
@@ -140,7 +160,7 @@
           <van-cell title="上次保存" :value="formatDateTime(page.siteFeedbackTime)" />
         </van-cell-group>
 
-        <footer class="page-footer">
+        <footer v-if="!isReadOnly" class="page-footer">
           <van-button type="primary" block round :loading="saving" @click="handleSave">
             提交
           </van-button>
@@ -152,7 +172,7 @@
 
 <script setup lang="ts">
   import type { UploaderFileListItem } from 'vant'
-  import { showFailToast, showSuccessToast } from 'vant'
+  import { showConfirmDialog, showFailToast, showSuccessToast } from 'vant'
   import {
     createPublicIoOrderApi,
     type PublicIoOrderItem,
@@ -161,9 +181,12 @@
   import PublicMobileShell from '@/layouts/PublicMobileShell.vue'
   import { buildAmapNavigationUrl } from '@/utils/amap/navigation'
 
+  import 'vant/es/dialog/style'
   import 'vant/es/toast/style'
   import '@/styles/public-mobile-vant.scss'
 
+  type IoOrderStatus = 'in_progress' | 'completed' | 'voided'
+
   const props = defineProps<{ kind: 'inbound' | 'outbound' }>()
   const api = createPublicIoOrderApi(props.kind)
   const route = useRoute()
@@ -180,6 +203,8 @@
   const feedbackImages = ref<string[]>([])
   const uploaderFiles = ref<UploaderFileListItem[]>([])
   const editableItems = ref<(PublicIoOrderItem & { actualQtyText: string })[]>([])
+  const selectedStatus = ref<IoOrderStatus>('in_progress')
+  const isReadOnly = computed(() => Boolean(page.value?.voided))
 
   const deliveryAddressDisplay = computed(() => {
     if (!page.value) return '—'
@@ -258,6 +283,11 @@
         ...it,
         actualQtyText: it.actualQty != null ? String(it.actualQty) : ''
       }))
+      selectedStatus.value = (
+        ['in_progress', 'completed', 'voided'].includes(data.status || '')
+          ? data.status
+          : 'in_progress'
+      ) as IoOrderStatus
       syncUploaderFromImages()
     } catch (e: any) {
       errorMessage.value = e?.message || '链接无效或已失效'
@@ -268,6 +298,7 @@
   }
 
   async function handleAfterRead(item: UploaderFileListItem | UploaderFileListItem[]) {
+    if (isReadOnly.value) return
     const files = Array.isArray(item) ? item : [item]
     for (const fileItem of files) {
       if (!fileItem.file) continue
@@ -290,6 +321,7 @@
   }
 
   function handleBeforeDelete(item: UploaderFileListItem) {
+    if (isReadOnly.value) return false
     const fileUrl = (item as UploaderFileListItem & { fileUrl?: string }).fileUrl
     if (fileUrl) {
       feedbackImages.value = feedbackImages.value.filter(u => u !== fileUrl)
@@ -298,9 +330,21 @@
   }
 
   async function handleSave() {
+    if (isReadOnly.value) return
+    if (selectedStatus.value === 'voided') {
+      try {
+        await showConfirmDialog({
+          title: '确认作废',
+          message: '确认作废本出入库单?作废后不可再提交。'
+        })
+      } catch {
+        return
+      }
+    }
     saving.value = true
     try {
       await api.saveFeedback(token.value, {
+        status: selectedStatus.value,
         remark: feedbackRemark.value,
         images: feedbackImages.value,
         items: editableItems.value.map(it => ({
@@ -350,6 +394,10 @@
     padding: 8px 14px 12px;
   }
 
+  .status-radio-wrap {
+    padding: 12px 16px 16px;
+  }
+
   .page-footer {
     padding: 16px;
   }