Gogs 2 сар өмнө
parent
commit
a09ca493e6

+ 88 - 15
src/views/storage/overview/index.vue

@@ -70,14 +70,18 @@
                 <el-button type="primary" size="small" @click="openUploadDialog">
                   <i class="el-icon-upload2"></i> 上传数据
                 </el-button>
-                <el-button size="small" @click="refreshData">
+                <el-button size="small" @click="refreshData" :disabled="uploadTaskPolling">
                   <i class="el-icon-refresh"></i> 刷新
                 </el-button>
               </div>
             </div>
           </template>
           <div class="upload-hint">
-            支持上传入库、销售、组装、产品资料、半成品映射文件(xlsx/xls)。上传后将自动刷新本页分析。
+            支持上传入库、销售、组装、产品资料、半成品映射文件(xlsx/xls)。上传后会先创建后台任务,处理完成后自动刷新本页分析。
+          </div>
+          <div v-if="uploadTaskStatusText" class="upload-task-status">
+            <el-tag :type="uploadTaskStatusType" size="mini">{{ uploadTaskStatusLabel }}</el-tag>
+            <span>{{ uploadTaskStatusText }}</span>
           </div>
           <div v-if="lastUploadSummary" class="upload-summary">
             <el-tag type="success" size="mini">最近上传</el-tag>
@@ -105,7 +109,7 @@
           <template slot="header">
             <div class="card-header">
               <span>SKU指标汇总</span>
-              <el-button type="primary" size="small" @click="refreshData">
+              <el-button type="primary" size="small" @click="refreshData" :disabled="uploadTaskPolling">
                 <i class="el-icon-refresh"></i> 刷新
               </el-button>
             </div>
@@ -165,7 +169,7 @@
           <template slot="header">
             <div class="card-header">
               <span>SPU指标汇总(成品)</span>
-              <el-button type="primary" size="small" @click="refreshData">
+              <el-button type="primary" size="small" @click="refreshData" :disabled="uploadTaskPolling">
                 <i class="el-icon-refresh"></i> 刷新
               </el-button>
             </div>
@@ -271,8 +275,8 @@
       <input ref="productInput" class="hidden-file-input" type="file" accept=".xlsx,.xls" @change="handleFileChange('product', $event)">
       <input ref="mappingInput" class="hidden-file-input" type="file" accept=".xlsx,.xls" @change="handleFileChange('mapping', $event)">
       <span slot="footer" class="dialog-footer">
-        <el-button @click="uploadDialogVisible = false">取消</el-button>
-        <el-button type="primary" :loading="uploadLoading" @click="submitUpload">上传并刷新</el-button>
+        <el-button @click="uploadDialogVisible = false" :disabled="uploadLoading">取消</el-button>
+        <el-button type="primary" :loading="uploadLoading" @click="submitUpload">上传并处理</el-button>
       </span>
     </el-dialog>
   </div>
@@ -283,6 +287,9 @@ import request from '@/utils/request'
 import * as echarts from 'echarts'
 require('echarts/theme/macarons')
 
