yangg před 2 měsíci
rodič
revize
a56dfea0a0

+ 7 - 1
api/user.js

@@ -116,7 +116,7 @@ export const fillUserInfo = (params) => {
 
 /* 获取面试列表 */
 export const getInterviewList = (params) => {
-  return http.get('/system/interview_question/list', params);
+  return http.get('/system/job/questions', params);
 };
 
 /* 获取面试详情 */
@@ -133,3 +133,9 @@ export const submitAnswer = (params) => {
 export const applyJob = (params) => {
   return http.post('/api/job/apply', params);
 };
+
+/* 文件上传 */
+export const uploadPhoto = (params) => {
+  return http.post('/api/system/upload/', params);
+};
+

+ 8 - 1
pages.json

@@ -21,7 +21,7 @@
 		{
 			"path": "pages/identity-verify/identity-verify",
 			"style": {
-				"navigationBarTitleText": "身份验证"
+				"navigationBarTitleText": ""
 			}
 		},
 		{
@@ -41,6 +41,13 @@
 			"style": {
 				"navigationBarTitleText": "我的"
 			}
+		},
+		{
+			"path" : "pages/interview/interview",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
 		}
 	],
 	"globalStyle": {

+ 27 - 22
pages/camera/camera.vue

@@ -218,11 +218,11 @@
 			async fetchInterviewList() {
 				try {
 					this.loading = true;
-					const res = await getInterviewList();
+					const res = await getInterviewList({job_id:JSON.parse(uni.getStorageSync('selectedJob')).id});
 					console.log(res);
 										// 使用第一个面试
-					this.interviewId = res.items//[0].id;
-					this.fetchInterviewData(res.items);
+					this.interviewId = res//[0].id;
+					this.fetchInterviewData(res);
 				} catch (error) {
 					console.error('获取面试列表失败:', error);
 					this.handleLoadError('获取面试列表失败');
@@ -399,30 +399,35 @@
 			},
 
 			nextQuestion(data) {
-				// 如果还没有显示结果,先检查答案
+				// 如果还没有显示结果,先检查答案并提交
 				if (!this.showResult) {
 					this.checkAnswer();
+					// 保存当前题目的答案并立即提交
+					this.saveAnswer(data);
+					this.submitCurrentAnswer(data).then(() => {
+						// 提交成功后显示结果
+						this.showResult = true;
+					}).catch(error => {
+						console.error('提交答案失败:', error);
+						uni.showToast({
+							title: '提交答案失败,请重试',
+							icon: 'none'
+						});
+						// 即使提交失败也显示结果
+						this.showResult = true;
+					});
 					return;
 				}
 				
-				// 保存当前题目的答案并提交
-				this.saveAnswer(data);
-				this.submitCurrentAnswer(data).then(() => {
-					// 如果是最后一题,显示结果页面
-					if (this.currentQuestionIndex >= this.questions.length - 1) {
-						this.showEndModal = true;
-						return;
-					}
-					
-					// 前往下一题
-					this.goToNextQuestion();
-				}).catch(error => {
-					console.error('提交答案失败:', error);
-					uni.showToast({
-						title: '提交答案失败,请重试',
-						icon: 'none'
-					});
-				});
+				// 如果已经显示结果,点击"下一题"按钮时执行
+				// 如果是最后一题,显示结果页面
+				if (this.currentQuestionIndex >= this.questions.length - 1) {
+					this.showEndModal = true;
+					return;
+				}
+				
+				// 前往下一题
+				this.goToNextQuestion();
 			},
 
 			// 保存当前题目的答案

+ 133 - 33
pages/face-photo/face-photo.vue

@@ -2,7 +2,7 @@
   <view class="photo-container" :class="{'loaded': isPageLoaded}">
     <!-- 标题 -->
     <view class="photo-header">
-      <text class="photo-title">拍摄照片</text>
+      <text class="photo-title">拍摄面部照片</text>
       <text class="photo-subtitle">我们将用于身份核验,请正对摄像头</text>
     </view>
     
@@ -37,12 +37,13 @@
     </view>
     <view v-else class="btn-group">
       <button class="retry-btn" @click="retakeMedia">重新{{mode === 'photo' ? '拍照' : '录制'}}</button>
-      <button class="start-btn" @click="startInterview">开始面试</button>
+      <button class="start-btn" @click="continueProcess">完成</button>
     </view>
   </view>
 </template>
 
 <script>
+import { uploadPhoto,fillUserInfo,getUserInfo } from '@/api/user';
 export default {
   data() {
     return {
@@ -54,6 +55,7 @@ export default {
       isRecording: false, // 是否正在录制视频
       recordingTime: 0, // 录制时间(秒)
       recordingTimer: null, // 录制计时器
+      photoUrl: '', // 存储上传后返回的图片URL
     }
   },
   onReady() {
@@ -167,6 +169,7 @@ export default {
       this.cameraContext.stopRecord({
         success: (res) => {
           this.mediaSource = res.tempVideoPath;
+          console.log(res);
           uni.hideLoading();
         },
         fail: (err) => {
@@ -193,57 +196,72 @@ export default {
       this.recordingTime = 0;
     },
     
-    startInterview() {
+    // 继续流程
+    continueProcess() {
       if (!this.mediaSource) {
         uni.showToast({
-          title: `请先完成${this.mode === 'photo' ? '拍照' : '视频录制'}`,
+          title: '请先完成拍照',
           icon: 'none'
         });
         return;
       }
       
       uni.showLoading({
-        title: '验证中...'
+        title: '上传中...'
       });
       
-      // 这里可以添加将照片或视频上传到服务器进行身份验证的代码
-      // 例如:this.uploadMedia();
+      // 上传当前照片
+      this.uploadMedia((url) => {
+        // 保存照片URL
+        this.photoUrl = url;
+        // 直接提交照片URL到指定接口
+        this.submitPhotoUrl();
+      });
+    },
+    
+    // 上传媒体文件方法
+    uploadMedia(callback) {
+      // 获取openid和tenant_id,可以从缓存或全局状态获取
+      const openid = JSON.parse(uni.getStorageSync('userInfo')).openid || '';
+      const tenant_id = 1 || '';
       
-      setTimeout(() => {
+      if (!openid || !tenant_id) {
         uni.hideLoading();
-        uni.navigateTo({
-          url: '/pages/camera/camera',
-          fail: (err) => {
-            console.error('页面跳转失败:', err);
-            uni.showToast({
-              title: '页面跳转失败',
-              icon: 'none'
-            });
-          }
+        uni.showToast({
+          title: '用户信息不完整,请重新登录',
+          icon: 'none'
         });
-      }, 1500);
-    }
-    
-    // 上传媒体文件方法(示例)
-    /* 
-    uploadMedia() {
+        return;
+      }
+      
+      // 使用uni.uploadFile方式上传图片
       uni.uploadFile({
-        url: 'https://your-api-endpoint.com/upload',
+        url: 'http://192.168.66.187:8083/api/system/upload/', 
         filePath: this.mediaSource,
-        name: this.mode === 'photo' ? 'photo' : 'video',
-        success: (res) => {
-          const data = JSON.parse(res.data);
-          if (data.success) {
-            // 验证成功,继续流程
+        name: 'file',
+        formData: {
+          openid: openid,
+          tenant_id: tenant_id
+        },
+        success: (uploadRes) => {
+          // 解析返回的JSON字符串
+          const res = JSON.parse(uploadRes.data);
+          if (res.code === 2000) { // 根据实际API响应调整
+            // 获取返回的图片URL
+            const photoUrl = res.data.url || res.data.photoUrl || '';
+            if (callback && typeof callback === 'function') {
+              callback(photoUrl);
+            }
           } else {
-            // 验证失败,提示用户
+            uni.hideLoading();
             uni.showToast({
-              title: data.message || '验证失败,请重试',
+              title: res.msg || '照片上传失败',
               icon: 'none'
             });
           }
         },
         fail: (err) => {
+          uni.hideLoading();
           console.error('上传失败:', err);
           uni.showToast({
             title: '网络错误,请重试',
@@ -251,8 +269,83 @@ export default {
           });
         }
       });
-    }
-    */
+    },
+    
+    // 提交照片URL到指定接口
+    submitPhotoUrl() {
+      const openid = JSON.parse(uni.getStorageSync('userInfo')).openid || '';
+      const tenant_id = 1 || '';
+      
+      if (!this.photoUrl) {
+        uni.hideLoading();
+        uni.showToast({
+          title: '照片信息不完整,请重试',
+          icon: 'none'
+        });
+        return;
+      }
+      
+      // 使用fillUserInfo方法进行上传
+      fillUserInfo({
+        application_id: JSON.parse(uni.getStorageSync('selectedJob')).id,
+        openid: openid,
+        tenant_id: tenant_id,
+        avatar: this.photoUrl,
+      }).then(res => {
+        uni.hideLoading();
+        console.log(res);
+        // this.updateLocalUserInfo();
+       
+          uni.showToast({
+            title: '照片上传成功',
+            icon: 'success'
+          });
+          // 上传成功后跳转到下一页
+          setTimeout(() => {
+            uni.navigateTo({
+              url: '/pages/identity-verify/identity-verify',
+              fail: (err) => {
+                console.error('页面跳转失败:', err);
+                uni.showToast({
+                  title: '页面跳转失败',
+                  icon: 'none'
+                });
+              }
+            });
+          }, 1500);
+      }).catch(err => {
+        uni.hideLoading();
+        console.error('提交失败:', err);
+        uni.showToast({
+          title: '网络错误,请重试',
+          icon: 'none'
+        });
+      });
+    },
+    updateLocalUserInfo() {
+				getUserInfo()
+					.then(res => {
+						if (res.code === 200 && res.data) {
+							let userInfo = {};
+							try {
+								userInfo = JSON.parse(uni.getStorageSync('userInfo') || '{}');
+							} catch (e) {
+								console.error('解析本地存储用户信息失败:', e);
+								userInfo = {};
+							}
+
+							const updatedUserInfo = {
+								...userInfo,
+								...res.data
+							};
+
+							uni.setStorageSync('userInfo', JSON.stringify(updatedUserInfo));
+						}
+					})
+					.catch(err => {
+						console.error('更新本地用户信息失败:', err);
+					});
+			}
   }
 }
 </script>
@@ -440,4 +533,11 @@ export default {
   border-radius: 45rpx;
   font-size: 32rpx;
 }
+
+/* 添加手部轮廓样式 */
+.face-outline.hand-outline {
+  width: 500rpx;
+  height: 300rpx;
+  border-radius: 30rpx;
+}
 </style> 

+ 956 - 131
pages/identity-verify/identity-verify.vue

@@ -1,191 +1,1016 @@
 <template>
-  <view class="verify-container">
-    <!-- 顶部提示 -->
-    <view class="verify-tip">
-      身份证号码仅用于验证您的身份信息,以确保面试的真实性和安全性
-    </view>
+  <div class="identity-verify-container">
+    <div class="digital-human-container">
+      <!-- AI数字人视频/图像显示区域 -->
+      <div class="digital-human-video">
+        <!-- <image v-if="!videoPlaying" src="/static/images/digital-human-placeholder.jpg" mode="aspectFit"></image> -->
+        <video 
+          :src="videoUrl"
+          id="myVideo"
+          ref="videoPlayer" 
+          autoplay 
+          playsinline
+          disablePictureInPicture
+          controlsList="nodownload nofullscreen noremoteplayback"
+          class="video-player"
+          :controls="false"
+          @error="handleVideoError"
+          @ended="handleVideoEnded"
+          @timeupdate="handleTimeUpdate">
+        </video>
+        
+        <!-- 添加字幕覆盖层 -->
+        <div class="subtitle-overlay" v-if="currentSubtitle">
+          {{ currentSubtitle }}
+        </div>
+        
+        <!-- 添加答题按钮 -->
+        <div class="answer-button-container" v-if="showAnswerButton">
+          <button class="answer-button" @click="handleAnswerButtonClick">
+            开始面试
+          </button>
+        </div>
+      </div>
+      
+      <!-- 用户摄像头视频显示区域 -->
+      <div class="user-camera-container">
+        <!-- 在小程序环境中使用camera组件 -->
+        <camera v-if="useMiniProgramCameraComponent" 
+                device-position="front" 
+                flash="off" 
+                class="user-camera-video"
+                @error="handleCameraError">
+        </camera>
+        <!-- 在H5/App环境中使用video元素 -->
+        <video v-else
+          id="userCamera"
+          ref="userCameraVideo"
+          autoplay
+          playsinline
+          muted
+          class="user-camera-video"
+          :controls="false">
+        </video>
+      </div>
+      
+      <!-- 字幕/文本覆盖区域 -->
+      <!-- <div class="subtitle-overlay" v-if="assistantResponse">
+        {{ assistantResponse }}
+      </div> -->
+    </div>
+
+    <!-- 加载状态 -->
+    <div v-if="loading" class="loading">加载中...</div>
     
-    <!-- 表单 -->
-    <view class="form-container">
-      <!-- 姓名 -->
-      <view class="form-item">
-        <text class="form-label">姓名<text class="required">*</text></text>
-        <input type="text" v-model="formData.name" placeholder="请输入姓名" class="form-input" />
-      </view>
-      
-      <!-- 身份证号 -->
-      <view class="form-item">
-        <text class="form-label">身份证号<text class="required">*</text></text>
-        <input type="text" v-model="formData.idCard" placeholder="请输入有效身份证号" class="form-input" maxlength="18" />
-      </view>
-      
-      <!-- 协议同意 -->
-      <view class="agreement">
-        <checkbox :checked="isAgreed" @tap="toggleAgreement" color="#6c5ce7" />
-        <text class="agreement-text">
-          我已阅读并同意
-          <text class="agreement-link">《身份验证服务协议》</text>
-          <text class="agreement-link">《隐私保护政策》</text>
-          <text class="agreement-link">《网络安全协议》</text>
-        </text>
-      </view>
-    </view>
+    <!-- 控制面板(可选,可以隐藏) -->
     
-    <!-- 提交按钮 -->
-    <button class="submit-btn" :disabled="!canSubmit" @click="submitForm">提交</button>
-  </view>
+    <!-- 响应数据(可以设为隐藏,仅用于调试) -->
+    <div v-if="showDebugInfo" class="response-container">
+      <div v-if="assistantResponse" class="response-item">
+        <div class="response-content">
+          <span>助手回复: {{ assistantResponse }}</span>
+        </div>
+      </div>
+      <div v-if="audioTranscript" class="response-item">
+        <div class="response-content">
+          <span>音频转写: {{ audioTranscript }}</span>
+        </div>
+      </div>
+      <div v-for="(item, index) in processedResponses" :key="index" class="response-item">
+        <div class="response-content">
+          <span v-if="item.role">角色: {{ item.role }}</span>
+          <span v-if="item.transcript">文本: {{ item.transcript }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script>
 export default {
+  name: 'IdentityVerify',
   data() {
     return {
-      formData: {
-        name: '',
-        idCard: ''
-      },
-      isAgreed: false
+      loading: false,
+      responses: [],
+      processedResponses: [],
+      assistantResponse: '',
+      audioTranscript: '',
+      videoPlaying: false,
+      showDebugInfo: false, // 设置为true可以显示调试信息
+      videoUrl: 'http://121.36.251.245:9000/minlong/0a0b3516-e0bb-4f6c-874c-8aaaca9d7f8f.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T135206Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=4a79bc77f80ae7344f339717dbe505a3b152140251a6b2c76c1fd5c047b39c74', // 用于存储AI数字人视频URL
+      showReplayButton: false,
+      cameraStream: null, // 存储摄像头流
+      cameraError: null, // 存储摄像头错误信息
+      useMiniProgramCameraComponent: false, // 添加小程序相机组件标志
+      cameraContext: null, // 添加相机上下文
+      currentSubtitle: '',
+      subtitles: [
+        {
+          startTime: 0, // 开始时间(秒)
+          endTime: 5,   // 结束时间(秒)
+          text: '你好,我是本次面试的面试官,欢迎参加本公司的线上面试!'
+        },
+        {
+          startTime: 5,
+          endTime: 13,
+          text: '面试预计需要15分钟,请你提前安排在网络良好、光线亮度合适、且相对安静的环境参加这次面试'
+        },
+        {
+          startTime: 13,
+          endTime: 20,
+          text: '以免影响本次面试的结果。如果你在面试过程中遇到问题,请与我们的招聘人员联系。'
+        }
+      ],
+      secondVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 10,
+          text: '请结合您的基本信息与过往履历进行简单的自我介绍,并讲一讲您有哪些优势胜任本岗位:'
+        }
+      ],
+      thirdVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 3,
+          text: '在工作中,你如何确保个人防护装备的正确使用?'
+        }
+      ],
+      fourthVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 3,
+          text: '描述一次你与团队合作改善生产流程的经历。'
+        }
+      ],
+      fifthVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 6,
+          text: '你在团队合作中曾遇到过哪些挑战?如何解决团队内部的分歧?'
+        }
+      ],
+      sixthVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 5,
+          text: '您已完成本次面试全部题目,请问您对于这个岗位还有什么想要了解的吗?'
+        }
+      ],
+      showAnswerButton: false, // 控制答题按钮显示
+      currentVideoIndex: 0, // 当前播放的视频索引
+      videoList: [
+        'http://121.36.251.245:9000/minlong/0a0b3516-e0bb-4f6c-874c-8aaaca9d7f8f.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T135206Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=4a79bc77f80ae7344f339717dbe505a3b152140251a6b2c76c1fd5c047b39c74', // 第一段视频
+        'http://121.36.251.245:9000/minlong/9ab3fd68-a2e9-47a7-a05e-a6e2253ef22c.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T143129Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=7a0ebf1058252b5f76895b31a5293d4781b33ede63d32dce148350846aa20621', // 第二段视频
+        'http://121.36.251.245:9000/minlong/69406ce9-8d8e-48aa-ba2f-3b12ea5b6a6c.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T144114Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=a43a06217a63f9bb6af975b1a7aa419b6f8e3d77d8c6c9c67d1070edfe60dc43', // 第三段视频
+        'http://121.36.251.245:9000/minlong/1cd448b2-16ea-4565-be25-2cf71d1bf7b2.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T144554Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=6ef5b0b160feab053e7d95952cdac62266d1a1eb48999efb17d7a3c4e0b495ed',
+        'http://121.36.251.245:9000/minlong/5a9ad6b2-0de8-48e3-8eb7-141a9bee4a9b.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T145623Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=8a2120a4c6b059d64f90620377fdda2536f9fff9c455d5140210ef35990758c6',
+        'http://121.36.251.245:9000/minlong/7aafb07e-ab0d-477e-9124-3263d0b7bf6f.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T145857Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=54ebe67d751c7e44a0f608b26175c9d076685a0e647e7e134cda22bbca2639eb'//结束
+      ],
     }
   },
-  computed: {
-    canSubmit() {
-      return this.formData.name.trim() && 
-             this.formData.idCard.trim() && 
-             this.isAgreed;
-    }
+  mounted() {
+    /* this.fetchData() */
+    this.playDigitalHumanVideo();
+    this.initCamera();
+  },
+  beforeDestroy() {
+    // 组件销毁前停止摄像头
+    this.stopUserCamera();
   },
   methods: {
-    toggleAgreement() {
-      this.isAgreed = !this.isAgreed;
+    // 初始化相机
+    initCamera() {
+      // 检查平台
+      const systemInfo = uni.getSystemInfoSync();
+      
+      // 判断是否在小程序环境中
+      const isMiniProgram = systemInfo.uniPlatform === 'mp-weixin' || 
+                            systemInfo.uniPlatform === 'mp-alipay' || 
+                            systemInfo.uniPlatform === 'mp-baidu' ||
+                            systemInfo.uniPlatform === 'mp-toutiao';
+      
+      // 设置标志,控制使用哪种相机组件
+      this.useMiniProgramCameraComponent = isMiniProgram;
+      
+      if (isMiniProgram) {
+        // 在小程序环境中使用camera组件
+        this.$nextTick(() => {
+          // 创建相机上下文
+          this.cameraContext = uni.createCameraContext();
+        });
+      } else {
+        // 只在非小程序环境(H5/App)中尝试使用getUserMedia
+        this.startUserCamera();
+      }
     },
-    submitForm() {
-      if (!this.canSubmit) {
-        let message = '';
-        if (!this.formData.name.trim()) {
-          message = '请输入姓名';
-        } else if (!this.formData.idCard.trim()) {
-          message = '请输入身份证号';
-        } else if (!this.isAgreed) {
-          message = '请阅读并同意相关协议';
+    
+    // 启动用户摄像头
+    async startUserCamera() {
+      try {
+        // 首先检查是否在小程序环境中
+        const systemInfo = uni.getSystemInfoSync();
+        const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith('mp-');
+        
+        if (isMiniProgram) {
+          // 小程序环境不应该调用这个方法,但如果调用了,直接返回
+          console.log('小程序环境不支持 getUserMedia API');
+          return;
         }
         
+        // 检查是否支持getUserMedia (仅在H5/App环境)
+        if (!navigator || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+          throw new Error('您的浏览器不支持摄像头访问');
+        }
+        
+        // 请求摄像头权限并获取媒体流
+        const stream = await navigator.mediaDevices.getUserMedia({
+          video: {
+            width: { ideal: 200 },
+            height: { ideal: 150 },
+            facingMode: 'user' // 使用前置摄像头
+          },
+          audio: true // 不需要音频
+        });
+        
+        this.cameraStream = stream;
+        
+        // 将摄像头流设置到视频元素
+        this.$nextTick(() => {
+          if (this.$refs.userCameraVideo) {
+            this.$refs.userCameraVideo.srcObject = stream;
+          }
+        });
+      } catch (error) {
+        console.error('无法访问摄像头:', error);
+        this.cameraError = error.message;
+        
+        // 根据错误类型显示不同的错误提示
+        let errorMessage = '无法访问摄像头,请检查权限设置';
+        
+        if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
+          errorMessage = '摄像头访问被拒绝,请在浏览器设置中允许摄像头访问';
+        } else if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
+          errorMessage = '未检测到摄像头设备';
+        } else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
+          errorMessage = '摄像头可能被其他应用占用';
+        } else if (error.name === 'OverconstrainedError') {
+          errorMessage = '摄像头不满足指定的要求';
+        } else if (error.name === 'TypeError' || error.message.includes('SSL')) {
+          errorMessage = '请确保在HTTPS环境下访问';
+        }
+        
+        // 显示错误提示
         uni.showToast({
-          title: message,
-          icon: 'none'
+          title: errorMessage,
+          icon: 'none',
+          duration: 3000
         });
-        return;
+        
+        // 添加摄像头错误处理
+        this.handleCameraError(errorMessage);
       }
+    },
+    
+    // 停止用户摄像头
+    stopUserCamera() {
+      if (this.cameraStream) {
+        // 停止所有轨道
+        this.cameraStream.getTracks().forEach(track => {
+          track.stop();
+        });
+        this.cameraStream = null;
+      }
+    },
+    
+    async fetchData() {
+      this.loading = true
+      this.assistantResponse = ''
+      this.audioTranscript = ''
+      this.processedResponses = []
+      try {
+        // 使用uni.request代替fetch
+        const requestTask = uni.request({
+          url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
+          method: 'POST',
+          header: {
+            'Content-Type': 'application/json',
+            'Authorization': 'Bearer sk-9e1ec73a7d97493b8613c63f06b6110c'
+          },
+          data: {
+            "model": "qwen-omni-turbo",
+            "messages":  [
+              {
+                "role": "user",
+                "content": [
+                  {
+                    "type": "input_audio",
+                    "input_audio": {
+                      "data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav",
+                      "format": "wav"
+                    }
+                  },
+                  {
+                    "type": "text",
+                    "text": "这段音频在说什么"
+                  }
+                ]
+              }
+            ],
+            "stream":true,
+            "stream_options":{
+                "include_usage":true
+            },
+            "modalities":["text","audio"],
+            "audio":{"voice":"Cherry","format":"wav"}
+          },
+          success: (res) => {
+            console.log('请求成功,响应数据:', res.data);
+            // 检查响应数据是否包含多个JSON对象
+            if (typeof res.data === 'string' && res.data.includes('data: {')) {
+              // 处理包含多个JSON对象的情况
+              const chunks = res.data.split('data: ').filter(chunk => chunk.trim() !== '');
+              chunks.forEach(chunk => {
+                this.handleStreamResponse(chunk);
+              });
+            } else {
+              // 处理单个响应对象的情况
+              this.handleStreamResponse(res.data);
+            }
+            
+            // 模拟获取到数字人视频并播放
+            this.playDigitalHumanVideo();
+          },
+          fail: (err) => {
+            console.error('请求失败:', err);
+          },
+          complete: () => {
+            this.loading = false;
+          }
+        });
+      } catch (error) {
+        console.error('获取数据失败:', error);
+        this.loading = false;
+      }
+    },
+    
+    handleStreamResponse(data) {
+      // 处理流式响应数据
+      if (typeof data === 'string') {
+        // 处理字符串格式的响应
+        if (data === '[DONE]') return;
+        
+        try {
+          // 移除可能存在的换行符和多余空格
+          const cleanData = data.trim();
+          // 检查是否是有效的JSON字符串
+          if (cleanData.startsWith('{') && cleanData.endsWith('}')) {
+            const jsonData = JSON.parse(cleanData);
+            this.processStreamChunk(jsonData);
+          }
+        } catch (e) {
+          console.error('解析JSON失败:', e, '原始数据:', data);
+        }
+      } else {
+        // 处理对象格式的响应
+        this.processStreamChunk(data);
+      }
+    },
+    
+    processStreamChunk(chunk) {
+      if (chunk.choices && chunk.choices.length > 0) {
+        const choice = chunk.choices[0];
+        
+        // 处理助手回复内容
+        if (choice.delta && choice.delta.content) {
+          this.assistantResponse += choice.delta.content;
+        }
+        
+        // 处理音频转写内容
+        if (choice.delta && choice.delta.audio && choice.delta.audio.transcript) {
+          this.audioTranscript += choice.delta.audio.transcript;
+        }
+        
+        // 处理角色和音频转写
+        if (choice.delta) {
+          const result = {};
+          
+          if (choice.delta.role) {
+            result.role = choice.delta.role;
+          }
+          
+          if (choice.delta.audio && choice.delta.audio.transcript) {
+            result.transcript = choice.delta.audio.transcript;
+          }
+          
+          if (Object.keys(result).length > 0) {
+            this.processedResponses.push(result);
+          }
+        }
+      }
+    },
+    
+    processResponseData() {
+      // 处理返回的数据
+      this.processedResponses = this.responses.map(item => {
+        const result = {}
+        
+        // 处理角色信息
+        if (item.delta && item.delta.role) {
+          result.role = item.delta.role
+        }
+        
+        // 处理音频转写文本
+        if (item.delta && item.delta.audio && item.delta.audio.transcript) {
+          result.transcript = item.delta.audio.transcript
+        }
+        
+        return result
+      }).filter(item => Object.keys(item).length > 0)
+    },
+    
+    // 播放数字人视频
+    playDigitalHumanVideo() {
+      // 设置第一个视频
+      this.videoUrl = this.videoList[this.currentVideoIndex];
+      this.videoPlaying = true;
+      
+      // 使用 uni.createVideoContext 来控制视频
+      this.$nextTick(() => {
+        const videoContext = uni.createVideoContext('myVideo', this);
+        if (videoContext) {
+          videoContext.play();
+          
+          // 设置超时检查,确认视频是否真的在播放
+          setTimeout(() => {
+            if (this.videoPlaying && this.$refs.videoPlayer) {
+              console.log('视频应该正在播放');
+            } else {
+              console.log('视频可能未成功播放,尝试替代方案');
+              this.tryAlternativeVideoPath();
+            }
+          }, 1000);
+        } else {
+          console.error('无法创建视频上下文');
+          this.tryAlternativeVideoPath();
+        }
+      });
+    },
+    
+    // 修改 tryAlternativeVideoPath 方法
+    tryAlternativeVideoPath() {
+      console.log('尝试使用替代路径');
+      
+      // 尝试不同的路径格式
+      const alternativePaths = [
+        './static/demo.mp4',
+        '../static/demo.mp4',
+        'static/demo.mp4',
+        '/static/demo.mp4',
+        // 添加绝对路径
+        `${window.location.origin}/static/demo.mp4`
+      ];
       
-      // 验证身份证号格式
-      const idCardReg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
-      if (!idCardReg.test(this.formData.idCard)) {
+      // 获取当前路径索引
+      const currentPathIndex = alternativePaths.indexOf(this.videoUrl);
+      const nextPathIndex = (currentPathIndex + 1) % alternativePaths.length;
+      
+      // 设置下一个路径
+      this.videoUrl = alternativePaths[nextPathIndex];
+      console.log('尝试新路径:', this.videoUrl);
+      
+      this.$nextTick(() => {
+        const videoContext = uni.createVideoContext('myVideo', this);
+        if (videoContext) {
+          videoContext.stop();
+          videoContext.play();
+          
+          // 检查是否成功播放
+          setTimeout(() => {
+            if (nextPathIndex === alternativePaths.length - 1 && !this.videoPlaying) {
+              console.log('所有路径均失败,尝试使用uni.getVideoInfo检查视频');
+              this.checkVideoWithAPI();
+            }
+          }, 1000);
+        }
+      });
+    },
+    
+    // 添加新方法:使用uni API检查视频
+    checkVideoWithAPI() {
+      // 尝试使用uni.getVideoInfo API检查视频是否可用
+      uni.getVideoInfo({
+        src: '/static/demo.mp4',
+        success: (res) => {
+          console.log('视频信息获取成功:', res);
+          // 如果能获取到视频信息,再次尝试播放
+          this.videoUrl = '/static/demo.mp4';
+          this.$nextTick(() => {
+            const videoContext = uni.createVideoContext('myVideo', this);
+            if (videoContext) {
+              videoContext.play();
+            }
+          });
+        },
+        fail: (err) => {
+          console.error('视频信息获取失败:', err);
+          // 最后尝试使用uni.chooseVideo API
+          this.fallbackToLocalVideo();
+        }
+      });
+    },
+    
+    // 添加新方法:回退到本地视频
+    fallbackToLocalVideo() {
+      console.log('尝试使用本地视频资源');
+      
+      // 检查平台
+      const platform = uni.getSystemInfoSync().platform;
+      if (platform === 'android' || platform === 'ios') {
+        // 移动端可以尝试使用本地资源
+        this.videoUrl = platform === 'android' ? 'android.resource://package_name/raw/demo' : 'file:///assets/demo.mp4';
+        this.$nextTick(() => {
+          const videoContext = uni.createVideoContext('myVideo', this);
+          if (videoContext) {
+            videoContext.play();
+          }
+        });
+      } else {
+        // 最终回退到静态图片
+        this.videoPlaying = false;
         uni.showToast({
-          title: '请输入正确的身份证号',
+          title: '视频加载失败,显示静态图片',
           icon: 'none'
         });
-        return;
+      }
+    },
+    
+    // 修改 handleVideoError 方法
+    handleVideoError(e) {
+      console.error('视频加载错误:', e);
+      
+      // 记录更详细的错误信息
+      if (e && e.detail) {
+        console.error('详细错误信息:', e.detail);
       }
       
-      // 提交表单
-      uni.showLoading({
-        title: '验证中...'
+      // 检查视频文件是否存在
+      uni.getFileInfo({
+        filePath: this.videoUrl.startsWith('/') ? this.videoUrl.substring(1) : this.videoUrl,
+        success: (res) => {
+          console.log('文件存在,大小:', res.size);
+          // 文件存在但播放失败,可能是格式问题
+          this.tryDifferentFormat();
+        },
+        fail: (err) => {
+          console.error('文件不存在或无法访问:', err);
+          // 尝试不同路径
+          this.tryAlternativeVideoPath();
+        }
       });
       
-      setTimeout(() => {
-        uni.hideLoading();
-        uni.navigateTo({
-          url: '/pages/face-photo/face-photo',
-          fail: (err) => {
-            console.error('页面跳转失败:', err);
-            uni.showToast({
-              title: '页面跳转失败',
-              icon: 'none'
-            });
+      // 如果多次尝试后仍然失败,显示错误信息
+      uni.showToast({
+        title: '视频加载失败,请检查文件是否存在',
+        icon: 'none',
+        duration: 2000
+      });
+    },
+
+    // 添加新方法:尝试不同格式
+    tryDifferentFormat() {
+      console.log('尝试不同的视频格式');
+      
+      // 尝试不同的视频格式
+      const formats = [
+        { ext: 'mp4', mime: 'video/mp4' },
+        { ext: 'webm', mime: 'video/webm' },
+        { ext: 'ogg', mime: 'video/ogg' },
+        { ext: 'mov', mime: 'video/quicktime' }
+      ];
+      
+      // 获取当前文件名(不含扩展名)
+      const currentPath = this.videoUrl;
+      const basePath = currentPath.substring(0, currentPath.lastIndexOf('.')) || '/static/demo';
+      
+      // 尝试下一个格式
+      let nextFormat = formats.find(f => !currentPath.endsWith(f.ext));
+      if (nextFormat) {
+        this.videoUrl = `${basePath}.${nextFormat.ext}`;
+        console.log('尝试新格式:', this.videoUrl);
+        
+        this.$nextTick(() => {
+          const videoContext = uni.createVideoContext('myVideo', this);
+          if (videoContext) {
+            videoContext.stop();
+            videoContext.play();
           }
         });
-      }, 1500);
-    }
+      } else {
+        // 所有格式都尝试过了,使用内置资源
+        this.useBuiltInResource();
+      }
+    },
+
+    // 添加新方法:使用内置资源
+    useBuiltInResource() {
+      console.log('尝试使用内置资源');
+      
+      // 检查平台
+      const platform = uni.getSystemInfoSync().platform;
+      
+      // 根据平台选择合适的视频源
+      if (platform === 'windows') {
+        // Windows平台特定处理
+        // 尝试使用相对于应用根目录的路径
+        const appRoot = process.env.UNI_INPUT_DIR || '';
+        this.videoUrl = `./static/demo.mp4`;
+        
+        // 或者尝试使用file://协议
+        // this.videoUrl = `file:///${appRoot.replace(/\\/g, '/')}/static/demo.mp4`;
+        
+        console.log('Windows平台尝试路径:', this.videoUrl);
+      } else if (platform === 'android' || platform === 'ios') {
+        // 移动端
+        this.useNativeVideo();
+      } else {
+        // Web平台
+        // 尝试使用完整URL
+        const baseUrl = window.location.origin;
+        this.videoUrl = `${baseUrl}/static/demo.mp4`;
+        console.log('Web平台尝试URL:', this.videoUrl);
+      }
+      
+      this.$nextTick(() => {
+        const videoContext = uni.createVideoContext('myVideo', this);
+        if (videoContext) {
+          videoContext.play();
+        }
+      });
+    },
+
+    // 添加新方法:使用原生视频能力
+    useNativeVideo() {
+      console.log('尝试使用原生视频能力');
+      
+      // 在移动端,可以尝试使用原生视频播放器
+      uni.chooseVideo({
+        sourceType: ['album'],
+        success: (res) => {
+          this.videoUrl = res.tempFilePath;
+          this.$nextTick(() => {
+            const videoContext = uni.createVideoContext('myVideo', this);
+            if (videoContext) {
+              videoContext.play();
+            }
+          });
+        },
+        fail: () => {
+          // 如果用户取消选择,回退到静态图片
+          this.videoPlaying = false;
+          uni.showToast({
+            title: '无法加载视频,显示静态图片',
+            icon: 'none'
+          });
+        }
+      });
+    },
+
+    // 处理视频结束事件
+    handleVideoEnded() {
+      console.log('视频播放结束');
+      this.videoPlaying = false;
+      
+      // 显示答题按钮
+      this.showAnswerButton = true;
+    },
+    
+    // 处理答题按钮点击
+    handleAnswerButtonClick() {
+      // 隐藏答题按钮
+      this.showAnswerButton = false;
+      
+      // 切换到下一个视频
+      this.currentVideoIndex++;
+      if (this.currentVideoIndex < this.videoList.length) {
+        // 还有下一段视频,播放它
+        this.videoUrl = this.videoList[this.currentVideoIndex];
+        this.videoPlaying = true;
+        
+        // 重置当前字幕
+        this.currentSubtitle = '';
+        
+        // 使用 nextTick 确保 DOM 更新后再播放视频
+        this.$nextTick(() => {
+          const videoContext = uni.createVideoContext('myVideo', this);
+          if (videoContext) {
+            videoContext.play();
+          }
+        });
+      } else {
+        // 所有视频都播放完毕,可以进行下一步操作
+        uni.showToast({
+          title: '面试完成',
+          icon: 'success',
+          duration: 2000
+        });
+        
+        // 可以在这里添加面试完成后的逻辑,比如跳转到下一个页面
+        setTimeout(() => {
+          uni.navigateTo({
+            url: '/pages/interview-result/interview-result'
+          });
+        }, 2000);
+      }
+    },
+
+    // 处理相机错误
+    handleCameraError(e) {
+      console.error('相机错误:', e);
+      
+      // 显示错误提示
+      uni.showToast({
+        title: '相机初始化失败,请检查权限设置',
+        icon: 'none'
+      });
+      
+      // 尝试备用选项
+      this.tryFallbackOptions();
+    },
+
+    // 添加新方法:尝试备用选项
+    tryFallbackOptions() {
+      // 检查环境
+      const systemInfo = uni.getSystemInfoSync();
+      
+      // 在小程序环境中使用小程序API
+      if (systemInfo.uniPlatform === 'mp-weixin' || systemInfo.uniPlatform === 'mp-alipay') {
+        this.useMiniProgramCamera();
+      } 
+      // 在H5环境中显示静态图像
+      else {
+        this.showStaticCameraPlaceholder();
+      }
+    },
+
+    // 添加新方法:使用小程序相机API
+    useMiniProgramCamera() {
+      console.log('尝试使用小程序相机组件');
+      // 这里需要在模板中添加小程序相机组件
+      // 并设置一个标志来控制显示
+      this.useMiniProgramCameraComponent = true;
+    },
+
+    // 添加新方法:显示静态图像
+    showStaticCameraPlaceholder() {
+      console.log('显示静态摄像头占位图');
+      // 创建一个图像元素
+      const img = document.createElement('img');
+      img.src = '/static/images/camera-placeholder.png'; // 确保有这个图片资源
+      img.className = 'static-camera-image';
+      img.style.width = '100%';
+      img.style.height = '100%';
+      img.style.objectFit = 'cover';
+      
+      // 获取容器并添加图像
+      const container = this.$refs.userCameraVideo.parentNode;
+      container.appendChild(img);
+    },
+
+    // 处理视频时间更新事件
+    handleTimeUpdate(e) {
+      // 获取当前视频播放时间
+      const currentTime = e.target.currentTime;
+      
+      // 根据当前播放的视频索引选择对应的字幕数组
+      let currentSubtitles;
+      if (this.currentVideoIndex === 0) {
+        currentSubtitles = this.subtitles;
+      } else if (this.currentVideoIndex === 1) {
+        currentSubtitles = this.secondVideoSubtitles;
+      }else if (this.currentVideoIndex === 2) {
+        currentSubtitles = this.thirdVideoSubtitles;
+      }else if (this.currentVideoIndex === 3) {
+        currentSubtitles = this.fourthVideoSubtitles;
+      }else if (this.currentVideoIndex === 4) {
+        currentSubtitles = this.fifthVideoSubtitles;
+      }else if (this.currentVideoIndex === 5) {
+        currentSubtitles = this.sixthVideoSubtitles;
+      }else {
+        // 如果有更多视频,可以继续添加条件
+        currentSubtitles = [];
+      }
+      
+      // 查找当前时间应该显示的字幕
+      const subtitle = currentSubtitles.find(
+        sub => currentTime >= sub.startTime && currentTime < sub.endTime
+      );
+      
+      // 更新当前字幕
+      this.currentSubtitle = subtitle ? subtitle.text : '';
+    },
   }
 }
 </script>
 
-<style>
-.verify-container {
+<style scoped>
+.identity-verify-container {
+  padding: 0;
+  max-width: 100%;
+  margin: 0 auto;
+  height: 100vh;
   display: flex;
   flex-direction: column;
-  min-height: 100vh;
-  background-color: #f5f7fa;
-  padding: 30rpx;
+  background-color: #f5f5f5;
 }
 
-.verify-tip {
-  font-size: 26rpx;
-  color: #666;
-  line-height: 1.5;
-  margin-bottom: 40rpx;
+.digital-human-container {
+  position: relative;
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+  background-color: #f0f0f0;
 }
 
-.form-container {
-  background-color: #fff;
-  border-radius: 12rpx;
-  padding: 20rpx;
-  margin-bottom: 40rpx;
+.digital-human-video {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
-.form-item {
-  margin-bottom: 30rpx;
+/* 用户摄像头容器样式 */
+.user-camera-container {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  width: 100px;
+  height: 150px;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+  z-index: 20;
+  border: 1px solid #fff;
 }
 
-.form-label {
-  display: block;
-  font-size: 28rpx;
-  color: #333;
-  margin-bottom: 15rpx;
+/* 用户摄像头视频样式 */
+.user-camera-video {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  background-color: #333;
+}
+
+.video-player {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  outline: none; /* 移除视频获得焦点时的轮廓 */
+  -webkit-tap-highlight-color: transparent; /* 移除移动设备上的点击高亮 */
+}
+
+/* 隐藏视频控制条 */
+video::-webkit-media-controls {
+  display: none !important;
+}
+
+video::-webkit-media-controls-enclosure {
+  display: none !important;
+}
+
+video::-webkit-media-controls-panel {
+  display: none !important;
+}
+
+video::-webkit-media-controls-play-button {
+  display: none !important;
+}
+
+video::-webkit-media-controls-timeline {
+  display: none !important;
+}
+
+video::-webkit-media-controls-current-time-display {
+  display: none !important;
+}
+
+video::-webkit-media-controls-time-remaining-display {
+  display: none !important;
+}
+
+video::-webkit-media-controls-mute-button {
+  display: none !important;
 }
 
-.required {
-  color: #e74c3c;
-  margin-left: 5rpx;
+video::-webkit-media-controls-volume-slider {
+  display: none !important;
 }
 
-.form-input {
+video::-webkit-media-controls-fullscreen-button {
+  display: none !important;
+}
+
+.subtitle-overlay {
+  position: absolute;
+  bottom: 50px;
+  left: 0;
   width: 100%;
-  height: 80rpx;
-  border-bottom: 1px solid #eee;
-  font-size: 28rpx;
-  box-sizing: border-box;
+  padding: 15px;
+  background-color: rgba(0, 0, 0, 0.7);
+  color: white;
+  text-align: center;
+  font-size: 16px;
+  line-height: 1.5;
+  z-index: 10;
+  border-radius: 4px;
+  max-width: 90%;
+  margin: 0 auto;
+  left: 5%;
+  right: 5%;
 }
 
-.agreement {
+.control-panel {
+  padding: 15px;
   display: flex;
-  align-items: flex-start;
-  margin-top: 20rpx;
+  justify-content: center;
 }
 
-.agreement-text {
-  font-size: 24rpx;
-  color: #666;
-  line-height: 1.5;
-  margin-left: 10rpx;
+.control-button {
+  padding: 10px 20px;
+  background-color: #4CAF50;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
 }
 
-.agreement-link {
-  color: #6c5ce7;
+.loading {
+  text-align: center;
+  margin: 20px 0;
+  font-size: 16px;
 }
 
-.submit-btn {
-  width: 100%;
-  height: 90rpx;
-  line-height: 90rpx;
-  background-color: #6c5ce7;
-  color: #fff;
-  border-radius: 45rpx;
-  font-size: 32rpx;
-  margin-top: 60rpx;
+.response-container {
+  margin-top: 20px;
+  padding: 0 20px;
+  display: none; /* 默认隐藏调试信息 */
+}
+
+/* 当showDebugInfo为true时显示 */
+.showDebugInfo .response-container {
+  display: block;
+}
+
+.response-item {
+  padding: 10px;
+  border: 1px solid #eee;
+  border-radius: 4px;
+  margin-bottom: 10px;
+  background-color: #f9f9f9;
 }
 
-.submit-btn[disabled] {
-  background-color: #b2b2b2;
-  color: #fff;
+.response-content {
+  display: flex;
+  flex-direction: column;
+}
+
+.answer-button-container {
+  position: absolute;
+  top: 75%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 20;
+}
+
+.answer-button {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+ background-color: #6c5ce7; /* 绿色背景,与图片中的颜色类似 */
+  color: white;
+  font-size: 18px;
+  border: none;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+  }
+  50% {
+    transform: scale(1.05);
+    box-shadow: 0 8px 15px rgba(0, 0, 0, 0.4);
+  }
+  100% {
+    transform: scale(1);
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+  }
 }
-</style> 
+</style>

+ 263 - 0
pages/interview/interview.vue

@@ -0,0 +1,263 @@
+<template>
+	<view class="container">
+		<view class="header">
+			<text class="title">语音对话</text>
+		</view>
+		
+		<view class="chat-container">
+			<scroll-view scroll-y class="chat-messages" :scroll-top="scrollTop">
+				<view v-for="(message, index) in messages" :key="index" class="message" :class="message.role">
+					<text>{{ message.content }}</text>
+				</view>
+			</scroll-view>
+		</view>
+		
+		<view class="controls">
+			<button class="voice-btn" :class="{ recording: isRecording }" @touchstart="startRecording" @touchend="stopRecording">
+				<text>{{ isRecording ? '松开结束' : '按住说话' }}</text>
+			</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				messages: [],
+				isRecording: false,
+				recorderManager: null,
+				innerAudioContext: null,
+				scrollTop: 0,
+				apiUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions'
+			}
+		},
+		onLoad() {
+			// 初始化录音管理器
+			this.recorderManager = uni.getRecorderManager();
+			this.innerAudioContext = uni.createInnerAudioContext();
+			
+			// 监听录音结束事件
+			this.recorderManager.onStop((res) => {
+				this.processAudio(res.tempFilePath);
+			});
+		},
+		methods: {
+			startRecording() {
+				this.isRecording = true;
+				// 开始录音
+				this.recorderManager.start({
+					format: 'mp3',
+					sampleRate: 16000,
+					numberOfChannels: 1
+				});
+			},
+			stopRecording() {
+				this.isRecording = false;
+				// 停止录音
+				this.recorderManager.stop();
+			},
+			processAudio(filePath) {
+				// 显示加载状态
+				uni.showLoading({
+					title: '处理中...'
+				});
+				
+				// 将录音文件转换为文本(语音识别)
+				// 这里需要接入语音识别服务,以下为示例
+				this.speechToText(filePath).then(text => {
+					// 添加用户消息
+					this.addMessage('user', text);
+					
+					// 调用DashScope API
+					this.callDashScopeAPI(text);
+				}).catch(err => {
+					uni.hideLoading();
+					uni.showToast({
+						title: '语音识别失败',
+						icon: 'none'
+					});
+					console.error(err);
+				});
+			},
+			speechToText(filePath) {
+				// 临时解决方案:直接返回模拟的语音识别结果
+				return new Promise((resolve) => {
+					console.log('录音文件路径:', filePath);
+					// 模拟语音识别结果
+					setTimeout(() => {
+						resolve('你好,我需要帮助');
+					}, 500);
+				});
+				
+				// 注释掉原来的实现,等待后续接入真实的语音识别服务
+				/*
+				return new Promise((resolve, reject) => {
+					uni.uploadFile({
+						url: '您的语音识别服务URL',
+						filePath: filePath,
+						name: 'file',
+						success: (res) => {
+							const data = JSON.parse(res.data);
+							if (data && data.result) {
+								resolve(data.result);
+							} else {
+								// 模拟语音识别结果,实际项目中请删除此行
+								resolve('你好,我需要帮助');
+								// reject(new Error('语音识别失败'));
+							}
+						},
+						fail: reject
+					});
+				});
+				*/
+			},
+			callDashScopeAPI(userInput) {
+				// 构建请求体
+				const requestBody = {
+					model: "qwen-plus",
+					messages: [
+						{
+							role: "system",
+							content: "You are a helpful assistant."
+						},
+						{
+							role: "user",
+							content: userInput
+						}
+					]
+				};
+				
+				// 发送请求到DashScope API
+				uni.request({
+					url: this.apiUrl,
+					method: 'POST',
+					header: {
+						'Content-Type': 'application/json',
+						'Authorization': 'Bearer YOUR_API_KEY' // 替换为您的API密钥
+					},
+					data: requestBody,
+					success: (res) => {
+						uni.hideLoading();
+						if (res.statusCode === 200 && res.data && res.data.choices && res.data.choices.length > 0) {
+							const assistantMessage = res.data.choices[0].message.content;
+							this.addMessage('assistant', assistantMessage);
+							
+							// 可选:将回复转为语音(文本转语音)
+							this.textToSpeech(assistantMessage);
+						} else {
+							uni.showToast({
+								title: '获取回复失败',
+								icon: 'none'
+							});
+							console.error('API响应错误:', res);
+						}
+					},
+					fail: (err) => {
+						uni.hideLoading();
+						uni.showToast({
+							title: '网络请求失败',
+							icon: 'none'
+						});
+						console.error(err);
+					}
+				});
+			},
+			addMessage(role, content) {
+				this.messages.push({ role, content });
+				// 滚动到底部
+				this.$nextTick(() => {
+					this.scrollTop = 9999;
+				});
+			},
+			textToSpeech(text) {
+				// 这里需要接入文本转语音服务
+				// 以下为示例,实际实现需要根据您使用的文本转语音服务调整
+				uni.request({
+					url: '您的文本转语音服务URL',
+					method: 'POST',
+					data: { text },
+					success: (res) => {
+						if (res.statusCode === 200 && res.data && res.data.audio_url) {
+							// 播放语音
+							this.innerAudioContext.src = res.data.audio_url;
+							this.innerAudioContext.play();
+						}
+					}
+				});
+			}
+		}
+	}
+</script>
+
+<style>
+.container {
+	display: flex;
+	flex-direction: column;
+	height: 100vh;
+	background-color: #f5f5f5;
+}
+
+.header {
+	padding: 20rpx;
+	background-color: #007AFF;
+	text-align: center;
+}
+
+.title {
+	color: #ffffff;
+	font-size: 36rpx;
+	font-weight: bold;
+}
+
+.chat-container {
+	flex: 1;
+	padding: 20rpx;
+	overflow: hidden;
+}
+
+.chat-messages {
+	height: 100%;
+}
+
+.message {
+	margin-bottom: 20rpx;
+	padding: 20rpx;
+	border-radius: 10rpx;
+	max-width: 80%;
+	word-break: break-word;
+}
+
+.message.user {
+	align-self: flex-end;
+	background-color: #007AFF;
+	color: white;
+	margin-left: auto;
+}
+
+.message.assistant {
+	align-self: flex-start;
+	background-color: #E5E5EA;
+	color: #333;
+}
+
+.controls {
+	padding: 20rpx;
+	background-color: #ffffff;
+	border-top: 1px solid #e0e0e0;
+}
+
+.voice-btn {
+	width: 100%;
+	height: 80rpx;
+	line-height: 80rpx;
+	text-align: center;
+	background-color: #007AFF;
+	color: white;
+	border-radius: 40rpx;
+}
+
+.voice-btn.recording {
+	background-color: #FF3B30;
+}
+</style>

binární
static/demo.mp4


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

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

+ 1 - 0
unpackage/dist/dev/mp-weixin/app.js

@@ -10,6 +10,7 @@ if (!Math) {
   "./pages/face-photo/face-photo.js";
   "./pages/camera/camera.js";
   "./pages/my/my.js";
+  "./pages/interview/interview.js";
 }
 const _sfc_main = {
   onLaunch: function() {

+ 2 - 1
unpackage/dist/dev/mp-weixin/app.json

@@ -6,7 +6,8 @@
     "pages/identity-verify/identity-verify",
     "pages/face-photo/face-photo",
     "pages/camera/camera",
-    "pages/my/my"
+    "pages/my/my",
+    "pages/interview/interview"
   ],
   "window": {
     "navigationBarTextStyle": "black",

+ 19 - 17
unpackage/dist/dev/mp-weixin/pages/camera/camera.js

@@ -75,10 +75,10 @@ const _sfc_main = {
     async fetchInterviewList() {
       try {
         this.loading = true;
-        const res2 = await api_user.getInterviewList();
+        const res2 = await api_user.getInterviewList({ job_id: JSON.parse(common_vendor.index.getStorageSync("selectedJob")).id });
         console.log(res2);
-        this.interviewId = res2.items;
-        this.fetchInterviewData(res2.items);
+        this.interviewId = res2;
+        this.fetchInterviewData(res2);
       } catch (error) {
         console.error("获取面试列表失败:", error);
         this.handleLoadError("获取面试列表失败");
@@ -214,22 +214,24 @@ const _sfc_main = {
     nextQuestion(data) {
       if (!this.showResult) {
         this.checkAnswer();
+        this.saveAnswer(data);
+        this.submitCurrentAnswer(data).then(() => {
+          this.showResult = true;
+        }).catch((error) => {
+          console.error("提交答案失败:", error);
+          common_vendor.index.showToast({
+            title: "提交答案失败,请重试",
+            icon: "none"
+          });
+          this.showResult = true;
+        });
         return;
       }
-      this.saveAnswer(data);
-      this.submitCurrentAnswer(data).then(() => {
-        if (this.currentQuestionIndex >= this.questions.length - 1) {
-          this.showEndModal = true;
-          return;
-        }
-        this.goToNextQuestion();
-      }).catch((error) => {
-        console.error("提交答案失败:", error);
-        common_vendor.index.showToast({
-          title: "提交答案失败,请重试",
-          icon: "none"
-        });
-      });
+      if (this.currentQuestionIndex >= this.questions.length - 1) {
+        this.showEndModal = true;
+        return;
+      }
+      this.goToNextQuestion();
     },
     // 保存当前题目的答案
     saveAnswer() {

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

@@ -1,5 +1,6 @@
 "use strict";
 const common_vendor = require("../../common/vendor.js");
+const api_user = require("../../api/user.js");
 const _sfc_main = {
   data() {
     return {
@@ -15,8 +16,10 @@ const _sfc_main = {
       // 是否正在录制视频
       recordingTime: 0,
       // 录制时间(秒)
-      recordingTimer: null
+      recordingTimer: null,
       // 录制计时器
+      photoUrl: ""
+      // 存储上传后返回的图片URL
     };
   },
   onReady() {
@@ -113,6 +116,7 @@ const _sfc_main = {
       this.cameraContext.stopRecord({
         success: (res) => {
           this.mediaSource = res.tempVideoPath;
+          console.log(res);
           common_vendor.index.hideLoading();
         },
         fail: (err) => {
@@ -136,60 +140,133 @@ const _sfc_main = {
       this.mediaSource = "";
       this.recordingTime = 0;
     },
-    startInterview() {
+    // 继续流程
+    continueProcess() {
       if (!this.mediaSource) {
         common_vendor.index.showToast({
-          title: `请先完成${this.mode === "photo" ? "拍照" : "视频录制"}`,
+          title: "请先完成拍照",
           icon: "none"
         });
         return;
       }
       common_vendor.index.showLoading({
-        title: "验证中..."
+        title: "上传中..."
       });
-      setTimeout(() => {
+      this.uploadMedia((url) => {
+        this.photoUrl = url;
+        this.submitPhotoUrl();
+      });
+    },
+    // 上传媒体文件方法
+    uploadMedia(callback) {
+      const openid = JSON.parse(common_vendor.index.getStorageSync("userInfo")).openid || "";
+      const tenant_id = 1;
+      if (!openid || !tenant_id) {
         common_vendor.index.hideLoading();
-        common_vendor.index.navigateTo({
-          url: "/pages/camera/camera",
-          fail: (err) => {
-            console.error("页面跳转失败:", err);
-            common_vendor.index.showToast({
-              title: "页面跳转失败",
-              icon: "none"
-            });
-          }
+        common_vendor.index.showToast({
+          title: "用户信息不完整,请重新登录",
+          icon: "none"
         });
-      }, 1500);
-    }
-    // 上传媒体文件方法(示例)
-    /* 
-    uploadMedia() {
-      uni.uploadFile({
-        url: 'https://your-api-endpoint.com/upload',
+        return;
+      }
+      common_vendor.index.uploadFile({
+        url: "http://192.168.66.187:8083/api/system/upload/",
         filePath: this.mediaSource,
-        name: this.mode === 'photo' ? 'photo' : 'video',
-        success: (res) => {
-          const data = JSON.parse(res.data);
-          if (data.success) {
-            // 验证成功,继续流程
+        name: "file",
+        formData: {
+          openid,
+          tenant_id
+        },
+        success: (uploadRes) => {
+          const res = JSON.parse(uploadRes.data);
+          if (res.code === 2e3) {
+            const photoUrl = res.data.url || res.data.photoUrl || "";
+            if (callback && typeof callback === "function") {
+              callback(photoUrl);
+            }
           } else {
-            // 验证失败,提示用户
-            uni.showToast({
-              title: data.message || '验证失败,请重试',
-              icon: 'none'
+            common_vendor.index.hideLoading();
+            common_vendor.index.showToast({
+              title: res.msg || "照片上传失败",
+              icon: "none"
             });
           }
         },
         fail: (err) => {
-          console.error('上传失败:', err);
-          uni.showToast({
-            title: '网络错误,请重试',
-            icon: 'none'
+          common_vendor.index.hideLoading();
+          console.error("上传失败:", err);
+          common_vendor.index.showToast({
+            title: "网络错误,请重试",
+            icon: "none"
+          });
+        }
+      });
+    },
+    // 提交照片URL到指定接口
+    submitPhotoUrl() {
+      const openid = JSON.parse(common_vendor.index.getStorageSync("userInfo")).openid || "";
+      const tenant_id = 1;
+      if (!this.photoUrl) {
+        common_vendor.index.hideLoading();
+        common_vendor.index.showToast({
+          title: "照片信息不完整,请重试",
+          icon: "none"
+        });
+        return;
+      }
+      api_user.fillUserInfo({
+        application_id: JSON.parse(common_vendor.index.getStorageSync("selectedJob")).id,
+        openid,
+        tenant_id,
+        avatar: this.photoUrl
+      }).then((res) => {
+        common_vendor.index.hideLoading();
+        console.log(res);
+        common_vendor.index.showToast({
+          title: "照片上传成功",
+          icon: "success"
+        });
+        setTimeout(() => {
+          common_vendor.index.navigateTo({
+            url: "/pages/identity-verify/identity-verify",
+            fail: (err) => {
+              console.error("页面跳转失败:", err);
+              common_vendor.index.showToast({
+                title: "页面跳转失败",
+                icon: "none"
+              });
+            }
           });
+        }, 1500);
+      }).catch((err) => {
+        common_vendor.index.hideLoading();
+        console.error("提交失败:", err);
+        common_vendor.index.showToast({
+          title: "网络错误,请重试",
+          icon: "none"
+        });
+      });
+    },
+    updateLocalUserInfo() {
+      api_user.getUserInfo().then((res) => {
+        if (res.code === 200 && res.data) {
+          let userInfo = {};
+          try {
+            userInfo = JSON.parse(common_vendor.index.getStorageSync("userInfo") || "{}");
+          } catch (e) {
+            console.error("解析本地存储用户信息失败:", e);
+            userInfo = {};
+          }
+          const updatedUserInfo = {
+            ...userInfo,
+            ...res.data
+          };
+          common_vendor.index.setStorageSync("userInfo", JSON.stringify(updatedUserInfo));
         }
+      }).catch((err) => {
+        console.error("更新本地用户信息失败:", err);
       });
     }
-    */
   }
 };
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
@@ -228,7 +305,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
   }) : {
     v: common_vendor.t($data.mode === "photo" ? "拍照" : "录制"),
     w: common_vendor.o((...args) => $options.retakeMedia && $options.retakeMedia(...args)),
-    x: common_vendor.o((...args) => $options.startInterview && $options.startInterview(...args))
+    x: common_vendor.o((...args) => $options.continueProcess && $options.continueProcess(...args))
   }, {
     y: $data.isPageLoaded ? 1 : ""
   });

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

@@ -1 +1 @@
-<view class="{{['photo-container', y && 'loaded']}}"><view class="photo-header"><text class="photo-title">拍摄照片</text><text class="photo-subtitle">我们将用于身份核验,请正对摄像头</text></view><view class="mode-selector"><view class="{{['mode-option', a && 'active']}}" bindtap="{{b}}">拍照</view><view class="{{['mode-option', c && 'active']}}" bindtap="{{d}}">录制视频</view></view><view class="photo-preview"><camera wx:if="{{e}}" device-position="front" flash="auto" class="camera" mode="{{f}}" binderror="{{g}}"></camera><image wx:elif="{{h}}" class="preview-image" src="{{i}}" mode="aspectFit"></image><video wx:elif="{{j}}" class="preview-video" src="{{k}}" controls autoplay></video><view class="face-outline"></view><view wx:if="{{l}}" class="recording-indicator"><view class="recording-dot"></view><text class="recording-time">{{m}}</text></view></view><view wx:if="{{n}}" class="capture-btn-container"><button wx:if="{{o}}" class="capture-btn" bindtap="{{p}}">拍照</button><button wx:elif="{{q}}" class="capture-btn" bindtap="{{r}}">开始录制</button><button wx:elif="{{s}}" class="stop-btn" bindtap="{{t}}">停止录制</button></view><view wx:else class="btn-group"><button class="retry-btn" bindtap="{{w}}">重新{{v}}</button><button class="start-btn" bindtap="{{x}}">开始面试</button></view></view>
+<view class="{{['photo-container', y && 'loaded']}}"><view class="photo-header"><text class="photo-title">拍摄面部照片</text><text class="photo-subtitle">我们将用于身份核验,请正对摄像头</text></view><view class="mode-selector"><view class="{{['mode-option', a && 'active']}}" bindtap="{{b}}">拍照</view><view class="{{['mode-option', c && 'active']}}" bindtap="{{d}}">录制视频</view></view><view class="photo-preview"><camera wx:if="{{e}}" device-position="front" flash="auto" class="camera" mode="{{f}}" binderror="{{g}}"></camera><image wx:elif="{{h}}" class="preview-image" src="{{i}}" mode="aspectFit"></image><video wx:elif="{{j}}" class="preview-video" src="{{k}}" controls autoplay></video><view class="face-outline"></view><view wx:if="{{l}}" class="recording-indicator"><view class="recording-dot"></view><text class="recording-time">{{m}}</text></view></view><view wx:if="{{n}}" class="capture-btn-container"><button wx:if="{{o}}" class="capture-btn" bindtap="{{p}}">拍照</button><button wx:elif="{{q}}" class="capture-btn" bindtap="{{r}}">开始录制</button><button wx:elif="{{s}}" class="stop-btn" bindtap="{{t}}">停止录制</button></view><view wx:else class="btn-group"><button class="retry-btn" bindtap="{{w}}">重新{{v}}</button><button class="start-btn" bindtap="{{x}}">完成</button></view></view>

+ 7 - 0
unpackage/dist/dev/mp-weixin/pages/face-photo/face-photo.wxss

@@ -165,3 +165,10 @@
   border-radius: 45rpx;
   font-size: 32rpx;
 }
+
+/* 添加手部轮廓样式 */
+.face-outline.hand-outline {
+  width: 500rpx;
+  height: 300rpx;
+  border-radius: 30rpx;
+}

+ 595 - 52
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.js

@@ -1,78 +1,621 @@
 "use strict";
 const common_vendor = require("../../common/vendor.js");
 const _sfc_main = {
+  name: "IdentityVerify",
   data() {
     return {
-      formData: {
-        name: "",
-        idCard: ""
-      },
-      isAgreed: false
+      loading: false,
+      responses: [],
+      processedResponses: [],
+      assistantResponse: "",
+      audioTranscript: "",
+      videoPlaying: false,
+      showDebugInfo: false,
+      // 设置为true可以显示调试信息
+      videoUrl: "http://121.36.251.245:9000/minlong/0a0b3516-e0bb-4f6c-874c-8aaaca9d7f8f.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T135206Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=4a79bc77f80ae7344f339717dbe505a3b152140251a6b2c76c1fd5c047b39c74",
+      // 用于存储AI数字人视频URL
+      showReplayButton: false,
+      cameraStream: null,
+      // 存储摄像头流
+      cameraError: null,
+      // 存储摄像头错误信息
+      useMiniProgramCameraComponent: false,
+      // 添加小程序相机组件标志
+      cameraContext: null,
+      // 添加相机上下文
+      currentSubtitle: "",
+      subtitles: [
+        {
+          startTime: 0,
+          // 开始时间(秒)
+          endTime: 5,
+          // 结束时间(秒)
+          text: "你好,我是本次面试的面试官,欢迎参加本公司的线上面试!"
+        },
+        {
+          startTime: 5,
+          endTime: 13,
+          text: "面试预计需要15分钟,请你提前安排在网络良好、光线亮度合适、且相对安静的环境参加这次面试"
+        },
+        {
+          startTime: 13,
+          endTime: 20,
+          text: "以免影响本次面试的结果。如果你在面试过程中遇到问题,请与我们的招聘人员联系。"
+        }
+      ],
+      secondVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 10,
+          text: "请结合您的基本信息与过往履历进行简单的自我介绍,并讲一讲您有哪些优势胜任本岗位:"
+        }
+      ],
+      thirdVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 3,
+          text: "在工作中,你如何确保个人防护装备的正确使用?"
+        }
+      ],
+      fourthVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 3,
+          text: "描述一次你与团队合作改善生产流程的经历。"
+        }
+      ],
+      fifthVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 6,
+          text: "你在团队合作中曾遇到过哪些挑战?如何解决团队内部的分歧?"
+        }
+      ],
+      sixthVideoSubtitles: [
+        {
+          startTime: 0,
+          endTime: 5,
+          text: "您已完成本次面试全部题目,请问您对于这个岗位还有什么想要了解的吗?"
+        }
+      ],
+      showAnswerButton: false,
+      // 控制答题按钮显示
+      currentVideoIndex: 0,
+      // 当前播放的视频索引
+      videoList: [
+        "http://121.36.251.245:9000/minlong/0a0b3516-e0bb-4f6c-874c-8aaaca9d7f8f.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T135206Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=4a79bc77f80ae7344f339717dbe505a3b152140251a6b2c76c1fd5c047b39c74",
+        // 第一段视频
+        "http://121.36.251.245:9000/minlong/9ab3fd68-a2e9-47a7-a05e-a6e2253ef22c.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T143129Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=7a0ebf1058252b5f76895b31a5293d4781b33ede63d32dce148350846aa20621",
+        // 第二段视频
+        "http://121.36.251.245:9000/minlong/69406ce9-8d8e-48aa-ba2f-3b12ea5b6a6c.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T144114Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=a43a06217a63f9bb6af975b1a7aa419b6f8e3d77d8c6c9c67d1070edfe60dc43",
+        // 第三段视频
+        "http://121.36.251.245:9000/minlong/1cd448b2-16ea-4565-be25-2cf71d1bf7b2.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T144554Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=6ef5b0b160feab053e7d95952cdac62266d1a1eb48999efb17d7a3c4e0b495ed",
+        "http://121.36.251.245:9000/minlong/5a9ad6b2-0de8-48e3-8eb7-141a9bee4a9b.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T145623Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=8a2120a4c6b059d64f90620377fdda2536f9fff9c455d5140210ef35990758c6",
+        "http://121.36.251.245:9000/minlong/7aafb07e-ab0d-477e-9124-3263d0b7bf6f.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250416%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250416T145857Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=54ebe67d751c7e44a0f608b26175c9d076685a0e647e7e134cda22bbca2639eb"
+        //结束
+      ]
     };
   },
-  computed: {
-    canSubmit() {
-      return this.formData.name.trim() && this.formData.idCard.trim() && this.isAgreed;
-    }
+  mounted() {
+    this.playDigitalHumanVideo();
+    this.initCamera();
+  },
+  beforeDestroy() {
+    this.stopUserCamera();
   },
   methods: {
-    toggleAgreement() {
-      this.isAgreed = !this.isAgreed;
-    },
-    submitForm() {
-      if (!this.canSubmit) {
-        let message = "";
-        if (!this.formData.name.trim()) {
-          message = "请输入姓名";
-        } else if (!this.formData.idCard.trim()) {
-          message = "请输入身份证号";
-        } else if (!this.isAgreed) {
-          message = "请阅读并同意相关协议";
+    // 初始化相机
+    initCamera() {
+      const systemInfo = common_vendor.index.getSystemInfoSync();
+      const isMiniProgram = systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay" || systemInfo.uniPlatform === "mp-baidu" || systemInfo.uniPlatform === "mp-toutiao";
+      this.useMiniProgramCameraComponent = isMiniProgram;
+      if (isMiniProgram) {
+        this.$nextTick(() => {
+          this.cameraContext = common_vendor.index.createCameraContext();
+        });
+      } else {
+        this.startUserCamera();
+      }
+    },
+    // 启动用户摄像头
+    async startUserCamera() {
+      try {
+        const systemInfo = common_vendor.index.getSystemInfoSync();
+        const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
+        if (isMiniProgram) {
+          console.log("小程序环境不支持 getUserMedia API");
+          return;
+        }
+        if (!navigator || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+          throw new Error("您的浏览器不支持摄像头访问");
+        }
+        const stream = await navigator.mediaDevices.getUserMedia({
+          video: {
+            width: { ideal: 200 },
+            height: { ideal: 150 },
+            facingMode: "user"
+            // 使用前置摄像头
+          },
+          audio: true
+          // 不需要音频
+        });
+        this.cameraStream = stream;
+        this.$nextTick(() => {
+          if (this.$refs.userCameraVideo) {
+            this.$refs.userCameraVideo.srcObject = stream;
+          }
+        });
+      } catch (error) {
+        console.error("无法访问摄像头:", error);
+        this.cameraError = error.message;
+        let errorMessage = "无法访问摄像头,请检查权限设置";
+        if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
+          errorMessage = "摄像头访问被拒绝,请在浏览器设置中允许摄像头访问";
+        } else if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
+          errorMessage = "未检测到摄像头设备";
+        } else if (error.name === "NotReadableError" || error.name === "TrackStartError") {
+          errorMessage = "摄像头可能被其他应用占用";
+        } else if (error.name === "OverconstrainedError") {
+          errorMessage = "摄像头不满足指定的要求";
+        } else if (error.name === "TypeError" || error.message.includes("SSL")) {
+          errorMessage = "请确保在HTTPS环境下访问";
         }
         common_vendor.index.showToast({
-          title: message,
-          icon: "none"
+          title: errorMessage,
+          icon: "none",
+          duration: 3e3
         });
-        return;
+        this.handleCameraError(errorMessage);
       }
-      const idCardReg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
-      if (!idCardReg.test(this.formData.idCard)) {
+    },
+    // 停止用户摄像头
+    stopUserCamera() {
+      if (this.cameraStream) {
+        this.cameraStream.getTracks().forEach((track) => {
+          track.stop();
+        });
+        this.cameraStream = null;
+      }
+    },
+    async fetchData() {
+      this.loading = true;
+      this.assistantResponse = "";
+      this.audioTranscript = "";
+      this.processedResponses = [];
+      try {
+        const requestTask = common_vendor.index.request({
+          url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
+          method: "POST",
+          header: {
+            "Content-Type": "application/json",
+            "Authorization": "Bearer sk-9e1ec73a7d97493b8613c63f06b6110c"
+          },
+          data: {
+            "model": "qwen-omni-turbo",
+            "messages": [
+              {
+                "role": "user",
+                "content": [
+                  {
+                    "type": "input_audio",
+                    "input_audio": {
+                      "data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav",
+                      "format": "wav"
+                    }
+                  },
+                  {
+                    "type": "text",
+                    "text": "这段音频在说什么"
+                  }
+                ]
+              }
+            ],
+            "stream": true,
+            "stream_options": {
+              "include_usage": true
+            },
+            "modalities": ["text", "audio"],
+            "audio": { "voice": "Cherry", "format": "wav" }
+          },
+          success: (res) => {
+            console.log("请求成功,响应数据:", res.data);
+            if (typeof res.data === "string" && res.data.includes("data: {")) {
+              const chunks = res.data.split("data: ").filter((chunk) => chunk.trim() !== "");
+              chunks.forEach((chunk) => {
+                this.handleStreamResponse(chunk);
+              });
+            } else {
+              this.handleStreamResponse(res.data);
+            }
+            this.playDigitalHumanVideo();
+          },
+          fail: (err) => {
+            console.error("请求失败:", err);
+          },
+          complete: () => {
+            this.loading = false;
+          }
+        });
+      } catch (error) {
+        console.error("获取数据失败:", error);
+        this.loading = false;
+      }
+    },
+    handleStreamResponse(data) {
+      if (typeof data === "string") {
+        if (data === "[DONE]")
+          return;
+        try {
+          const cleanData = data.trim();
+          if (cleanData.startsWith("{") && cleanData.endsWith("}")) {
+            const jsonData = JSON.parse(cleanData);
+            this.processStreamChunk(jsonData);
+          }
+        } catch (e) {
+          console.error("解析JSON失败:", e, "原始数据:", data);
+        }
+      } else {
+        this.processStreamChunk(data);
+      }
+    },
+    processStreamChunk(chunk) {
+      if (chunk.choices && chunk.choices.length > 0) {
+        const choice = chunk.choices[0];
+        if (choice.delta && choice.delta.content) {
+          this.assistantResponse += choice.delta.content;
+        }
+        if (choice.delta && choice.delta.audio && choice.delta.audio.transcript) {
+          this.audioTranscript += choice.delta.audio.transcript;
+        }
+        if (choice.delta) {
+          const result = {};
+          if (choice.delta.role) {
+            result.role = choice.delta.role;
+          }
+          if (choice.delta.audio && choice.delta.audio.transcript) {
+            result.transcript = choice.delta.audio.transcript;
+          }
+          if (Object.keys(result).length > 0) {
+            this.processedResponses.push(result);
+          }
+        }
+      }
+    },
+    processResponseData() {
+      this.processedResponses = this.responses.map((item) => {
+        const result = {};
+        if (item.delta && item.delta.role) {
+          result.role = item.delta.role;
+        }
+        if (item.delta && item.delta.audio && item.delta.audio.transcript) {
+          result.transcript = item.delta.audio.transcript;
+        }
+        return result;
+      }).filter((item) => Object.keys(item).length > 0);
+    },
+    // 播放数字人视频
+    playDigitalHumanVideo() {
+      this.videoUrl = this.videoList[this.currentVideoIndex];
+      this.videoPlaying = true;
+      this.$nextTick(() => {
+        const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+        if (videoContext) {
+          videoContext.play();
+          setTimeout(() => {
+            if (this.videoPlaying && this.$refs.videoPlayer) {
+              console.log("视频应该正在播放");
+            } else {
+              console.log("视频可能未成功播放,尝试替代方案");
+              this.tryAlternativeVideoPath();
+            }
+          }, 1e3);
+        } else {
+          console.error("无法创建视频上下文");
+          this.tryAlternativeVideoPath();
+        }
+      });
+    },
+    // 修改 tryAlternativeVideoPath 方法
+    tryAlternativeVideoPath() {
+      console.log("尝试使用替代路径");
+      const alternativePaths = [
+        "./static/demo.mp4",
+        "../static/demo.mp4",
+        "static/demo.mp4",
+        "/static/demo.mp4",
+        // 添加绝对路径
+        `${window.location.origin}/static/demo.mp4`
+      ];
+      const currentPathIndex = alternativePaths.indexOf(this.videoUrl);
+      const nextPathIndex = (currentPathIndex + 1) % alternativePaths.length;
+      this.videoUrl = alternativePaths[nextPathIndex];
+      console.log("尝试新路径:", this.videoUrl);
+      this.$nextTick(() => {
+        const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+        if (videoContext) {
+          videoContext.stop();
+          videoContext.play();
+          setTimeout(() => {
+            if (nextPathIndex === alternativePaths.length - 1 && !this.videoPlaying) {
+              console.log("所有路径均失败,尝试使用uni.getVideoInfo检查视频");
+              this.checkVideoWithAPI();
+            }
+          }, 1e3);
+        }
+      });
+    },
+    // 添加新方法:使用uni API检查视频
+    checkVideoWithAPI() {
+      common_vendor.index.getVideoInfo({
+        src: "/static/demo.mp4",
+        success: (res) => {
+          console.log("视频信息获取成功:", res);
+          this.videoUrl = "/static/demo.mp4";
+          this.$nextTick(() => {
+            const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+            if (videoContext) {
+              videoContext.play();
+            }
+          });
+        },
+        fail: (err) => {
+          console.error("视频信息获取失败:", err);
+          this.fallbackToLocalVideo();
+        }
+      });
+    },
+    // 添加新方法:回退到本地视频
+    fallbackToLocalVideo() {
+      console.log("尝试使用本地视频资源");
+      const platform = common_vendor.index.getSystemInfoSync().platform;
+      if (platform === "android" || platform === "ios") {
+        this.videoUrl = platform === "android" ? "android.resource://package_name/raw/demo" : "file:///assets/demo.mp4";
+        this.$nextTick(() => {
+          const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+          if (videoContext) {
+            videoContext.play();
+          }
+        });
+      } else {
+        this.videoPlaying = false;
         common_vendor.index.showToast({
-          title: "请输入正确的身份证号",
+          title: "视频加载失败,显示静态图片",
           icon: "none"
         });
-        return;
       }
-      common_vendor.index.showLoading({
-        title: "验证中..."
+    },
+    // 修改 handleVideoError 方法
+    handleVideoError(e) {
+      console.error("视频加载错误:", e);
+      if (e && e.detail) {
+        console.error("详细错误信息:", e.detail);
+      }
+      common_vendor.index.getFileInfo({
+        filePath: this.videoUrl.startsWith("/") ? this.videoUrl.substring(1) : this.videoUrl,
+        success: (res) => {
+          console.log("文件存在,大小:", res.size);
+          this.tryDifferentFormat();
+        },
+        fail: (err) => {
+          console.error("文件不存在或无法访问:", err);
+          this.tryAlternativeVideoPath();
+        }
       });
-      setTimeout(() => {
-        common_vendor.index.hideLoading();
-        common_vendor.index.navigateTo({
-          url: "/pages/face-photo/face-photo",
-          fail: (err) => {
-            console.error("页面跳转失败:", err);
-            common_vendor.index.showToast({
-              title: "页面跳转失败",
-              icon: "none"
-            });
+      common_vendor.index.showToast({
+        title: "视频加载失败,请检查文件是否存在",
+        icon: "none",
+        duration: 2e3
+      });
+    },
+    // 添加新方法:尝试不同格式
+    tryDifferentFormat() {
+      console.log("尝试不同的视频格式");
+      const formats = [
+        { ext: "mp4", mime: "video/mp4" },
+        { ext: "webm", mime: "video/webm" },
+        { ext: "ogg", mime: "video/ogg" },
+        { ext: "mov", mime: "video/quicktime" }
+      ];
+      const currentPath = this.videoUrl;
+      const basePath = currentPath.substring(0, currentPath.lastIndexOf(".")) || "/static/demo";
+      let nextFormat = formats.find((f) => !currentPath.endsWith(f.ext));
+      if (nextFormat) {
+        this.videoUrl = `${basePath}.${nextFormat.ext}`;
+        console.log("尝试新格式:", this.videoUrl);
+        this.$nextTick(() => {
+          const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+          if (videoContext) {
+            videoContext.stop();
+            videoContext.play();
           }
         });
-      }, 1500);
+      } else {
+        this.useBuiltInResource();
+      }
+    },
+    // 添加新方法:使用内置资源
+    useBuiltInResource() {
+      console.log("尝试使用内置资源");
+      const platform = common_vendor.index.getSystemInfoSync().platform;
+      if (platform === "windows") {
+        process.env.UNI_INPUT_DIR || "";
+        this.videoUrl = `./static/demo.mp4`;
+        console.log("Windows平台尝试路径:", this.videoUrl);
+      } else if (platform === "android" || platform === "ios") {
+        this.useNativeVideo();
+      } else {
+        const baseUrl = window.location.origin;
+        this.videoUrl = `${baseUrl}/static/demo.mp4`;
+        console.log("Web平台尝试URL:", this.videoUrl);
+      }
+      this.$nextTick(() => {
+        const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+        if (videoContext) {
+          videoContext.play();
+        }
+      });
+    },
+    // 添加新方法:使用原生视频能力
+    useNativeVideo() {
+      console.log("尝试使用原生视频能力");
+      common_vendor.index.chooseVideo({
+        sourceType: ["album"],
+        success: (res) => {
+          this.videoUrl = res.tempFilePath;
+          this.$nextTick(() => {
+            const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+            if (videoContext) {
+              videoContext.play();
+            }
+          });
+        },
+        fail: () => {
+          this.videoPlaying = false;
+          common_vendor.index.showToast({
+            title: "无法加载视频,显示静态图片",
+            icon: "none"
+          });
+        }
+      });
+    },
+    // 处理视频结束事件
+    handleVideoEnded() {
+      console.log("视频播放结束");
+      this.videoPlaying = false;
+      this.showAnswerButton = true;
+    },
+    // 处理答题按钮点击
+    handleAnswerButtonClick() {
+      this.showAnswerButton = false;
+      this.currentVideoIndex++;
+      if (this.currentVideoIndex < this.videoList.length) {
+        this.videoUrl = this.videoList[this.currentVideoIndex];
+        this.videoPlaying = true;
+        this.currentSubtitle = "";
+        this.$nextTick(() => {
+          const videoContext = common_vendor.index.createVideoContext("myVideo", this);
+          if (videoContext) {
+            videoContext.play();
+          }
+        });
+      } else {
+        common_vendor.index.showToast({
+          title: "面试完成",
+          icon: "success",
+          duration: 2e3
+        });
+        setTimeout(() => {
+          common_vendor.index.navigateTo({
+            url: "/pages/interview-result/interview-result"
+          });
+        }, 2e3);
+      }
+    },
+    // 处理相机错误
+    handleCameraError(e) {
+      console.error("相机错误:", e);
+      common_vendor.index.showToast({
+        title: "相机初始化失败,请检查权限设置",
+        icon: "none"
+      });
+      this.tryFallbackOptions();
+    },
+    // 添加新方法:尝试备用选项
+    tryFallbackOptions() {
+      const systemInfo = common_vendor.index.getSystemInfoSync();
+      if (systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay") {
+        this.useMiniProgramCamera();
+      } else {
+        this.showStaticCameraPlaceholder();
+      }
+    },
+    // 添加新方法:使用小程序相机API
+    useMiniProgramCamera() {
+      console.log("尝试使用小程序相机组件");
+      this.useMiniProgramCameraComponent = true;
+    },
+    // 添加新方法:显示静态图像
+    showStaticCameraPlaceholder() {
+      console.log("显示静态摄像头占位图");
+      const img = document.createElement("img");
+      img.src = "/static/images/camera-placeholder.png";
+      img.className = "static-camera-image";
+      img.style.width = "100%";
+      img.style.height = "100%";
+      img.style.objectFit = "cover";
+      const container = this.$refs.userCameraVideo.parentNode;
+      container.appendChild(img);
+    },
+    // 处理视频时间更新事件
+    handleTimeUpdate(e) {
+      const currentTime = e.target.currentTime;
+      let currentSubtitles;
+      if (this.currentVideoIndex === 0) {
+        currentSubtitles = this.subtitles;
+      } else if (this.currentVideoIndex === 1) {
+        currentSubtitles = this.secondVideoSubtitles;
+      } else if (this.currentVideoIndex === 2) {
+        currentSubtitles = this.thirdVideoSubtitles;
+      } else if (this.currentVideoIndex === 3) {
+        currentSubtitles = this.fourthVideoSubtitles;
+      } else if (this.currentVideoIndex === 4) {
+        currentSubtitles = this.fifthVideoSubtitles;
+      } else if (this.currentVideoIndex === 5) {
+        currentSubtitles = this.sixthVideoSubtitles;
+      } else {
+        currentSubtitles = [];
+      }
+      const subtitle = currentSubtitles.find(
+        (sub) => currentTime >= sub.startTime && currentTime < sub.endTime
+      );
+      this.currentSubtitle = subtitle ? subtitle.text : "";
     }
   }
 };
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
-  return {
-    a: $data.formData.name,
-    b: common_vendor.o(($event) => $data.formData.name = $event.detail.value),
-    c: $data.formData.idCard,
-    d: common_vendor.o(($event) => $data.formData.idCard = $event.detail.value),
-    e: $data.isAgreed,
-    f: common_vendor.o((...args) => $options.toggleAgreement && $options.toggleAgreement(...args)),
-    g: !$options.canSubmit,
-    h: common_vendor.o((...args) => $options.submitForm && $options.submitForm(...args))
-  };
+  return common_vendor.e({
+    a: $data.videoUrl,
+    b: common_vendor.o((...args) => $options.handleVideoError && $options.handleVideoError(...args)),
+    c: common_vendor.o((...args) => $options.handleVideoEnded && $options.handleVideoEnded(...args)),
+    d: common_vendor.o((...args) => $options.handleTimeUpdate && $options.handleTimeUpdate(...args)),
+    e: $data.currentSubtitle
+  }, $data.currentSubtitle ? {
+    f: common_vendor.t($data.currentSubtitle)
+  } : {}, {
+    g: $data.showAnswerButton
+  }, $data.showAnswerButton ? {
+    h: common_vendor.o((...args) => $options.handleAnswerButtonClick && $options.handleAnswerButtonClick(...args))
+  } : {}, {
+    i: $data.useMiniProgramCameraComponent
+  }, $data.useMiniProgramCameraComponent ? {
+    j: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args))
+  } : {}, {
+    k: $data.loading
+  }, $data.loading ? {} : {}, {
+    l: $data.showDebugInfo
+  }, $data.showDebugInfo ? common_vendor.e({
+    m: $data.assistantResponse
+  }, $data.assistantResponse ? {
+    n: common_vendor.t($data.assistantResponse)
+  } : {}, {
+    o: $data.audioTranscript
+  }, $data.audioTranscript ? {
+    p: common_vendor.t($data.audioTranscript)
+  } : {}, {
+    q: common_vendor.f($data.processedResponses, (item, index, i0) => {
+      return common_vendor.e({
+        a: item.role
+      }, item.role ? {
+        b: common_vendor.t(item.role)
+      } : {}, {
+        c: item.transcript
+      }, item.transcript ? {
+        d: common_vendor.t(item.transcript)
+      } : {}, {
+        e: index
+      });
+    })
+  }) : {});
 }
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-464e78c6"]]);
 wx.createPage(MiniProgramPage);

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.json

@@ -1,4 +1,4 @@
 {
-  "navigationBarTitleText": "身份验证",
+  "navigationBarTitleText": "",
   "usingComponents": {}
 }

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.wxml

@@ -1 +1 @@
-<view class="verify-container"><view class="verify-tip"> 身份证号码仅用于验证您的身份信息,以确保面试的真实性和安全性 </view><view class="form-container"><view class="form-item"><text class="form-label">姓名<text class="required">*</text></text><input type="text" placeholder="请输入姓名" class="form-input" value="{{a}}" bindinput="{{b}}"/></view><view class="form-item"><text class="form-label">身份证号<text class="required">*</text></text><input type="text" placeholder="请输入有效身份证号" class="form-input" maxlength="18" value="{{c}}" bindinput="{{d}}"/></view><view class="agreement"><checkbox checked="{{e}}" bindtap="{{f}}" color="#6c5ce7"/><text class="agreement-text"> 我已阅读并同意 <text class="agreement-link">《身份验证服务协议》</text><text class="agreement-link">《隐私保护政策》</text><text class="agreement-link">《网络安全协议》</text></text></view></view><button class="submit-btn" disabled="{{g}}" bindtap="{{h}}">提交</button></view>
+<view class="identity-verify-container data-v-464e78c6"><view class="digital-human-container data-v-464e78c6"><view class="digital-human-video data-v-464e78c6"><video src="{{a}}" id="myVideo" ref="videoPlayer" autoplay playsinline disablePictureInPicture controlsList="nodownload nofullscreen noremoteplayback" class="video-player data-v-464e78c6" controls="{{false}}" binderror="{{b}}" bindended="{{c}}" bindtimeupdate="{{d}}"></video><view wx:if="{{e}}" class="subtitle-overlay data-v-464e78c6">{{f}}</view><view wx:if="{{g}}" class="answer-button-container data-v-464e78c6"><button class="answer-button data-v-464e78c6" bindtap="{{h}}"> 开始面试 </button></view></view><view class="user-camera-container data-v-464e78c6"><camera wx:if="{{i}}" device-position="front" flash="off" class="user-camera-video data-v-464e78c6" binderror="{{j}}"></camera><video wx:else id="userCamera" ref="userCameraVideo" autoplay playsinline muted class="user-camera-video data-v-464e78c6" controls="{{false}}"></video></view></view><view wx:if="{{k}}" class="loading data-v-464e78c6">加载中...</view><view wx:if="{{l}}" class="response-container data-v-464e78c6"><view wx:if="{{m}}" class="response-item data-v-464e78c6"><view class="response-content data-v-464e78c6"><label class="data-v-464e78c6">助手回复: {{n}}</label></view></view><view wx:if="{{o}}" class="response-item data-v-464e78c6"><view class="response-content data-v-464e78c6"><label class="data-v-464e78c6">音频转写: {{p}}</label></view></view><view wx:for="{{q}}" wx:for-item="item" wx:key="e" class="response-item data-v-464e78c6"><view class="response-content data-v-464e78c6"><label wx:if="{{item.a}}" class="data-v-464e78c6">角色: {{item.b}}</label><label wx:if="{{item.c}}" class="data-v-464e78c6">文本: {{item.d}}</label></view></view></view></view>

+ 161 - 51
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.wxss

@@ -1,68 +1,178 @@
 
-.verify-container {
+.identity-verify-container.data-v-464e78c6 {
+  padding: 0;
+  max-width: 100%;
+  margin: 0 auto;
+  height: 100vh;
   display: flex;
   flex-direction: column;
-  min-height: 100vh;
-  background-color: #f5f7fa;
-  padding: 30rpx;
+  background-color: #f5f5f5;
 }
-.verify-tip {
-  font-size: 26rpx;
-  color: #666;
+.digital-human-container.data-v-464e78c6 {
+  position: relative;
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+  background-color: #f0f0f0;
+}
+.digital-human-video.data-v-464e78c6 {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+/* 用户摄像头容器样式 */
+.user-camera-container.data-v-464e78c6 {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  width: 100px;
+  height: 150px;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+  z-index: 20;
+  border: 1px solid #fff;
+}
+
+/* 用户摄像头视频样式 */
+.user-camera-video.data-v-464e78c6 {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  background-color: #333;
+}
+.video-player.data-v-464e78c6 {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  outline: none; /* 移除视频获得焦点时的轮廓 */
+  -webkit-tap-highlight-color: transparent; /* 移除移动设备上的点击高亮 */
+}
+
+/* 隐藏视频控制条 */
+video.data-v-464e78c6::-webkit-media-controls {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-enclosure {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-panel {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-play-button {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-timeline {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-current-time-display {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-time-remaining-display {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-mute-button {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-volume-slider {
+  display: none !important;
+}
+video.data-v-464e78c6::-webkit-media-controls-fullscreen-button {
+  display: none !important;
+}
+.subtitle-overlay.data-v-464e78c6 {
+  position: absolute;
+  bottom: 50px;
+  left: 0;
+  width: 100%;
+  padding: 15px;
+  background-color: rgba(0, 0, 0, 0.7);
+  color: white;
+  text-align: center;
+  font-size: 16px;
   line-height: 1.5;
-  margin-bottom: 40rpx;
+  z-index: 10;
+  border-radius: 4px;
+  max-width: 90%;
+  margin: 0 auto;
+  left: 5%;
+  right: 5%;
+}
+.control-panel.data-v-464e78c6 {
+  padding: 15px;
+  display: flex;
+  justify-content: center;
+}
+.control-button.data-v-464e78c6 {
+  padding: 10px 20px;
+  background-color: #4CAF50;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
 }
-.form-container {
-  background-color: #fff;
-  border-radius: 12rpx;
-  padding: 20rpx;
-  margin-bottom: 40rpx;
+.loading.data-v-464e78c6 {
+  text-align: center;
+  margin: 20px 0;
+  font-size: 16px;
 }
-.form-item {
-  margin-bottom: 30rpx;
+.response-container.data-v-464e78c6 {
+  margin-top: 20px;
+  padding: 0 20px;
+  display: none; /* 默认隐藏调试信息 */
 }
-.form-label {
+
+/* 当showDebugInfo为true时显示 */
+.showDebugInfo .response-container.data-v-464e78c6 {
   display: block;
-  font-size: 28rpx;
-  color: #333;
-  margin-bottom: 15rpx;
 }
-.required {
-  color: #e74c3c;
-  margin-left: 5rpx;
+.response-item.data-v-464e78c6 {
+  padding: 10px;
+  border: 1px solid #eee;
+  border-radius: 4px;
+  margin-bottom: 10px;
+  background-color: #f9f9f9;
 }
-.form-input {
-  width: 100%;
-  height: 80rpx;
-  border-bottom: 1px solid #eee;
-  font-size: 28rpx;
-  box-sizing: border-box;
+.response-content.data-v-464e78c6 {
+  display: flex;
+  flex-direction: column;
 }
-.agreement {
+.answer-button-container.data-v-464e78c6 {
+  position: absolute;
+  top: 75%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 20;
+}
+.answer-button.data-v-464e78c6 {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+ background-color: #6c5ce7; /* 绿色背景,与图片中的颜色类似 */
+  color: white;
+  font-size: 18px;
+  border: none;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
   display: flex;
-  align-items: flex-start;
-  margin-top: 20rpx;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+  animation: pulse-464e78c6 2s infinite;
 }
-.agreement-text {
-  font-size: 24rpx;
-  color: #666;
-  line-height: 1.5;
-  margin-left: 10rpx;
+@keyframes pulse-464e78c6 {
+0% {
+    transform: scale(1);
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
 }
-.agreement-link {
-  color: #6c5ce7;
+50% {
+    transform: scale(1.05);
+    box-shadow: 0 8px 15px rgba(0, 0, 0, 0.4);
+}
+100% {
+    transform: scale(1);
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
 }
-.submit-btn {
-  width: 100%;
-  height: 90rpx;
-  line-height: 90rpx;
-  background-color: #6c5ce7;
-  color: #fff;
-  border-radius: 45rpx;
-  font-size: 32rpx;
-  margin-top: 60rpx;
-}
-.submit-btn[disabled] {
-  background-color: #b2b2b2;
-  color: #fff;
 }

+ 143 - 0
unpackage/dist/dev/mp-weixin/pages/interview/interview.js

@@ -0,0 +1,143 @@
+"use strict";
+const common_vendor = require("../../common/vendor.js");
+const _sfc_main = {
+  data() {
+    return {
+      messages: [],
+      isRecording: false,
+      recorderManager: null,
+      innerAudioContext: null,
+      scrollTop: 0,
+      apiUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
+    };
+  },
+  onLoad() {
+    this.recorderManager = common_vendor.index.getRecorderManager();
+    this.innerAudioContext = common_vendor.index.createInnerAudioContext();
+    this.recorderManager.onStop((res) => {
+      this.processAudio(res.tempFilePath);
+    });
+  },
+  methods: {
+    startRecording() {
+      this.isRecording = true;
+      this.recorderManager.start({
+        format: "mp3",
+        sampleRate: 16e3,
+        numberOfChannels: 1
+      });
+    },
+    stopRecording() {
+      this.isRecording = false;
+      this.recorderManager.stop();
+    },
+    processAudio(filePath) {
+      common_vendor.index.showLoading({
+        title: "处理中..."
+      });
+      this.speechToText(filePath).then((text) => {
+        this.addMessage("user", text);
+        this.callDashScopeAPI(text);
+      }).catch((err) => {
+        common_vendor.index.hideLoading();
+        common_vendor.index.showToast({
+          title: "语音识别失败",
+          icon: "none"
+        });
+        console.error(err);
+      });
+    },
+    speechToText(filePath) {
+      return new Promise((resolve) => {
+        console.log("录音文件路径:", filePath);
+        setTimeout(() => {
+          resolve("你好,我需要帮助");
+        }, 500);
+      });
+    },
+    callDashScopeAPI(userInput) {
+      const requestBody = {
+        model: "qwen-plus",
+        messages: [
+          {
+            role: "system",
+            content: "You are a helpful assistant."
+          },
+          {
+            role: "user",
+            content: userInput
+          }
+        ]
+      };
+      common_vendor.index.request({
+        url: this.apiUrl,
+        method: "POST",
+        header: {
+          "Content-Type": "application/json",
+          "Authorization": "Bearer YOUR_API_KEY"
+          // 替换为您的API密钥
+        },
+        data: requestBody,
+        success: (res) => {
+          common_vendor.index.hideLoading();
+          if (res.statusCode === 200 && res.data && res.data.choices && res.data.choices.length > 0) {
+            const assistantMessage = res.data.choices[0].message.content;
+            this.addMessage("assistant", assistantMessage);
+            this.textToSpeech(assistantMessage);
+          } else {
+            common_vendor.index.showToast({
+              title: "获取回复失败",
+              icon: "none"
+            });
+            console.error("API响应错误:", res);
+          }
+        },
+        fail: (err) => {
+          common_vendor.index.hideLoading();
+          common_vendor.index.showToast({
+            title: "网络请求失败",
+            icon: "none"
+          });
+          console.error(err);
+        }
+      });
+    },
+    addMessage(role, content) {
+      this.messages.push({ role, content });
+      this.$nextTick(() => {
+        this.scrollTop = 9999;
+      });
+    },
+    textToSpeech(text) {
+      common_vendor.index.request({
+        url: "您的文本转语音服务URL",
+        method: "POST",
+        data: { text },
+        success: (res) => {
+          if (res.statusCode === 200 && res.data && res.data.audio_url) {
+            this.innerAudioContext.src = res.data.audio_url;
+            this.innerAudioContext.play();
+          }
+        }
+      });
+    }
+  }
+};
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return {
+    a: common_vendor.f($data.messages, (message, index, i0) => {
+      return {
+        a: common_vendor.t(message.content),
+        b: index,
+        c: common_vendor.n(message.role)
+      };
+    }),
+    b: $data.scrollTop,
+    c: common_vendor.t($data.isRecording ? "松开结束" : "按住说话"),
+    d: $data.isRecording ? 1 : "",
+    e: common_vendor.o((...args) => $options.startRecording && $options.startRecording(...args)),
+    f: common_vendor.o((...args) => $options.stopRecording && $options.stopRecording(...args))
+  };
+}
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
+wx.createPage(MiniProgramPage);

+ 4 - 0
unpackage/dist/dev/mp-weixin/pages/interview/interview.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "",
+  "usingComponents": {}
+}

+ 1 - 0
unpackage/dist/dev/mp-weixin/pages/interview/interview.wxml

@@ -0,0 +1 @@
+<view class="container"><view class="header"><text class="title">语音对话</text></view><view class="chat-container"><scroll-view scroll-y class="chat-messages" scroll-top="{{b}}"><view wx:for="{{a}}" wx:for-item="message" wx:key="b" class="{{['message', message.c]}}"><text>{{message.a}}</text></view></scroll-view></view><view class="controls"><button class="{{['voice-btn', d && 'recording']}}" bindtouchstart="{{e}}" bindtouchend="{{f}}"><text>{{c}}</text></button></view></view>

+ 60 - 0
unpackage/dist/dev/mp-weixin/pages/interview/interview.wxss

@@ -0,0 +1,60 @@
+
+.container {
+	display: flex;
+	flex-direction: column;
+	height: 100vh;
+	background-color: #f5f5f5;
+}
+.header {
+	padding: 20rpx;
+	background-color: #007AFF;
+	text-align: center;
+}
+.title {
+	color: #ffffff;
+	font-size: 36rpx;
+	font-weight: bold;
+}
+.chat-container {
+	flex: 1;
+	padding: 20rpx;
+	overflow: hidden;
+}
+.chat-messages {
+	height: 100%;
+}
+.message {
+	margin-bottom: 20rpx;
+	padding: 20rpx;
+	border-radius: 10rpx;
+	max-width: 80%;
+	word-break: break-word;
+}
+.message.user {
+	align-self: flex-end;
+	background-color: #007AFF;
+	color: white;
+	margin-left: auto;
+}
+.message.assistant {
+	align-self: flex-start;
+	background-color: #E5E5EA;
+	color: #333;
+}
+.controls {
+	padding: 20rpx;
+	background-color: #ffffff;
+	border-top: 1px solid #e0e0e0;
+}
+.voice-btn {
+	width: 100%;
+	height: 80rpx;
+	line-height: 80rpx;
+	text-align: center;
+	background-color: #007AFF;
+	color: white;
+	border-radius: 40rpx;
+}
+.voice-btn.recording {
+	background-color: #FF3B30;
+}

binární
unpackage/dist/dev/mp-weixin/static/demo.mp4