|
@@ -0,0 +1,701 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
+import { message } from 'ant-design-vue'
|
|
|
+import type { FormInstance } from 'ant-design-vue'
|
|
|
+
|
|
|
+interface CandidateInfo {
|
|
|
+ name: string
|
|
|
+ idNumber: string
|
|
|
+ phoneNumber: string
|
|
|
+ score: number
|
|
|
+ availabilityPeriod: string
|
|
|
+ onboardingTime: string
|
|
|
+ dimensions: {
|
|
|
+ teamwork: string
|
|
|
+ learningAbility: string
|
|
|
+ attention: string
|
|
|
+ workAdaptability: string
|
|
|
+ serviceAwareness: string
|
|
|
+ }
|
|
|
+ dimensionDetails: {
|
|
|
+ teamwork: string
|
|
|
+ learningAbility: string
|
|
|
+ attention: string
|
|
|
+ workAdaptability: string
|
|
|
+ serviceAwareness: string
|
|
|
+ }
|
|
|
+ duvAnalysis: {
|
|
|
+ title: string
|
|
|
+ content: string
|
|
|
+ score: string
|
|
|
+ type: 'positive' | 'negative' | 'neutral'
|
|
|
+ }[]
|
|
|
+ interviewRecord: {
|
|
|
+ question: string
|
|
|
+ answer: string
|
|
|
+ analysis: string
|
|
|
+ score: string
|
|
|
+ videoUrl?: string
|
|
|
+ thumbnail?: string
|
|
|
+ }[]
|
|
|
+ videoRecords: {
|
|
|
+ category: string
|
|
|
+ videos: {
|
|
|
+ url: string
|
|
|
+ thumbnail: string
|
|
|
+ description: string
|
|
|
+ }[]
|
|
|
+ }[]
|
|
|
+ additionalInfo: {
|
|
|
+ idVerification: string
|
|
|
+ phoneVerification: string
|
|
|
+ leftHandGestures: {
|
|
|
+ passed: boolean
|
|
|
+ images: string[]
|
|
|
+ }
|
|
|
+ rightHandGestures: {
|
|
|
+ passed: boolean
|
|
|
+ images: string[]
|
|
|
+ }
|
|
|
+ faceVerification: {
|
|
|
+ passed: boolean
|
|
|
+ images: string[]
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const candidateInfo = ref<CandidateInfo>({
|
|
|
+ name: '孙壁涵',
|
|
|
+ idNumber: '152201199809150015',
|
|
|
+ phoneNumber: '19943770171',
|
|
|
+ score: 71,
|
|
|
+ availabilityPeriod: '全天任意时间段',
|
|
|
+ onboardingTime: '1周内',
|
|
|
+ dimensions: {
|
|
|
+ teamwork: '欠佳',
|
|
|
+ learningAbility: '优秀',
|
|
|
+ attention: '欠佳',
|
|
|
+ workAdaptability: '优秀',
|
|
|
+ serviceAwareness: '中等'
|
|
|
+ },
|
|
|
+ dimensionDetails: {
|
|
|
+ teamwork: '主动交流但缺乏倾听与尊重。',
|
|
|
+ learningAbility: '积极向上,具备优秀的学习意愿。',
|
|
|
+ attention: '对细节处理不够严谨,需提高标准意识。',
|
|
|
+ workAdaptability: '快速适应,表现出强烈的责任心。',
|
|
|
+ serviceAwareness: '关注客户需求,但缺乏提升顾客体验的具体措施。'
|
|
|
+ },
|
|
|
+ duvAnalysis: [
|
|
|
+ {
|
|
|
+ title: '工作经验',
|
|
|
+ content: '有相关工作经验,能够胜任岗位要求',
|
|
|
+ score: '优秀',
|
|
|
+ type: 'positive'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '学历背景',
|
|
|
+ content: '学历符合岗位要求',
|
|
|
+ score: '良好',
|
|
|
+ type: 'positive'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ interviewRecord: [
|
|
|
+ {
|
|
|
+ question: '请问您对餐饮服务工作有什么理解?',
|
|
|
+ answer: '餐饮服务工作需要具备良好的服务意识和沟通能力,要以客户满意为目标,及时响应客户需求,保持微笑和耐心。同时要注意个人卫生和仪容仪表,遵守餐饮服务规范。',
|
|
|
+ analysis: '答案表现出对岗位的基本理解,但缺乏深入思考',
|
|
|
+ score: '中等',
|
|
|
+ videoUrl: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
|
|
|
+ thumbnail: '/images/video-placeholder.jpg'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ question: '您如何处理顾客投诉?',
|
|
|
+ answer: '首先我会认真倾听顾客的意见,表示理解和歉意。然后了解具体情况,及时向主管报告,并根据公司规定提供合适的解决方案。最后会总结经验,避免类似问题再次发生。',
|
|
|
+ analysis: '回答显示了基本的服务意识,但处理方法较为简单',
|
|
|
+ score: '良好',
|
|
|
+ videoUrl: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
|
|
|
+ thumbnail: '/images/video-placeholder.jpg'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ videoRecords: [
|
|
|
+ {
|
|
|
+ category: '服务态度测评',
|
|
|
+ videos: [
|
|
|
+ {
|
|
|
+ url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
|
|
|
+ thumbnail: '/images/thumbnail1.jpg',
|
|
|
+ description: '微笑服务示范'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
|
|
|
+ thumbnail: '/images/thumbnail2.jpg',
|
|
|
+ description: '礼貌用语展示'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ additionalInfo: {
|
|
|
+ idVerification: '通过',
|
|
|
+ phoneVerification: '通过',
|
|
|
+ leftHandGestures: {
|
|
|
+ passed: true,
|
|
|
+ images: [
|
|
|
+ '/images/left-hand-1.jpg',
|
|
|
+ '/images/left-hand-2.jpg',
|
|
|
+ '/images/left-hand-3.jpg'
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ rightHandGestures: {
|
|
|
+ passed: true,
|
|
|
+ images: [
|
|
|
+ '/images/right-hand-1.jpg',
|
|
|
+ '/images/right-hand-2.jpg',
|
|
|
+ '/images/right-hand-3.jpg'
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ faceVerification: {
|
|
|
+ passed: true,
|
|
|
+ images: [
|
|
|
+ '/images/face-1.jpg',
|
|
|
+ '/images/face-2.jpg',
|
|
|
+ '/images/face-3.jpg',
|
|
|
+ '/images/face-4.jpg',
|
|
|
+ '/images/face-5.jpg',
|
|
|
+ '/images/face-6.jpg'
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const apiData = ref<any>(null)
|
|
|
+const loading = ref<boolean>(true)
|
|
|
+const error = ref<string>('')
|
|
|
+
|
|
|
+const fetchApplicationDetail = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const urlParams = new URLSearchParams(window.location.search)
|
|
|
+ const id = urlParams.get('id') || '1'
|
|
|
+ const tenant_id = urlParams.get('tenant_id') || '1'
|
|
|
+ const application_id = urlParams.get('application_id') || '1'
|
|
|
+
|
|
|
+ const response = await fetch(`${import.meta.env.VITE_API_URL}/api/job/application_detail?id=${id}&tenant_id=${tenant_id}&application_id=${application_id}`)
|
|
|
+ const result = await response.json()
|
|
|
+
|
|
|
+ if (result.code === 2000) {
|
|
|
+ apiData.value = result.data
|
|
|
+ updateCandidateInfo(result.data)
|
|
|
+ } else {
|
|
|
+ error.value = result.msg || '获取数据失败'
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('获取申请详情失败:', err)
|
|
|
+ error.value = '获取数据失败,请稍后重试'
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const updateCandidateInfo = (data: any) => {
|
|
|
+ if (!data) return
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ if (application.comprehensive_score !== null) {
|
|
|
+ candidateInfo.value.score = application.comprehensive_score
|
|
|
+ }
|
|
|
+
|
|
|
+ if (application.ai_capability_scores) {
|
|
|
+ const dimensionMapping: Record<string, keyof typeof candidateInfo.value.dimensions> = {
|
|
|
+ '专业性': 'workAdaptability',
|
|
|
+ '沟通能力': 'teamwork',
|
|
|
+ '技术匹配度': 'learningAbility',
|
|
|
+ '解决问题能力': 'attention'
|
|
|
+ }
|
|
|
+
|
|
|
+ Object.entries(application.ai_capability_scores).forEach(([key, value]) => {
|
|
|
+ const mappedKey = dimensionMapping[key]
|
|
|
+ if (mappedKey && typeof value === 'number') {
|
|
|
+ let rating = '中等'
|
|
|
+ if (value >= 80) rating = '优秀'
|
|
|
+ else if (value < 65) rating = '欠佳'
|
|
|
+
|
|
|
+ candidateInfo.value.dimensions[mappedKey] = rating
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (interview_progress && interview_progress.length > 0) {
|
|
|
+ candidateInfo.value.interviewRecord = interview_progress
|
|
|
+ .filter(q => q.video_answer)
|
|
|
+ .map(q => ({
|
|
|
+ question: q.question_text,
|
|
|
+ answer: q.video_answer?.transcript || '未提供回答',
|
|
|
+ analysis: '面试官正在评估中',
|
|
|
+ score: '评估中',
|
|
|
+ videoUrl: q.video_answer?.video_url || '',
|
|
|
+ thumbnail: '/images/video-placeholder.jpg'
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if (posture_photos && posture_photos.length > 0) {
|
|
|
+ const leftHandPhotos = posture_photos.filter(p =>
|
|
|
+ p.description.includes('left_') || p.description.includes('左手')
|
|
|
+ ).map(p => p.photo_url)
|
|
|
+
|
|
|
+ if (leftHandPhotos.length > 0) {
|
|
|
+ candidateInfo.value.additionalInfo.leftHandGestures.images = leftHandPhotos
|
|
|
+ }
|
|
|
+
|
|
|
+ const rightHandPhotos = posture_photos.filter(p =>
|
|
|
+ p.description.includes('right_') || p.description.includes('右手')
|
|
|
+ ).map(p => p.photo_url)
|
|
|
+
|
|
|
+ if (rightHandPhotos.length > 0) {
|
|
|
+ candidateInfo.value.additionalInfo.rightHandGestures.images = rightHandPhotos
|
|
|
+ }
|
|
|
+
|
|
|
+ const facePhotos = posture_photos.filter(p =>
|
|
|
+ !p.description.includes('left_') &&
|
|
|
+ !p.description.includes('right_') &&
|
|
|
+ !p.description.includes('左手') &&
|
|
|
+ !p.description.includes('右手')
|
|
|
+ ).map(p => p.photo_url)
|
|
|
+
|
|
|
+ if (facePhotos.length > 0) {
|
|
|
+ candidateInfo.value.additionalInfo.faceVerification.images = facePhotos
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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'
|
|
|
+ }))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ fetchApplicationDetail()
|
|
|
+})
|
|
|
+
|
|
|
+const evaluationScore = ref<number>(0)
|
|
|
+const evaluationComments = ref<string>('')
|
|
|
+const formRef = ref<FormInstance>()
|
|
|
+
|
|
|
+const handleSubmit = async () => {
|
|
|
+ try {
|
|
|
+ await formRef.value?.validate()
|
|
|
+ message.success('评估已提交')
|
|
|
+ } catch (error) {
|
|
|
+ message.error('请完成所有必填项')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const scrollToTop = () => {
|
|
|
+ window.scrollTo({
|
|
|
+ top: 0,
|
|
|
+ behavior: 'smooth'
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const handleShare = () => {
|
|
|
+ message.success('分享链接已复制')
|
|
|
+}
|
|
|
+
|
|
|
+const handleDownload = () => {
|
|
|
+ message.success('报告下载中...')
|
|
|
+}
|
|
|
+
|
|
|
+const videoStyle = {
|
|
|
+ width: '100%',
|
|
|
+ maxWidth: '240px',
|
|
|
+ aspectRatio: '3/4',
|
|
|
+ margin: '0 auto',
|
|
|
+ background: '#000',
|
|
|
+ borderRadius: '12px',
|
|
|
+ overflow: 'hidden'
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="max-w-4xl mx-auto p-6 relative overflow-y-auto h-full">
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <a-spin :spinning="loading" tip="加载中...">
|
|
|
+ <!-- 页面标题 -->
|
|
|
+ <div class="bg-[#1d1e3a] text-white p-4 rounded-t-lg">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-sm">No.{{ apiData?.applicant?.id || candidateInfo.idNumber }}</div>
|
|
|
+ <div class="text-sm">{{ new Date().toLocaleString() }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
|
|
+ <strong class="font-bold">错误!</strong>
|
|
|
+ <span class="block sm:inline">{{ error }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else class="bg-white rounded-b-lg shadow-lg p-6">
|
|
|
+ <!-- 头部信息 -->
|
|
|
+ <div class="flex justify-between items-center mb-8 pb-4 border-b">
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
+ <div class="w-20 h-20 bg-gray-200 rounded-lg overflow-hidden">
|
|
|
+ <img
|
|
|
+ :src="candidateInfo.additionalInfo.faceVerification.images[0]"
|
|
|
+ alt="头像"
|
|
|
+ class="w-full h-full object-cover"
|
|
|
+ @error="e => e.target.src = '/images/avatar-placeholder.jpg'"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <h1 class="text-2xl font-bold">{{ candidateInfo.name }}</h1>
|
|
|
+ <p class="text-gray-600">{{ candidateInfo.phoneNumber }}</p>
|
|
|
+ <p v-if="apiData?.position" class="text-gray-500 text-sm">
|
|
|
+ 应聘职位: {{ apiData.position.title }} - {{ apiData.position.department }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="text-right">
|
|
|
+ <div class="text-4xl font-bold text-yellow-500">{{ candidateInfo.score }}<span class="text-base ml-1">分</span></div>
|
|
|
+ <div class="text-sm text-yellow-500">
|
|
|
+ {{ candidateInfo.score >= 70 ? '推荐 👍' : candidateInfo.score >= 60 ? '待定 🤔' : '不推荐 👎' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 基本信息 -->
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
|
|
+ <div>
|
|
|
+ <label class="text-gray-600">身份证号</label>
|
|
|
+ <div>{{ candidateInfo.idNumber }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="text-gray-600">入职之后,您缺岗的班次时间段是?</label>
|
|
|
+ <div>{{ candidateInfo.availabilityPeriod }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="text-gray-600">请问您通过面试之后多久可以入职?</label>
|
|
|
+ <div>{{ candidateInfo.onboardingTime }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- AI维度分析 -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-6">1. AI维度分析</h2>
|
|
|
+ <div class="space-y-6">
|
|
|
+ <div v-for="(value, key) in candidateInfo.dimensions" :key="key" class="border-b pb-4">
|
|
|
+ <div class="flex items-center mb-2">
|
|
|
+ <span class="text-gray-600 w-32">{{ {
|
|
|
+ teamwork: '团队合作能力',
|
|
|
+ learningAbility: '学习能力',
|
|
|
+ attention: '细致严谨',
|
|
|
+ workAdaptability: '工作适应性',
|
|
|
+ serviceAwareness: '服务意识'
|
|
|
+ }[key] }}</span>
|
|
|
+ <span class="ml-4" :class="{
|
|
|
+ 'text-red-500': value === '欠佳',
|
|
|
+ 'text-green-500': value === '优秀',
|
|
|
+ 'text-yellow-500': value === '中等'
|
|
|
+ }">{{ value }}</span>
|
|
|
+ </div>
|
|
|
+ <p class="text-gray-600 text-sm">{{ candidateInfo.dimensionDetails[key] }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- DUV分析评估 -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-6">2. DUV分析评估</h2>
|
|
|
+ <div class="space-y-4">
|
|
|
+ <div v-for="(item, index) in candidateInfo.duvAnalysis" :key="index" class="border-b pb-4">
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
+ <span class="text-gray-600">{{ item.title }}</span>
|
|
|
+ <span :class="{
|
|
|
+ 'text-green-500': item.type === 'positive',
|
|
|
+ 'text-red-500': item.type === 'negative',
|
|
|
+ 'text-yellow-500': item.type === 'neutral'
|
|
|
+ }">{{ item.score }}</span>
|
|
|
+ </div>
|
|
|
+ <p class="text-gray-600 text-sm">{{ item.content }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 面试记录 -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-6">3. 面试记录</h2>
|
|
|
+ <div class="space-y-6">
|
|
|
+ <div v-for="(record, index) in candidateInfo.interviewRecord" :key="index" class="border-b pb-4">
|
|
|
+ <div class="mb-2">
|
|
|
+ <h3 class="font-semibold">问题:{{ record.question }}</h3>
|
|
|
+ <p class="text-gray-600 mt-2">答案:{{ record.answer }}</p>
|
|
|
+ <p class="text-gray-500 mt-2 text-sm">分析:{{ record.analysis }}</p>
|
|
|
+
|
|
|
+ <!-- 视频回放 -->
|
|
|
+ <div v-if="record.videoUrl" class="mt-4">
|
|
|
+ <div class="video-container">
|
|
|
+ <video
|
|
|
+ class="w-full h-full object-cover"
|
|
|
+ controls
|
|
|
+ :src="record.videoUrl"
|
|
|
+ preload="none"
|
|
|
+ :poster="record.thumbnail"
|
|
|
+ >
|
|
|
+ <source :src="record.videoUrl" type="video/mp4">
|
|
|
+ 您的浏览器不支持视频播放。
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mt-2 flex justify-end">
|
|
|
+ <span class="text-blue-500">得分:{{ record.score }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 视频记录 -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-6">4. 视频记录</h2>
|
|
|
+ <div class="space-y-8">
|
|
|
+ <div v-for="(category, index) in candidateInfo.videoRecords" :key="index">
|
|
|
+ <h3 class="text-lg font-semibold mb-4">{{ category.category }}</h3>
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
+ <div v-for="(video, vIndex) in category.videos" :key="vIndex" class="bg-gray-50 rounded-lg p-4">
|
|
|
+ <div class="video-container">
|
|
|
+ <video
|
|
|
+ class="w-full h-full object-cover"
|
|
|
+ controls
|
|
|
+ :src="video.url"
|
|
|
+ preload="none"
|
|
|
+ :poster="video.thumbnail"
|
|
|
+ >
|
|
|
+ <source :src="video.url" type="video/mp4">
|
|
|
+ 您的浏览器不支持视频播放。
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+ <p class="text-sm text-gray-600 mt-2">{{ video.description }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 其他信息 -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-6">5. 其他信息</h2>
|
|
|
+ <div class="space-y-6">
|
|
|
+ <!-- 验证状态 -->
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
|
+ <div class="flex items-center space-x-2">
|
|
|
+ <span class="text-gray-600">身份证验证:</span>
|
|
|
+ <span class="text-green-500">{{ candidateInfo.additionalInfo.idVerification }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center space-x-2">
|
|
|
+ <span class="text-gray-600">手机号验证:</span>
|
|
|
+ <span class="text-green-500">{{ candidateInfo.additionalInfo.phoneVerification }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 手势验证 -->
|
|
|
+ <div class="mb-6">
|
|
|
+ <h3 class="font-semibold mb-4">手势验证</h3>
|
|
|
+ <div class="grid grid-cols-2 gap-8">
|
|
|
+ <!-- 左手手势 -->
|
|
|
+ <div>
|
|
|
+ <h4 class="text-sm font-medium mb-2">左手手势验证</h4>
|
|
|
+ <div class="grid grid-cols-3 gap-2">
|
|
|
+ <div v-for="(image, index) in candidateInfo.additionalInfo.leftHandGestures.images" :key="index"
|
|
|
+ class="aspect-square rounded-lg overflow-hidden bg-gray-100">
|
|
|
+ <img :src="image" alt="左手手势" class="w-full h-full object-cover">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 右手手势 -->
|
|
|
+ <div>
|
|
|
+ <h4 class="text-sm font-medium mb-2">右手手势验证</h4>
|
|
|
+ <div class="grid grid-cols-3 gap-2">
|
|
|
+ <div v-for="(image, index) in candidateInfo.additionalInfo.rightHandGestures.images" :key="index"
|
|
|
+ class="aspect-square rounded-lg overflow-hidden bg-gray-100">
|
|
|
+ <img :src="image" alt="右手手势" class="w-full h-full object-cover">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 人脸验证 -->
|
|
|
+ <div>
|
|
|
+ <h3 class="font-semibold mb-4">人脸验证</h3>
|
|
|
+ <div class="grid grid-cols-3 md:grid-cols-6 gap-2">
|
|
|
+ <div v-for="(image, index) in candidateInfo.additionalInfo.faceVerification.images" :key="index"
|
|
|
+ class="aspect-square rounded-lg overflow-hidden bg-gray-100">
|
|
|
+ <img :src="image" alt="人脸验证" class="w-full h-full object-cover">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 评估表单 -->
|
|
|
+ <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
|
|
|
+ label="请为候选人打分"
|
|
|
+ name="score"
|
|
|
+ :rules="[{ required: true, message: '请选择评分' }]"
|
|
|
+ >
|
|
|
+ <a-rate
|
|
|
+ v-model:value="evaluationScore"
|
|
|
+ :count="5"
|
|
|
+ allow-half
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 评价 -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-4">评价意见</h2>
|
|
|
+ <a-form-item
|
|
|
+ label="请输入评价意见"
|
|
|
+ name="comments"
|
|
|
+ :rules="[{ required: true, message: '请输入评价意见' }]"
|
|
|
+ >
|
|
|
+ <a-textarea
|
|
|
+ v-model:value="evaluationComments"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入您的评价意见..."
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 提交按钮 -->
|
|
|
+ <div class="flex justify-end">
|
|
|
+ <a-button type="primary" @click="handleSubmit">
|
|
|
+ 提交评估
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+ </a-form>
|
|
|
+ </div>
|
|
|
+ </a-spin>
|
|
|
+
|
|
|
+ <!-- 悬浮按钮 -->
|
|
|
+ <div class="fixed right-8 bottom-24 flex flex-col space-y-4">
|
|
|
+ <a-button
|
|
|
+ type="primary"
|
|
|
+ shape="circle"
|
|
|
+ class="flex items-center justify-center"
|
|
|
+ @click="handleShare"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path d="M15 8a3 3 0 10-2.977-2.63l-4.94 2.47a3 3 0 100 4.319l4.94 2.47a3 3 0 10.895-1.789l-4.94-2.47a3.027 3.027 0 000-.74l4.94-2.47C13.456 7.68 14.19 8 15 8z" />
|
|
|
+ </svg>
|
|
|
+ </template>
|
|
|
+ </a-button>
|
|
|
+ <a-button
|
|
|
+ type="primary"
|
|
|
+ shape="circle"
|
|
|
+ class="flex items-center justify-center"
|
|
|
+ @click="handleDownload"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </template>
|
|
|
+ </a-button>
|
|
|
+ <a-button
|
|
|
+ type="primary"
|
|
|
+ shape="circle"
|
|
|
+ class="flex items-center justify-center"
|
|
|
+ @click="scrollToTop"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
+ <path fill-rule="evenodd" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z" clip-rule="evenodd" />
|
|
|
+ </svg>
|
|
|
+ </template>
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.video-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 240px;
|
|
|
+ aspect-ratio: 3/4;
|
|
|
+ margin: 0 auto;
|
|
|
+ background: #000;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.ant-form-item {
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.fixed {
|
|
|
+ z-index: 100;
|
|
|
+}
|
|
|
+
|
|
|
+.ant-btn-circle {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ padding: 0;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 修改以下样式以确保页面可以滚动 */
|
|
|
+html, body {
|
|
|
+ height: 100%;
|
|
|
+ min-height: 100%;
|
|
|
+ overflow-y: auto !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保主容器不会限制滚动 */
|
|
|
+.max-w-4xl {
|
|
|
+ position: relative;
|
|
|
+ overflow: visible;
|
|
|
+}
|
|
|
+
|
|
|
+/* 添加以下样式确保内容可以正常滚动 */
|
|
|
+body {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+#app {
|
|
|
+ height: auto;
|
|
|
+ min-height: 100vh;
|
|
|
+ overflow: visible;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .max-w-4xl {
|
|
|
+ padding: 1rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .grid-cols-3 {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fixed {
|
|
|
+ right: 1rem;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|