+const TASK_POLL_INTERVAL = 2000
+const TASK_POLL_LIMIT = 120
+
 export default {
   name: 'StorageOverview',
   data() {
@@ -317,7 +324,26 @@ export default {
         product: '',
         mapping: ''
       },
-      lastUploadSummary: ''
+      lastUploadSummary: '',
+      uploadTaskId: '',
+      uploadTaskStatus: '',
+      uploadTaskStatusText: '',
+      uploadTaskPolling: false
+    }
+  },
+  computed: {
+    uploadTaskStatusType() {
+      if (this.uploadTaskStatus === 'success') return 'success'
+      if (this.uploadTaskStatus === 'failed') return 'danger'
+      if (this.uploadTaskStatus === 'running') return 'warning'
+      return 'info'
+    },
+    uploadTaskStatusLabel() {
+      if (this.uploadTaskStatus === 'success') return '处理完成'
+      if (this.uploadTaskStatus === 'failed') return '处理失败'
+      if (this.uploadTaskStatus === 'running') return '处理中'
+      if (this.uploadTaskStatus === 'pending') return '排队中'
+      return '任务状态'
     }
   },
   mounted() {
@@ -510,6 +536,8 @@ export default {
       if (this.uploadFiles.mapping) formData.append('semiMappingFile', this.uploadFiles.mapping)
 
       this.uploadLoading = true
+      this.uploadTaskStatus = 'pending'
+      this.uploadTaskStatusText = '文件上传中,请稍候'
       try {
         const res = await this.inventoryRequest({
           url: '/api/inventory/upload',
@@ -518,23 +546,67 @@ export default {
           headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false },
           timeout: 300000
         })
-        if (res && res.code === 200) {
-          const count = res.data && res.data.count ? res.data.count : 0
-          this.lastUploadSummary = `已保存 ${count} 个文件`
-          this.$message.success('上传成功,已刷新分析数据')
-          this.uploadDialogVisible = false
-          this.resetUploadForm()
-          await this.refreshData()
-        } else {
+        if (!(res && res.code === 200 && res.data && res.data.taskId)) {
           this.$message.error((res && res.msg) || '上传失败')
+          this.uploadTaskStatus = 'failed'
+          this.uploadTaskStatusText = (res && res.msg) || '上传失败'
+          return
         }
+        this.uploadTaskId = res.data.taskId
+        this.uploadTaskStatus = res.data.status || 'pending'
+        this.uploadTaskStatusText = res.data.message || '任务已创建,等待后台处理'
+        this.lastUploadSummary = `已接收 ${res.data.count || 0} 个文件,任务ID:${this.uploadTaskId}`
+        this.uploadDialogVisible = false
+        this.resetUploadForm()
+        this.$message.success('上传成功,后台正在处理库存数据')
+        await this.pollUploadTask(this.uploadTaskId)
       } catch (error) {
         console.error('上传库存数据失败:', error)
+        this.uploadTaskStatus = 'failed'
+        this.uploadTaskStatusText = '上传失败,请检查文件格式或后端日志'
         this.$message.error('上传失败,请检查文件格式或后端日志')
       } finally {
         this.uploadLoading = false
       }
     },
+    async pollUploadTask(taskId) {
+      this.uploadTaskPolling = true
+      try {
+        for (let attempt = 0; attempt < TASK_POLL_LIMIT; attempt++) {
+          const res = await this.inventoryRequest({
+            url: `/api/inventory/upload-task/${taskId}`,
+            method: 'get',
+            timeout: 30000
+          })
+          const data = this.normalizeResponse(res)
+          if (!data) {
+            throw new Error((res && res.msg) || '任务状态获取失败')
+          }
+          this.uploadTaskStatus = data.status || 'pending'
+          this.uploadTaskStatusText = data.message || '后台处理中'
+          if (data.status === 'success') {
+            this.lastUploadSummary = `已处理 ${data.count || 0} 个文件`
+            this.$message.success('库存数据处理完成,已刷新分析结果')
+            await this.refreshData()
+            return
+          }
+          if (data.status === 'failed') {
+            throw new Error(data.message || '库存数据处理失败')
+          }
+          await this.sleep(TASK_POLL_INTERVAL)
+        }
+        throw new Error('库存数据处理超时,请稍后手动刷新')
+      } catch (error) {
+        this.uploadTaskStatus = 'failed'
+        this.uploadTaskStatusText = error.message || '库存数据处理失败'
+        this.$message.error(this.uploadTaskStatusText)
+      } finally {
+        this.uploadTaskPolling = false
+      }
+    },
+    sleep(ms) {
+      return new Promise(resolve => setTimeout(resolve, ms))
+    },
     getTurnoverRateType(rate) {
       if (rate === 0) return 'info'
       if (rate >= 1) return 'success'
@@ -628,6 +700,7 @@ export default {
   line-height: 1.6;
 }
 
+.upload-task-status,
 .upload-summary {
   margin-top: 10px;
   display: flex;