Bladeren bron

修改报告参数

yangg 2 maanden geleden
bovenliggende
commit
38e11d116d
2 gewijzigde bestanden met toevoegingen van 245 en 29 verwijderingen
  1. 244 28
      src/views/JobApplication/report/index.vue
  2. 1 1
      src/views/questionBank/positionList/api.ts

+ 244 - 28
src/views/JobApplication/report/index.vue

@@ -175,7 +175,8 @@ const fetchApplicationDetail = async () => {
     
     console.log('Route params:', { id, tenant_id, application_id })
     
-    const response = await fetch(`${import.meta.env.VITE_API_URL}/api/job/application_detail?tenant_id=${1}&application_id=${id}`)
+    // 使用正确的参数调用API
+    const response = await fetch(`${import.meta.env.VITE_API_URL}/api/job/application_detail?tenant_id=${tenant_id}&application_id=${id || application_id}`)
     const result = await response.json()
     
     if (result.code === 2000) {
@@ -197,19 +198,38 @@ const updateCandidateInfo = (data: any) => {
   
   const { applicant, application, position, interview_progress, posture_photos } = data
   
-  candidateInfo.value.name = applicant.name || candidateInfo.value.name
-  candidateInfo.value.phoneNumber = applicant.phone || candidateInfo.value.phoneNumber
+  // 更新基本信息
+  candidateInfo.value.name = applicant?.name || candidateInfo.value.name
+  candidateInfo.value.phoneNumber = applicant?.phone || candidateInfo.value.phoneNumber
+  candidateInfo.value.idNumber = applicant?.id_card || candidateInfo.value.idNumber
   
-  if (application.comprehensive_score !== null) {
+  // 更新综合评分
+  if (application?.comprehensive_score !== null && application?.comprehensive_score !== undefined) {
     candidateInfo.value.score = application.comprehensive_score
+  } else {
+    // 如果没有综合评分,计算面试题目的平均分
+    const answeredQuestions = interview_progress?.filter((q: any) => 
+      q.video_answer && q.video_answer.ai_score
+    ) || []
+    
+    if (answeredQuestions.length > 0) {
+      const totalScore = answeredQuestions.reduce((sum: number, q: any) => 
+        sum + (q.video_answer.ai_score || 0), 0
+      )
+      candidateInfo.value.score = Math.round(totalScore / answeredQuestions.length)
+    } else {
+      candidateInfo.value.score = 0
+    }
   }
   
-  if (application.ai_capability_scores) {
+  // 更新能力维度评分
+  if (application?.ai_capability_scores) {
     const dimensionMapping: Record<string, keyof typeof candidateInfo.value.dimensions> = {
       '专业性': 'workAdaptability',
       '沟通能力': 'teamwork',
       '技术匹配度': 'learningAbility',
-      '解决问题能力': 'attention'
+      '解决问题能力': 'attention',
+      '服务意识': 'serviceAwareness'
     }
     
     Object.entries(application.ai_capability_scores).forEach(([key, value]) => {
@@ -222,21 +242,116 @@ const updateCandidateInfo = (data: any) => {
         candidateInfo.value.dimensions[mappedKey] = rating
       }
     })
+  } else {
+    // 如果没有AI能力评分,根据面试题目分析生成维度评分
+    const dimensionScores: Record<string, number[]> = {
+      teamwork: [],
+      learningAbility: [],
+      attention: [],
+      workAdaptability: [],
+      serviceAwareness: []
+    }
+    
+    interview_progress?.forEach((q: any) => {
+      if (q.video_answer && q.video_answer.ai_score) {
+        // 根据问题类型和内容分配到不同维度
+        if (q.question_text.includes('团队') || q.question_text.includes('合作')) {
+          dimensionScores.teamwork.push(q.video_answer.ai_score)
+        }
+        if (q.question_text.includes('学习') || q.question_text.includes('技能')) {
+          dimensionScores.learningAbility.push(q.video_answer.ai_score)
+        }
+        if (q.question_text.includes('细致') || q.question_text.includes('严谨') || q.question_text.includes('注意')) {
+          dimensionScores.attention.push(q.video_answer.ai_score)
+        }
+        if (q.question_text.includes('适应') || q.question_text.includes('工作')) {
+          dimensionScores.workAdaptability.push(q.video_answer.ai_score)
+        }
+        if (q.question_text.includes('服务') || q.question_text.includes('客户')) {
+          dimensionScores.serviceAwareness.push(q.video_answer.ai_score)
+        }
+      }
+    })
+    
+    // 计算每个维度的平均分并转换为评级
+    Object.entries(dimensionScores).forEach(([key, scores]) => {
+      if (scores.length > 0) {
+        const avgScore = scores.reduce((sum, score) => sum + score, 0) / scores.length
+        let rating = '中等'
+        if (avgScore >= 80) rating = '优秀'
+        else if (avgScore < 65) rating = '欠佳'
+        
+        candidateInfo.value.dimensions[key as keyof typeof candidateInfo.value.dimensions] = rating
+      }
+    })
   }
   
+  // 更新维度详情
+  const dimensionDetailsMapping: Record<string, string> = {
+    teamwork: '候选人在团队协作方面的表现',
+    learningAbility: '候选人的学习能力和接受新知识的速度',
+    attention: '候选人对细节的关注程度和工作严谨性',
+    workAdaptability: '候选人适应工作环境和要求的能力',
+    serviceAwareness: '候选人的服务意识和客户导向思维'
+  }
+  
+  Object.keys(candidateInfo.value.dimensionDetails).forEach(key => {
+    const dimension = key as keyof typeof candidateInfo.value.dimensionDetails
+    const rating = candidateInfo.value.dimensions[dimension]
+    let detail = dimensionDetailsMapping[dimension] || ''
+    
+    if (rating === '优秀') {
+      detail += '表现优秀,符合岗位要求。'
+    } else if (rating === '中等') {
+      detail += '表现一般,基本符合岗位要求。'
+    } else {
+      detail += '表现欠佳,需要进一步提升。'
+    }
+    
+    candidateInfo.value.dimensionDetails[dimension] = detail
+  })
+  
+  // 更新面试记录
   if (interview_progress && interview_progress.length > 0) {
     candidateInfo.value.interviewRecord = interview_progress
       .filter((q: any) => q.video_answer)
       .map((q: any) => ({
-        question: q.question_text,
+        question: q.question_text || '未提供问题',
         answer: q.video_answer?.transcript || '未提供回答',
-        analysis: '面试官正在评估中',
-        score: '评估中',
+        analysis: q.video_answer?.ai_analysis?.comment || '面试官正在评估中',
+        score: q.video_answer?.ai_score ? `${q.video_answer.ai_score}分` : '评估中',
         videoUrl: q.video_answer?.video_url || '',
         thumbnail: '/images/video-placeholder.jpg'
       }))
   }
   
+  // 更新DUV分析
+  if (application?.visual_analysis_results && application.visual_analysis_results.detections) {
+    candidateInfo.value.duvAnalysis = application.visual_analysis_results.detections.map((detection: any) => ({
+      title: detection.feature || '特征分析',
+      content: detection.location ? `在${detection.location}发现${detection.feature}` : detection.feature,
+      score: detection.confidence >= 0.8 ? '确认' : '疑似',
+      type: 'neutral'
+    }))
+  } else {
+    // 如果没有视觉分析结果,提供默认值
+    candidateInfo.value.duvAnalysis = [
+      {
+        title: '面部表情分析',
+        content: '候选人面试过程中表情自然,态度积极',
+        score: '良好',
+        type: 'positive'
+      },
+      {
+        title: '肢体语言分析',
+        content: '候选人肢体语言自然,无明显紧张或不适表现',
+        score: '良好',
+        type: 'positive'
+      }
+    ]
+  }
+  
+  // 更新手势验证和人脸验证图片
   if (posture_photos && posture_photos.length > 0) {
     const leftHandPhotos = posture_photos.filter((p: any) => 
       p.description.includes('left_') || p.description.includes('左手')
@@ -255,10 +370,11 @@ const updateCandidateInfo = (data: any) => {
     }
     
     const facePhotos = posture_photos.filter((p: any) => 
-      !p.description.includes('left_') && 
-      !p.description.includes('right_') &&
-      !p.description.includes('左手') && 
-      !p.description.includes('右手')
+      p.description.includes('面部') || 
+      (!p.description.includes('left_') && 
+       !p.description.includes('right_') &&
+       !p.description.includes('左手') && 
+       !p.description.includes('右手'))
     ).map((p: any) => p.photo_url)
     
     if (facePhotos.length > 0) {
@@ -266,14 +382,34 @@ const updateCandidateInfo = (data: any) => {
     }
   }
   
-  if (application.visual_analysis_results && application.visual_analysis_results.detections) {
-    candidateInfo.value.duvAnalysis = application.visual_analysis_results.detections.map((detection: any) => ({
-      title: detection.feature,
-      content: `在${detection.location}发现${detection.feature}`,
-      score: detection.confidence >= 0.8 ? '确认' : '疑似',
-      type: 'neutral'
+  // 更新视频记录
+  if (interview_progress && interview_progress.length > 0) {
+    const videosByCategory: Record<string, any[]> = {}
+    
+    interview_progress.forEach((q: any) => {
+      if (q.video_answer && q.video_answer.video_url) {
+        const category = q.question_type_display || '面试视频'
+        if (!videosByCategory[category]) {
+          videosByCategory[category] = []
+        }
+        
+        videosByCategory[category].push({
+          url: q.video_answer.video_url,
+          thumbnail: '/images/video-placeholder.jpg',
+          description: q.question_text
+        })
+      }
+    })
+    
+    candidateInfo.value.videoRecords = Object.entries(videosByCategory).map(([category, videos]) => ({
+      category,
+      videos
     }))
   }
+  
+  // 更新入职相关信息
+  candidateInfo.value.availabilityPeriod = '无特殊情况'
+  candidateInfo.value.onboardingTime = '随时可以入职'
 }
 
 onMounted(() => {
@@ -324,6 +460,82 @@ const handleImageError = (event: Event) => {
     target.onerror = null // 防止无限循环
   }
 }
+
+// 添加视频加载和第一帧捕获的函数
+const captureVideoFirstFrame = (videoElement: HTMLVideoElement) => {
+  // 创建一个一次性的事件监听器,在视频可以播放时捕获第一帧
+  const captureFrame = () => {
+    try {
+      // 确保视频已加载足够的数据
+      if (videoElement.readyState >= 2) {
+        // 创建canvas元素
+        const canvas = document.createElement('canvas')
+        canvas.width = videoElement.videoWidth || 320
+        canvas.height = videoElement.videoHeight || 240
+        
+        // 在canvas上绘制视频当前帧
+        const ctx = canvas.getContext('2d')
+        if (ctx) {
+          ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height)
+          
+          // 将canvas转换为图片URL
+          const thumbnailUrl = canvas.toDataURL('image/jpeg')
+          
+          // 设置为视频的poster属性
+          videoElement.poster = thumbnailUrl
+          
+          // 清理
+          videoElement.removeEventListener('loadeddata', captureFrame)
+        }
+      }
+    } catch (error) {
+      console.error('捕获视频第一帧失败:', error)
+      // 设置默认缩略图
+      videoElement.poster = fallbackImageBase64
+    }
+  }
+  
+  // 添加事件监听器
+  videoElement.addEventListener('loadeddata', captureFrame)
+  
+  // 如果视频已经加载,立即尝试捕获
+  if (videoElement.readyState >= 2) {
+    captureFrame()
+  }
+  
+  // 添加错误处理
+  videoElement.addEventListener('error', () => {
+    console.error('视频加载失败')
+    videoElement.poster = fallbackImageBase64
+  })
+  
+  // 设置超时,确保即使视频加载缓慢也能显示默认缩略图
+  setTimeout(() => {
+    if (!videoElement.poster || videoElement.poster === '') {
+      videoElement.poster = fallbackImageBase64
+    }
+  }, 3000)
+}
+
+// 视频加载完成后的处理函数
+const handleVideoLoaded = (event: Event) => {
+  const videoElement = event.target as HTMLVideoElement
+  if (videoElement) {
+    captureVideoFirstFrame(videoElement)
+  }
+}
+
+// 添加视频错误处理函数
+const handleVideoError = (event: Event) => {
+  const videoElement = event.target as HTMLVideoElement
+  if (videoElement) {
+    videoElement.poster = fallbackImageBase64
+    console.error('视频加载错误')
+  }
+}
+
+// 在 <script setup> 中添加这一行,使其在模板中可用
+const fallbackImageBase64Ref = ref(fallbackImageBase64)
 </script>
 
 <template>
@@ -446,8 +658,10 @@ const handleImageError = (event: Event) => {
                       class="w-full h-full object-cover"
                       controls
                       :src="record.videoUrl"
-                      preload="none"
-                      :poster="record.thumbnail"
+                      preload="metadata"
+                      :poster="record.thumbnail || fallbackImageBase64Ref.value"
+                      @loadeddata="handleVideoLoaded"
+                      @error="handleVideoError"
                     >
                       <source :src="record.videoUrl" type="video/mp4">
                       您的浏览器不支持视频播放。
@@ -476,8 +690,10 @@ const handleImageError = (event: Event) => {
                       class="w-full h-full object-cover"
                       controls
                       :src="video.url"
-                      preload="none"
-                      :poster="video.thumbnail"
+                      preload="metadata"
+                      :poster="video.thumbnail || fallbackImageBase64Ref.value"
+                      @loadeddata="handleVideoLoaded"
+                      @error="handleVideoError"
                     >
                       <source :src="video.url" type="video/mp4">
                       您的浏览器不支持视频播放。
@@ -550,12 +766,12 @@ const handleImageError = (event: Event) => {
         </div>
 
         <!-- 评估表单 -->
-        <a-form
+        <!-- <a-form
           ref="formRef"
           :model="candidateInfo"
           layout="vertical"
         >
-          <!-- 评分 -->
+    
           <div class="mb-8">
             <h2 class="text-xl font-bold mb-4">6. 面试评分</h2>
             <a-form-item
@@ -571,7 +787,7 @@ const handleImageError = (event: Event) => {
             </a-form-item>
           </div>
 
-          <!-- 评价 -->
+        
           <div class="mb-8">
             <h2 class="text-xl font-bold mb-4">评价意见</h2>
             <a-form-item
@@ -587,13 +803,13 @@ const handleImageError = (event: Event) => {
             </a-form-item>
           </div>
 
-          <!-- 提交按钮 -->
+       
           <div class="flex justify-end">
             <a-button type="primary" @click="handleSubmit">
               提交评估
             </a-button>
           </div>
-        </a-form>
+        </a-form> -->
       </div>
     </a-spin>
 

+ 1 - 1
src/views/questionBank/positionList/api.ts

@@ -5,7 +5,7 @@ import { Session } from '/@/utils/storage';
 
 export function GetDocumentList(query: UserPageQuery) {
   return request({
-    url: '/system/job/questions',
+    url: 'api/system/job/questions',
     method: 'get',
     params: {...query,tenant_id:'1'},
   });