Ver código fonte

Merge branch 'master' of https://gogs.dev.dazesoft.cn/dtm_organization/dtm_vue

Zhu Jiaqi 2 meses atrás
pai
commit
9ee6ec51b1
3 arquivos alterados com 102 adições e 20 exclusões
  1. 4 0
      src/views/login.vue
  2. 4 0
      src/views/register.vue
  3. 94 20
      src/views/storage/overview/index.vue

+ 4 - 0
src/views/login.vue

@@ -118,6 +118,10 @@ export default {
           this.codeUrl = "data:image/gif;base64," + res.img
           this.loginForm.uuid = res.uuid
         }
+      }).catch(() => {
+        this.captchaEnabled = false
+        this.codeUrl = ""
+        this.loginForm.uuid = ""
       })
     },
     getCookie() {

+ 4 - 0
src/views/register.vue

@@ -122,6 +122,10 @@ export default {
           this.codeUrl = "data:image/gif;base64," + res.img
           this.registerForm.uuid = res.uuid
         }
+      }).catch(() => {
+        this.captchaEnabled = false
+        this.codeUrl = ""
+        this.registerForm.uuid = ""
       })
     },
     handleRegister() {

+ 94 - 20
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() {
@@ -336,7 +362,7 @@ export default {
   },
   methods: {
     inventoryRequest(config) {
-      return request({ timeout: 30000, ...config })
+      return request({ timeout: 120000, ...config })
     },
     normalizeResponse(res) {
       if (!res) return null
@@ -444,9 +470,9 @@ export default {
     },
     async refreshData() {
       await this.fetchOverviewData()
-      this.fetchMonthlyComparison()
-      this.fetchSkuSummary()
-      this.fetchSpuSummary()
+      await this.fetchMonthlyComparison()
+      await this.fetchSkuSummary()
+      await this.fetchSpuSummary()
     },
     openUploadDialog() {
       this.uploadDialogVisible = true
@@ -510,30 +536,77 @@ 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',
           method: 'post',
           data: formData,
-          headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false }
+          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'
@@ -627,6 +700,7 @@ export default {
   line-height: 1.6;
 }
 
+.upload-task-status,
 .upload-summary {
   margin-top: 10px;
   display: flex;