ソースを参照

修改录像之后异步上传

yangg 2 ヶ月 前
コミット
e86bacd23b

+ 411 - 181
pages/identity-verify/identity-verify.vue

@@ -122,6 +122,14 @@
     <!-- <div class="recording-timer" v-if="isRecording">
       <span class="timer-text">{{ recordingTimeDisplay || '00:00' }}</span>
     </div> -->
+
+    <!-- 添加上传状态指示器 -->
+    <div class="upload-status-indicator" v-if="showUploadStatus && uploadStatusText">
+      <div class="upload-status-content">
+        <div class="upload-status-icon" :class="{'uploading': isUploading}"></div>
+        <span class="upload-status-text">{{ uploadStatusText }}</span>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -220,6 +228,14 @@ export default {
       recordingStartTime: null, // 录制开始时间
       recordingTimerCount: 0, // 录制计时器计数
       recordingTimeDisplay: '00:00', // 格式化的录制时间显示
+      
+      // 添加上传队列相关数据
+      uploadQueue: [], // 存储待上传的视频
+      isUploading: false, // 标记是否正在上传
+      uploadProgress: {}, // 存储每个视频的上传进度
+      uploadStatus: {}, // 存储每个视频的上传状态
+      showUploadStatus: false, // 是否显示上传状态指示器
+      uploadStatusText: '', // 上传状态文本
     }
   },
   mounted() {
@@ -1428,17 +1444,6 @@ export default {
     uploadRecordedVideo(fileOrPath) {
       console.log('准备上传视频:', typeof fileOrPath === 'string' ? fileOrPath : fileOrPath.name);
       
-      // 显示上传中提示
-      uni.showLoading({
-        title: '正在上传...',
-        mask: true
-      });
-      
-      // 获取用户信息
-      const userInfo = uni.getStorageSync('userInfo');
-      const openid = userInfo ? (JSON.parse(userInfo).openid || '') : '';
-      const tenant_id = uni.getStorageSync('tenant_id') || '1';
-      
       // 根据当前视频索引映射到正确的问题ID
       let questionId;
       switch(this.currentVideoIndex) {
@@ -1458,93 +1463,168 @@ export default {
           questionId = 10; // 默认值
       }
       
-      // 检查是否在浏览器环境中使用File对象
-      if (typeof fileOrPath !== 'string') {
-        // 在浏览器环境中,我们需要将File对象转换为FormData
-        const formData = new FormData();
-        formData.append('file', fileOrPath);
-        formData.append('openid', openid);
-        formData.append('tenant_id', tenant_id);
-        formData.append('application_id', uni.getStorageSync('appId'));
-        formData.append('question_id', questionId);
-        formData.append('video_duration', 0);
-        formData.append('has_audio', 'true'); // 明确标记视频包含音频
-        
-        // 使用XMLHttpRequest直接上传
-        const xhr = new XMLHttpRequest();
-        xhr.open('POST', `${apiBaseUrl}/api/system/upload/`, true);
-        xhr.timeout = 120000; // 30秒超时
-        xhr.onload = () => {
-          uni.hideLoading();
-          if (xhr.status === 200) {
-            try {
-              const res = JSON.parse(xhr.responseText);
-              console.log('上传响应:', res);
-              if (res.code === 2000) {
-                // 获取上传后的视频URL
-                const videoUrl = res.data.url || res.data.photoUrl || '';
-                if (videoUrl) {
-                  // 提交到面试接口
-                  this.submitVideoToInterview(videoUrl);
-                } else {
-                  uni.showToast({
-                    title: '视频URL获取失败',
-                    icon: 'none'
-                  });
-                }
+      // 创建上传任务对象
+      const uploadTask = {
+        id: Date.now().toString(), // 生成唯一ID
+        file: fileOrPath,
+        questionId: questionId,
+        attempts: 0, // 上传尝试次数
+        maxAttempts: 3, // 最大尝试次数
+      };
+      
+      // 添加到上传队列
+      this.uploadQueue.push(uploadTask);
+      
+      // 初始化上传进度和状态
+      this.uploadProgress[uploadTask.id] = 0;
+      this.uploadStatus[uploadTask.id] = 'pending'; // pending, uploading, success, failed
+      
+      // 显示简短的上传状态提示
+      uni.showToast({
+        title: '视频已加入上传队列',
+        icon: 'none',
+        duration: 1500
+      });
+      
+      // 更新上传状态文本
+      this.updateUploadStatusText();
+      
+      // 如果当前没有上传任务在进行,开始处理队列
+      if (!this.isUploading) {
+        this.processUploadQueue();
+      }
+      
+      // 立即进入下一个问题,不等待上传完成
+      this.proceedToNextQuestion();
+    },
+    
+    // 添加新方法:处理上传队列
+    processUploadQueue() {
+      // 如果队列为空,结束处理
+      if (this.uploadQueue.length === 0) {
+        this.isUploading = false;
+        this.showUploadStatus = false;
+        return;
+      }
+      
+      // 标记为正在上传
+      this.isUploading = true;
+      this.showUploadStatus = true;
+      
+      // 获取队列中的第一个任务
+      const task = this.uploadQueue[0];
+      
+      // 更新任务状态
+      this.uploadStatus[task.id] = 'uploading';
+      this.updateUploadStatusText();
+      
+      // 增加尝试次数
+      task.attempts++;
+      
+      // 根据文件类型选择上传方法
+      if (typeof task.file !== 'string') {
+        // 浏览器环境,使用XMLHttpRequest上传
+        this.uploadFileWithXHR(task);
+      } else {
+        // 小程序环境,使用uni.uploadFile上传
+        this.uploadFileWithUni(task);
+      }
+    },
+    
+    // 添加新方法:使用XMLHttpRequest上传文件
+    uploadFileWithXHR(task) {
+      // 获取用户信息
+      const userInfo = uni.getStorageSync('userInfo');
+      const openid = userInfo ? (JSON.parse(userInfo).openid || '') : '';
+      const tenant_id = uni.getStorageSync('tenant_id') || '1';
+      
+      // 创建FormData
+      const formData = new FormData();
+      formData.append('file', task.file);
+      formData.append('openid', openid);
+      formData.append('tenant_id', tenant_id);
+      formData.append('application_id', uni.getStorageSync('appId'));
+      formData.append('question_id', task.questionId);
+      formData.append('video_duration', 0);
+      formData.append('has_audio', 'true');
+      
+      // 创建XMLHttpRequest
+      const xhr = new XMLHttpRequest();
+      xhr.open('POST', `${apiBaseUrl}/api/upload/`, true);
+      xhr.timeout = 120000; // 2分钟超时
+      
+      // 监听上传进度
+      xhr.upload.onprogress = (event) => {
+        if (event.lengthComputable) {
+          const percentComplete = Math.round((event.loaded / event.total) * 100);
+          this.uploadProgress[task.id] = percentComplete;
+          this.updateUploadStatusText();
+        }
+      };
+      
+      // 监听请求完成
+      xhr.onload = () => {
+        if (xhr.status === 200) {
+          try {
+            const res = JSON.parse(xhr.responseText);
+            console.log('上传响应:', res);
+            if (res.code === 2000) {
+              // 获取上传后的视频URL
+              const videoUrl = res.data.url || res.data.photoUrl || '';
+              if (videoUrl) {
+                // 上传成功,更新状态
+                this.uploadStatus[task.id] = 'success';
+                this.updateUploadStatusText();
+                
+                // 提交到面试接口
+                this.submitVideoToInterview(videoUrl, task);
               } else {
-                uni.showToast({
-                  title: res.msg || '上传失败,请重试',
-                  icon: 'none'
-                });
+                this.handleUploadFailure(task, '视频URL获取失败');
               }
-            } catch (e) {
-              console.error('解析响应失败:', e);
-              uni.showToast({
-                title: '解析响应失败',
-                icon: 'none'
-              });
+            } else {
+              this.handleUploadFailure(task, res.msg || '上传失败');
             }
-          } else {
-            uni.showToast({
-              title: '上传失败,HTTP状态: ' + xhr.status,
-              icon: 'none'
-            });
-          }
-        };
-        
-        xhr.onerror = () => {
-          uni.hideLoading();
-          console.error('上传网络错误');
-          uni.showToast({
-            title: '网络错误,请重试',
-            icon: 'none'
-          });
-        };
-        
-        xhr.upload.onprogress = (event) => {
-          if (event.lengthComputable) {
-            const percentComplete = Math.round((event.loaded / event.total) * 100);
-            console.log('上传进度:', percentComplete + '%');
+          } catch (e) {
+            this.handleUploadFailure(task, '解析响应失败');
           }
-        };
-        
-        xhr.send(formData);
-        return;
-      }
+        } else {
+          this.handleUploadFailure(task, 'HTTP状态: ' + xhr.status);
+        }
+      };
+      
+      // 监听错误
+      xhr.onerror = () => {
+        this.handleUploadFailure(task, '网络错误');
+      };
       
-      // 对于小程序环境,使用uni.uploadFile
-      uni.uploadFile({
-        url: `${apiBaseUrl}/api/system/upload/`,
-        filePath: fileOrPath,
+      // 监听超时
+      xhr.ontimeout = () => {
+        this.handleUploadFailure(task, '上传超时');
+      };
+      
+      // 发送请求
+      xhr.send(formData);
+    },
+    
+    // 添加新方法:使用uni.uploadFile上传文件
+    uploadFileWithUni(task) {
+      // 获取用户信息
+      const userInfo = uni.getStorageSync('userInfo');
+      const openid = userInfo ? (JSON.parse(userInfo).openid || '') : '';
+      const tenant_id = uni.getStorageSync('tenant_id') || '1';
+      
+      // 创建上传任务
+      const uploadTask = uni.uploadFile({
+        url: `${apiBaseUrl}/api/upload/`,
+        filePath: task.file,
         name: 'file',
         formData: {
           openid: openid,
           tenant_id: tenant_id,
           application_id: uni.getStorageSync('appId'),
-          question_id: questionId,
+          question_id: task.questionId,
           video_duration: 0,
-          has_audio: 'true'  // 明确标记视频包含音频
+          has_audio: 'true'
         },
         success: (uploadRes) => {
           try {
@@ -1554,71 +1634,106 @@ export default {
               // 获取上传后的视频URL
               const videoUrl = res.data.permanent_link || res.data.url || '';
               if (videoUrl) {
+                // 上传成功,更新状态
+                this.uploadStatus[task.id] = 'success';
+                this.updateUploadStatusText();
+                
                 // 提交到面试接口
-                this.submitVideoToInterview(videoUrl);
+                this.submitVideoToInterview(videoUrl, task);
               } else {
-                uni.showToast({
-                  title: '视频URL获取失败',
-                  icon: 'none'
-                });
+                this.handleUploadFailure(task, '视频URL获取失败');
               }
             } else {
-              uni.showToast({
-                title: res.msg || '上传失败,请重试',
-                icon: 'none'
-              });
+              this.handleUploadFailure(task, res.msg || '上传失败');
             }
           } catch (e) {
-            console.error('解析响应失败:', e);
-            uni.showToast({
-              title: '解析响应失败',
-              icon: 'none'
-            });
+            this.handleUploadFailure(task, '解析响应失败');
           }
         },
         fail: (err) => {
-          console.error('上传失败:', err);
-          uni.showToast({
-            title: '上传失败,请重试',
-            icon: 'none'
-          });
-        },
-        complete: () => {
-          uni.hideLoading();
+          this.handleUploadFailure(task, err.errMsg || '上传失败');
         }
       });
+      
+      // 监听上传进度
+      uploadTask.onProgressUpdate((res) => {
+        this.uploadProgress[task.id] = res.progress;
+        this.updateUploadStatusText();
+      });
     },
-
+    
+    // 添加新方法:处理上传失败
+    handleUploadFailure(task, errorMsg) {
+      console.error('上传失败:', errorMsg);
+      
+      // 更新任务状态
+      this.uploadStatus[task.id] = 'failed';
+      this.updateUploadStatusText();
+      
+      // 检查是否还有重试机会
+      if (task.attempts < task.maxAttempts) {
+        console.log(`将在5秒后重试上传,当前尝试次数: ${task.attempts}/${task.maxAttempts}`);
+        
+        // 5秒后重试
+        setTimeout(() => {
+          // 重置进度
+          this.uploadProgress[task.id] = 0;
+          
+          // 重新开始上传
+          if (typeof task.file !== 'string') {
+            this.uploadFileWithXHR(task);
+          } else {
+            this.uploadFileWithUni(task);
+          }
+        }, 5000);
+      } else {
+        // 超过最大重试次数,移除任务并继续处理队列
+        console.log('超过最大重试次数,放弃上传');
+        
+        // 显示错误提示
+        uni.showToast({
+          title: '视频上传失败,请稍后重试',
+          icon: 'none',
+          duration: 2000
+        });
+        
+        // 从队列中移除当前任务
+        this.uploadQueue.shift();
+        
+        // 继续处理队列中的下一个任务
+        this.processUploadQueue();
+      }
+    },
+    
     // 修改 submitVideoToInterview 方法
-    submitVideoToInterview(videoUrl) {
+    submitVideoToInterview(videoUrl, task = null) {
       console.log('提交视频URL到面试接口:', videoUrl);
       
-      // 显示加载提示
-      uni.showLoading({
-        title: '正在提交...',
-        mask: true
-      });
-      
-      // 根据当前视频索引映射到正确的问题ID
+      // 确定问题ID
       let questionId;
-      switch(this.currentVideoIndex) {
-        case 1: // 第一个问题(索引1是第二个视频,第一个是介绍视频)
-          questionId = 10;
-          break;
-        case 2: // 第二个问题
-          questionId = 11;
-          break;
-        case 3: // 第三个问题
-          questionId = 12;
-          break;
-        case 4: // 第四个问题
-          questionId = 13;
-          break;
-        default:
-          questionId = 10; // 默认值,以防万一
+      if (task) {
+        questionId = task.questionId;
+      } else {
+        // 根据当前视频索引映射到正确的问题ID(兼容旧代码)
+        switch(this.currentVideoIndex) {
+          case 1:
+            questionId = 10;
+            break;
+          case 2:
+            questionId = 11;
+            break;
+          case 3:
+            questionId = 12;
+            break;
+          case 4:
+            questionId = 13;
+            break;
+          default:
+            questionId = 10;
+        }
       }
       
-      // 准备请求参数,使用映射后的问题ID
+      // 准备请求参数
       const requestData = {
         application_id: uni.getStorageSync('appId'),
         question_id: questionId,
@@ -1626,49 +1741,75 @@ export default {
         tenant_id: uni.getStorageSync('tenant_id') || '1'
       };
       
-      // 发送请求到面试接口,使用form-data格式
+      // 发送请求到面试接口
       uni.request({
-        url: `${apiBaseUrl}/api/job/upload_question_video`,
+        url: `${apiBaseUrl}/api/job/upload_video`,
         method: 'POST',
-        data: requestData,  // 使用data而不是formData
+        data: requestData,
         header: {
-          'content-type': 'application/x-www-form-urlencoded'  // 修改为form-data格式的content-type
+          'content-type': 'application/x-www-form-urlencoded'
         },
         success: (res) => {
           console.log('面试接口提交成功:', res);
-          uni.hideLoading();
           
           if (res.data.code === 0 || res.data.code === 2000) {
             // 提交成功
-            uni.showToast({
-              title: '回答已提交',
-              icon: 'success'
-            });
-            
-            // 保存最后上传的视频URL
-            this.lastUploadedVideoUrl = videoUrl;
-            
-            // 隐藏重试按钮(如果之前显示了)
-            this.showRetryButton = false;
-            this.lastVideoToRetry = null;
-            
-            // 延迟一下再进入下一题,让用户看到成功提示
-            setTimeout(() => {
-              // 检查是否完成了第五个视频问题(索引为4)
-              if (this.currentVideoIndex === 4) {
-                // 完成第五个问题后,跳转到相机页面
-                uni.navigateTo({
-                  url: '/pages/camera/camera'
-                });
-              } else {
-                // 否则继续正常流程
-                this.proceedToNextQuestion();
-              }
-            }, 1500);
+            if (task) {
+              // 从队列中移除当前任务
+              this.uploadQueue.shift();
+              
+              // 显示简短的成功提示
+              uni.showToast({
+                title: '视频提交成功',
+                icon: 'success',
+                duration: 1500
+              });
+              
+              // 继续处理队列中的下一个任务
+              this.processUploadQueue();
+            } else {
+              // 兼容旧代码的处理逻辑
+              uni.showToast({
+                title: '回答已提交',
+                icon: 'success'
+              });
+              
+              // 保存最后上传的视频URL
+              this.lastUploadedVideoUrl = videoUrl;
+              
+              // 隐藏重试按钮(如果之前显示了)
+              this.showRetryButton = false;
+              this.lastVideoToRetry = null;
+            }
           } else {
             // 提交失败
+            if (task) {
+              this.handleSubmitFailure(task, res.data.msg || '提交失败');
+            } else {
+              // 兼容旧代码的处理逻辑
+              uni.showToast({
+                title: res.data.msg || '提交失败,请重试',
+                icon: 'none'
+              });
+              
+              // 保存失败的视频URL,用于重试
+              this.lastVideoToRetry = videoUrl;
+              
+              // 显示重试按钮
+              this.showRetryButton = true;
+            }
+          }
+        },
+        fail: (err) => {
+          console.error('面试接口提交失败:', err);
+          
+          if (task) {
+            this.handleSubmitFailure(task, err.errMsg || '网络错误');
+          } else {
+            // 兼容旧代码的处理逻辑
+            uni.hideLoading();
             uni.showToast({
-              title: res.data.msg || '提交失败,请重试',
+              title: '网络错误,请重试',
               icon: 'none'
             });
             
@@ -1678,25 +1819,82 @@ export default {
             // 显示重试按钮
             this.showRetryButton = true;
           }
-        },
-        fail: (err) => {
-          console.error('面试接口提交失败:', err);
-          uni.hideLoading();
-          uni.showToast({
-            title: '网络错误,请重试',
-            icon: 'none'
-          });
-          
-          // 保存失败的视频URL,用于重试
-          this.lastVideoToRetry = videoUrl;
-          
-          // 显示重试按钮
-          this.showRetryButton = true;
         }
       });
     },
-
-    // 修改 proceedToNextQuestion 方法
+    
+    // 添加新方法:处理提交失败
+    handleSubmitFailure(task, errorMsg) {
+      console.error('提交失败:', errorMsg);
+      
+      // 更新任务状态
+      this.uploadStatus[task.id] = 'failed';
+      this.updateUploadStatusText();
+      
+      // 检查是否还有重试机会
+      if (task.attempts < task.maxAttempts) {
+        console.log(`将在5秒后重试提交,当前尝试次数: ${task.attempts}/${task.maxAttempts}`);
+        
+        // 5秒后重试
+        setTimeout(() => {
+          // 重新提交
+          this.submitVideoToInterview(task.videoUrl, task);
+        }, 5000);
+      } else {
+        // 超过最大重试次数,移除任务并继续处理队列
+        console.log('超过最大重试次数,放弃提交');
+        
+        // 显示错误提示
+        uni.showToast({
+          title: '视频提交失败,请稍后重试',
+          icon: 'none',
+          duration: 2000
+        });
+        
+        // 从队列中移除当前任务
+        this.uploadQueue.shift();
+        
+        // 继续处理队列中的下一个任务
+        this.processUploadQueue();
+      }
+    },
+    
+    // 添加新方法:更新上传状态文本
+    updateUploadStatusText() {
+      if (this.uploadQueue.length === 0) {
+        this.uploadStatusText = '';
+        return;
+      }
+      
+      const currentTask = this.uploadQueue[0];
+      const progress = this.uploadProgress[currentTask.id] || 0;
+      const status = this.uploadStatus[currentTask.id] || 'pending';
+      
+      let statusText = '';
+      switch (status) {
+        case 'pending':
+          statusText = '等待上传';
+          break;
+        case 'uploading':
+          statusText = `上传中 ${progress}%`;
+          break;
+        case 'success':
+          statusText = '上传成功,提交中...';
+          break;
+        case 'failed':
+          statusText = `上传失败,${currentTask.attempts < currentTask.maxAttempts ? '即将重试' : '已放弃'}`;
+          break;
+      }
+      
+      this.uploadStatusText = `问题${currentTask.questionId - 9}:${statusText}`;
+      
+      // 如果队列中有多个任务,显示总数
+      if (this.uploadQueue.length > 1) {
+        this.uploadStatusText += ` (${this.uploadQueue.length}个视频待处理)`;
+      }
+    },
+    
+    // 修改 proceedToNextQuestion 方法,不再等待上传完成
     proceedToNextQuestion() {
       // 检查是否完成了第五个视频问题(索引为4)
       if (this.currentVideoIndex === 4) {
@@ -2561,4 +2759,36 @@ video::-webkit-media-controls-fullscreen-button {
   font-size: 14px;
   font-family: monospace; /* 使用等宽字体,使时间显示更稳定 */
 }
+
+/* 添加上传状态指示器 */
+.upload-status-indicator {
+  position: absolute;
+  top: 20px;
+  left: 130px; /* 放在录制指示器旁边 */
+  background-color: rgba(0, 0, 0, 0.6);
+  padding: 5px 10px;
+  border-radius: 15px;
+  z-index: 20;
+}
+
+.upload-status-content {
+  display: flex;
+  align-items: center;
+}
+
+.upload-status-icon {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  margin-right: 8px;
+}
+
+.upload-status-text {
+  color: white;
+  font-size: 14px;
+}
+
+.uploading {
+  background-color: #e74c3c;
+}
 </style>

+ 2 - 2
unpackage/dist/dev/mp-weixin/api/user.js

@@ -46,10 +46,10 @@ const getJobList = (params = {}) => {
   return utils_request.http.get("/api/job/list", defaultParams);
 };
 const fillUserInfo = (params) => {
-  return utils_request.http.post("/api/system/wechat/save_user_info", params);
+  return utils_request.http.post("/api/wechat/save_user_info", params);
 };
 const getInterviewList = (params) => {
-  return utils_request.http.get("/system/job/questions", params);
+  return utils_request.http.get("/job/questions", params);
 };
 const applyJob = (params) => {
   return utils_request.http.post("/api/job/apply", params);

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/face-photo/face-photo.js

@@ -171,7 +171,7 @@ const _sfc_main = {
         return;
       }
       common_vendor.index.uploadFile({
-        url: `${common_config.apiBaseUrl}/api/system/upload/`,
+        url: `${common_config.apiBaseUrl}/api/upload/`,
         filePath: this.mediaSource,
         name: "file",
         formData: {

+ 267 - 148
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.js

@@ -111,8 +111,21 @@ const _sfc_main = {
       // 录制开始时间
       recordingTimerCount: 0,
       // 录制计时器计数
-      recordingTimeDisplay: "00:00"
+      recordingTimeDisplay: "00:00",
       // 格式化的录制时间显示
+      // 添加上传队列相关数据
+      uploadQueue: [],
+      // 存储待上传的视频
+      isUploading: false,
+      // 标记是否正在上传
+      uploadProgress: {},
+      // 存储每个视频的上传进度
+      uploadStatus: {},
+      // 存储每个视频的上传状态
+      showUploadStatus: false,
+      // 是否显示上传状态指示器
+      uploadStatusText: ""
+      // 上传状态文本
     };
   },
   mounted() {
@@ -999,13 +1012,6 @@ const _sfc_main = {
     // 修改上传录制的视频方法
     uploadRecordedVideo(fileOrPath) {
       console.log("准备上传视频:", typeof fileOrPath === "string" ? fileOrPath : fileOrPath.name);
-      common_vendor.index.showLoading({
-        title: "正在上传...",
-        mask: true
-      });
-      const userInfo = common_vendor.index.getStorageSync("userInfo");
-      const openid = userInfo ? JSON.parse(userInfo).openid || "" : "";
-      const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
       let questionId;
       switch (this.currentVideoIndex) {
         case 1:
@@ -1023,83 +1029,120 @@ const _sfc_main = {
         default:
           questionId = 10;
       }
-      if (typeof fileOrPath !== "string") {
-        const formData = new FormData();
-        formData.append("file", fileOrPath);
-        formData.append("openid", openid);
-        formData.append("tenant_id", tenant_id);
-        formData.append("application_id", common_vendor.index.getStorageSync("appId"));
-        formData.append("question_id", questionId);
-        formData.append("video_duration", 0);
-        formData.append("has_audio", "true");
-        const xhr = new XMLHttpRequest();
-        xhr.open("POST", `${common_config.apiBaseUrl}/api/system/upload/`, true);
-        xhr.timeout = 12e4;
-        xhr.onload = () => {
-          common_vendor.index.hideLoading();
-          if (xhr.status === 200) {
-            try {
-              const res = JSON.parse(xhr.responseText);
-              console.log("上传响应:", res);
-              if (res.code === 2e3) {
-                const videoUrl = res.data.url || res.data.photoUrl || "";
-                if (videoUrl) {
-                  this.submitVideoToInterview(videoUrl);
-                } else {
-                  common_vendor.index.showToast({
-                    title: "视频URL获取失败",
-                    icon: "none"
-                  });
-                }
+      const uploadTask = {
+        id: Date.now().toString(),
+        // 生成唯一ID
+        file: fileOrPath,
+        questionId,
+        attempts: 0,
+        // 上传尝试次数
+        maxAttempts: 3
+        // 最大尝试次数
+      };
+      this.uploadQueue.push(uploadTask);
+      this.uploadProgress[uploadTask.id] = 0;
+      this.uploadStatus[uploadTask.id] = "pending";
+      common_vendor.index.showToast({
+        title: "视频已加入上传队列",
+        icon: "none",
+        duration: 1500
+      });
+      this.updateUploadStatusText();
+      if (!this.isUploading) {
+        this.processUploadQueue();
+      }
+      this.proceedToNextQuestion();
+    },
+    // 添加新方法:处理上传队列
+    processUploadQueue() {
+      if (this.uploadQueue.length === 0) {
+        this.isUploading = false;
+        this.showUploadStatus = false;
+        return;
+      }
+      this.isUploading = true;
+      this.showUploadStatus = true;
+      const task = this.uploadQueue[0];
+      this.uploadStatus[task.id] = "uploading";
+      this.updateUploadStatusText();
+      task.attempts++;
+      if (typeof task.file !== "string") {
+        this.uploadFileWithXHR(task);
+      } else {
+        this.uploadFileWithUni(task);
+      }
+    },
+    // 添加新方法:使用XMLHttpRequest上传文件
+    uploadFileWithXHR(task) {
+      const userInfo = common_vendor.index.getStorageSync("userInfo");
+      const openid = userInfo ? JSON.parse(userInfo).openid || "" : "";
+      const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
+      const formData = new FormData();
+      formData.append("file", task.file);
+      formData.append("openid", openid);
+      formData.append("tenant_id", tenant_id);
+      formData.append("application_id", common_vendor.index.getStorageSync("appId"));
+      formData.append("question_id", task.questionId);
+      formData.append("video_duration", 0);
+      formData.append("has_audio", "true");
+      const xhr = new XMLHttpRequest();
+      xhr.open("POST", `${common_config.apiBaseUrl}/api/upload/`, true);
+      xhr.timeout = 12e4;
+      xhr.upload.onprogress = (event) => {
+        if (event.lengthComputable) {
+          const percentComplete = Math.round(event.loaded / event.total * 100);
+          this.uploadProgress[task.id] = percentComplete;
+          this.updateUploadStatusText();
+        }
+      };
+      xhr.onload = () => {
+        if (xhr.status === 200) {
+          try {
+            const res = JSON.parse(xhr.responseText);
+            console.log("上传响应:", res);
+            if (res.code === 2e3) {
+              const videoUrl = res.data.url || res.data.photoUrl || "";
+              if (videoUrl) {
+                this.uploadStatus[task.id] = "success";
+                this.updateUploadStatusText();
+                this.submitVideoToInterview(videoUrl, task);
               } else {
-                common_vendor.index.showToast({
-                  title: res.msg || "上传失败,请重试",
-                  icon: "none"
-                });
+                this.handleUploadFailure(task, "视频URL获取失败");
               }
-            } catch (e) {
-              console.error("解析响应失败:", e);
-              common_vendor.index.showToast({
-                title: "解析响应失败",
-                icon: "none"
-              });
+            } else {
+              this.handleUploadFailure(task, res.msg || "上传失败");
             }
-          } else {
-            common_vendor.index.showToast({
-              title: "上传失败,HTTP状态: " + xhr.status,
-              icon: "none"
-            });
-          }
-        };
-        xhr.onerror = () => {
-          common_vendor.index.hideLoading();
-          console.error("上传网络错误");
-          common_vendor.index.showToast({
-            title: "网络错误,请重试",
-            icon: "none"
-          });
-        };
-        xhr.upload.onprogress = (event) => {
-          if (event.lengthComputable) {
-            const percentComplete = Math.round(event.loaded / event.total * 100);
-            console.log("上传进度:", percentComplete + "%");
+          } catch (e) {
+            this.handleUploadFailure(task, "解析响应失败");
           }
-        };
-        xhr.send(formData);
-        return;
-      }
-      common_vendor.index.uploadFile({
-        url: `${common_config.apiBaseUrl}/api/system/upload/`,
-        filePath: fileOrPath,
+        } else {
+          this.handleUploadFailure(task, "HTTP状态: " + xhr.status);
+        }
+      };
+      xhr.onerror = () => {
+        this.handleUploadFailure(task, "网络错误");
+      };
+      xhr.ontimeout = () => {
+        this.handleUploadFailure(task, "上传超时");
+      };
+      xhr.send(formData);
+    },
+    // 添加新方法:使用uni.uploadFile上传文件
+    uploadFileWithUni(task) {
+      const userInfo = common_vendor.index.getStorageSync("userInfo");
+      const openid = userInfo ? JSON.parse(userInfo).openid || "" : "";
+      const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
+      const uploadTask = common_vendor.index.uploadFile({
+        url: `${common_config.apiBaseUrl}/api/upload/`,
+        filePath: task.file,
         name: "file",
         formData: {
           openid,
           tenant_id,
           application_id: common_vendor.index.getStorageSync("appId"),
-          question_id: questionId,
+          question_id: task.questionId,
           video_duration: 0,
           has_audio: "true"
-          // 明确标记视频包含音频
         },
         success: (uploadRes) => {
           try {
@@ -1108,62 +1151,77 @@ const _sfc_main = {
             if (res.code === 2e3) {
               const videoUrl = res.data.permanent_link || res.data.url || "";
               if (videoUrl) {
-                this.submitVideoToInterview(videoUrl);
+                this.uploadStatus[task.id] = "success";
+                this.updateUploadStatusText();
+                this.submitVideoToInterview(videoUrl, task);
               } else {
-                common_vendor.index.showToast({
-                  title: "视频URL获取失败",
-                  icon: "none"
-                });
+                this.handleUploadFailure(task, "视频URL获取失败");
               }
             } else {
-              common_vendor.index.showToast({
-                title: res.msg || "上传失败,请重试",
-                icon: "none"
-              });
+              this.handleUploadFailure(task, res.msg || "上传失败");
             }
           } catch (e) {
-            console.error("解析响应失败:", e);
-            common_vendor.index.showToast({
-              title: "解析响应失败",
-              icon: "none"
-            });
+            this.handleUploadFailure(task, "解析响应失败");
           }
         },
         fail: (err) => {
-          console.error("上传失败:", err);
-          common_vendor.index.showToast({
-            title: "上传失败,请重试",
-            icon: "none"
-          });
-        },
-        complete: () => {
-          common_vendor.index.hideLoading();
+          this.handleUploadFailure(task, err.errMsg || "上传失败");
         }
       });
+      uploadTask.onProgressUpdate((res) => {
+        this.uploadProgress[task.id] = res.progress;
+        this.updateUploadStatusText();
+      });
+    },
+    // 添加新方法:处理上传失败
+    handleUploadFailure(task, errorMsg) {
+      console.error("上传失败:", errorMsg);
+      this.uploadStatus[task.id] = "failed";
+      this.updateUploadStatusText();
+      if (task.attempts < task.maxAttempts) {
+        console.log(`将在5秒后重试上传,当前尝试次数: ${task.attempts}/${task.maxAttempts}`);
+        setTimeout(() => {
+          this.uploadProgress[task.id] = 0;
+          if (typeof task.file !== "string") {
+            this.uploadFileWithXHR(task);
+          } else {
+            this.uploadFileWithUni(task);
+          }
+        }, 5e3);
+      } else {
+        console.log("超过最大重试次数,放弃上传");
+        common_vendor.index.showToast({
+          title: "视频上传失败,请稍后重试",
+          icon: "none",
+          duration: 2e3
+        });
+        this.uploadQueue.shift();
+        this.processUploadQueue();
+      }
     },
     // 修改 submitVideoToInterview 方法
-    submitVideoToInterview(videoUrl) {
+    submitVideoToInterview(videoUrl, task = null) {
       console.log("提交视频URL到面试接口:", videoUrl);
-      common_vendor.index.showLoading({
-        title: "正在提交...",
-        mask: true
-      });
       let questionId;
-      switch (this.currentVideoIndex) {
-        case 1:
-          questionId = 10;
-          break;
-        case 2:
-          questionId = 11;
-          break;
-        case 3:
-          questionId = 12;
-          break;
-        case 4:
-          questionId = 13;
-          break;
-        default:
-          questionId = 10;
+      if (task) {
+        questionId = task.questionId;
+      } else {
+        switch (this.currentVideoIndex) {
+          case 1:
+            questionId = 10;
+            break;
+          case 2:
+            questionId = 11;
+            break;
+          case 3:
+            questionId = 12;
+            break;
+          case 4:
+            questionId = 13;
+            break;
+          default:
+            questionId = 10;
+        }
       }
       const requestData = {
         application_id: common_vendor.index.getStorageSync("appId"),
@@ -1172,56 +1230,112 @@ const _sfc_main = {
         tenant_id: common_vendor.index.getStorageSync("tenant_id") || "1"
       };
       common_vendor.index.request({
-        url: `${common_config.apiBaseUrl}/api/job/upload_question_video`,
+        url: `${common_config.apiBaseUrl}/api/job/upload_video`,
         method: "POST",
         data: requestData,
-        // 使用data而不是formData
         header: {
           "content-type": "application/x-www-form-urlencoded"
-          // 修改为form-data格式的content-type
         },
         success: (res) => {
           console.log("面试接口提交成功:", res);
-          common_vendor.index.hideLoading();
           if (res.data.code === 0 || res.data.code === 2e3) {
-            common_vendor.index.showToast({
-              title: "回答已提交",
-              icon: "success"
-            });
-            this.lastUploadedVideoUrl = videoUrl;
-            this.showRetryButton = false;
-            this.lastVideoToRetry = null;
-            setTimeout(() => {
-              if (this.currentVideoIndex === 4) {
-                common_vendor.index.navigateTo({
-                  url: "/pages/camera/camera"
-                });
-              } else {
-                this.proceedToNextQuestion();
-              }
-            }, 1500);
+            if (task) {
+              this.uploadQueue.shift();
+              common_vendor.index.showToast({
+                title: "视频提交成功",
+                icon: "success",
+                duration: 1500
+              });
+              this.processUploadQueue();
+            } else {
+              common_vendor.index.showToast({
+                title: "回答已提交",
+                icon: "success"
+              });
+              this.lastUploadedVideoUrl = videoUrl;
+              this.showRetryButton = false;
+              this.lastVideoToRetry = null;
+            }
           } else {
+            if (task) {
+              this.handleSubmitFailure(task, res.data.msg || "提交失败");
+            } else {
+              common_vendor.index.showToast({
+                title: res.data.msg || "提交失败,请重试",
+                icon: "none"
+              });
+              this.lastVideoToRetry = videoUrl;
+              this.showRetryButton = true;
+            }
+          }
+        },
+        fail: (err) => {
+          console.error("面试接口提交失败:", err);
+          if (task) {
+            this.handleSubmitFailure(task, err.errMsg || "网络错误");
+          } else {
+            common_vendor.index.hideLoading();
             common_vendor.index.showToast({
-              title: res.data.msg || "提交失败,请重试",
+              title: "网络错误,请重试",
               icon: "none"
             });
             this.lastVideoToRetry = videoUrl;
             this.showRetryButton = true;
           }
-        },
-        fail: (err) => {
-          console.error("面试接口提交失败:", err);
-          common_vendor.index.hideLoading();
-          common_vendor.index.showToast({
-            title: "网络错误,请重试",
-            icon: "none"
-          });
-          this.lastVideoToRetry = videoUrl;
-          this.showRetryButton = true;
         }
       });
     },
-    // 修改 proceedToNextQuestion 方法
+    // 添加新方法:处理提交失败
+    handleSubmitFailure(task, errorMsg) {
+      console.error("提交失败:", errorMsg);
+      this.uploadStatus[task.id] = "failed";
+      this.updateUploadStatusText();
+      if (task.attempts < task.maxAttempts) {
+        console.log(`将在5秒后重试提交,当前尝试次数: ${task.attempts}/${task.maxAttempts}`);
+        setTimeout(() => {
+          this.submitVideoToInterview(task.videoUrl, task);
+        }, 5e3);
+      } else {
+        console.log("超过最大重试次数,放弃提交");
+        common_vendor.index.showToast({
+          title: "视频提交失败,请稍后重试",
+          icon: "none",
+          duration: 2e3
+        });
+        this.uploadQueue.shift();
+        this.processUploadQueue();
+      }
+    },
+    // 添加新方法:更新上传状态文本
+    updateUploadStatusText() {
+      if (this.uploadQueue.length === 0) {
+        this.uploadStatusText = "";
+        return;
+      }
+      const currentTask = this.uploadQueue[0];
+      const progress = this.uploadProgress[currentTask.id] || 0;
+      const status = this.uploadStatus[currentTask.id] || "pending";
+      let statusText = "";
+      switch (status) {
+        case "pending":
+          statusText = "等待上传";
+          break;
+        case "uploading":
+          statusText = `上传中 ${progress}%`;
+          break;
+        case "success":
+          statusText = "上传成功,提交中...";
+          break;
+        case "failed":
+          statusText = `上传失败,${currentTask.attempts < currentTask.maxAttempts ? "即将重试" : "已放弃"}`;
+          break;
+      }
+      this.uploadStatusText = `问题${currentTask.questionId - 9}:${statusText}`;
+      if (this.uploadQueue.length > 1) {
+        this.uploadStatusText += ` (${this.uploadQueue.length}个视频待处理)`;
+      }
+    },
+    // 修改 proceedToNextQuestion 方法,不再等待上传完成
     proceedToNextQuestion() {
       if (this.currentVideoIndex === 4) {
         common_vendor.index.navigateTo({
@@ -1666,6 +1780,11 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     x: $data.showRetryButton
   }, $data.showRetryButton ? {
     y: common_vendor.o((...args) => $options.retryVideoUpload && $options.retryVideoUpload(...args))
+  } : {}, {
+    z: $data.showUploadStatus && $data.uploadStatusText
+  }, $data.showUploadStatus && $data.uploadStatusText ? {
+    A: $data.isUploading ? 1 : "",
+    B: common_vendor.t($data.uploadStatusText)
   } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-464e78c6"]]);

ファイルの差分が大きいため隠しています
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.wxml


+ 28 - 0
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.wxss

@@ -305,3 +305,31 @@ video.data-v-464e78c6::-webkit-media-controls-fullscreen-button {
   font-size: 14px;
   font-family: monospace; /* 使用等宽字体,使时间显示更稳定 */
 }
+
+/* 添加上传状态指示器 */
+.upload-status-indicator.data-v-464e78c6 {
+  position: absolute;
+  top: 20px;
+  left: 130px; /* 放在录制指示器旁边 */
+  background-color: rgba(0, 0, 0, 0.6);
+  padding: 5px 10px;
+  border-radius: 15px;
+  z-index: 20;
+}
+.upload-status-content.data-v-464e78c6 {
+  display: flex;
+  align-items: center;
+}
+.upload-status-icon.data-v-464e78c6 {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  margin-right: 8px;
+}
+.upload-status-text.data-v-464e78c6 {
+  color: white;
+  font-size: 14px;
+}
+.uploading.data-v-464e78c6 {
+  background-color: #e74c3c;
+}

+ 1 - 1
unpackage/dist/dev/mp-weixin/project.config.json

@@ -8,7 +8,7 @@
     "urlCheck": false,
     "es6": true,
     "postcss": false,
-    "minified": true,
+    "minified": false,
     "newFeature": true,
     "bigPackageSizeSupport": true,
     "babelSetting": {

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません