Browse Source

修改内容

yangg 2 tháng trước cách đây
mục cha
commit
58bcfaba8f

+ 7 - 0
pages.json

@@ -45,6 +45,13 @@
 		{
 			"path" : "pages/interview/interview",
 			"style" : 
+			{
+				"navigationBarTitleText" : "手部照片采集"
+			}
+		},
+		{
+			"path" : "pages/ResumeEvaluation/ResumeEvaluation",
+			"style" : 
 			{
 				"navigationBarTitleText" : ""
 			}

+ 735 - 0
pages/ResumeEvaluation/ResumeEvaluation.vue

@@ -0,0 +1,735 @@
+<template>
+	<view class="max-w-4xl mx-auto p-6 relative">
+		<!-- 页面标题 -->
+		<view class="bg-[#1d1e3a] text-white p-4 rounded-t-lg">
+			<view class="flex justify-between items-center">
+				<view class="text-sm">No.{{ candidateInfo.idNumber }}</view>
+				<view class="text-sm">2025-03-07 14:42:14</view>
+			</view>
+		</view>
+
+		<view class="bg-white rounded-b-lg shadow-lg p-6">
+			<!-- 头部信息 -->
+			<view class="flex justify-between items-center mb-8 pb-4 border-b">
+				<view class="flex items-center space-x-4">
+					<view class="w-20 h-20 bg-gray-200 rounded-lg overflow-hidden">
+						<image :src="candidateInfo.additionalInfo.faceVerification.images[0]" mode="aspectFill" class="w-full h-full"></image>
+					</view>
+					<view>
+						<text class="text-2xl font-bold">{{ candidateInfo.name }}</text>
+						<text class="text-gray-600 block">{{ candidateInfo.phoneNumber }}</text>
+					</view>
+				</view>
+				<view class="text-right">
+					<view class="text-4xl font-bold text-yellow-500">{{ candidateInfo.score }}<text class="text-base ml-1">分</text></view>
+					<view class="text-sm text-yellow-500">推荐 👍</view>
+				</view>
+			</view>
+
+			<!-- 基本信息 -->
+			<view class="grid grid-cols-1 gap-4 mb-8">
+				<view>
+					<text class="text-gray-600 block">身份证号</text>
+					<text>{{ candidateInfo.idNumber }}</text>
+				</view>
+				<view>
+					<text class="text-gray-600 block">入职之后,您缺岗的班次时间段是?</text>
+					<text>{{ candidateInfo.availabilityPeriod }}</text>
+				</view>
+				<view>
+					<text class="text-gray-600 block">请问您通过面试之后多久可以入职?</text>
+					<text>{{ candidateInfo.onboardingTime }}</text>
+				</view>
+			</view>
+
+			<!-- AI维度分析 -->
+			<view class="mb-8">
+				<text class="text-xl font-bold mb-6 block">1. AI维度分析</text>
+				<view class="space-y-6">
+					<view v-for="(value, key) in candidateInfo.dimensions" :key="key" class="border-b pb-4">
+						<view class="flex items-center mb-2">
+							<text class="text-gray-600 w-32">{{ dimensionLabels[key] }}</text>
+							<text class="ml-4" :class="{
+								'text-red-500': value === '欠佳',
+								'text-green-500': value === '优秀',
+								'text-yellow-500': value === '中等'
+							}">{{ value }}</text>
+						</view>
+						<text class="text-gray-600 text-sm block">{{ candidateInfo.dimensionDetails[key] }}</text>
+					</view>
+				</view>
+			</view>
+
+			<!-- DUV分析评估 -->
+			<view class="mb-8">
+				<text class="text-xl font-bold mb-6 block">2. DUV分析评估</text>
+				<view class="space-y-4">
+					<view v-for="(item, index) in candidateInfo.duvAnalysis" :key="index" class="border-b pb-4">
+						<view class="flex items-center justify-between mb-2">
+							<text class="text-gray-600">{{ item.title }}</text>
+							<text :class="{
+								'text-green-500': item.type === 'positive',
+								'text-red-500': item.type === 'negative',
+								'text-yellow-500': item.type === 'neutral'
+							}">{{ item.score }}</text>
+						</view>
+						<text class="text-gray-600 text-sm block">{{ item.content }}</text>
+					</view>
+				</view>
+			</view>
+
+			<!-- 面试记录 -->
+			<view class="mb-8">
+				<text class="text-xl font-bold mb-6 block">3. 面试记录</text>
+				<view class="space-y-6">
+					<view v-for="(record, index) in candidateInfo.interviewRecord" :key="index" class="border-b pb-4">
+						<view class="mb-2">
+							<text class="font-semibold block">问题:{{ record.question }}</text>
+							<text class="text-gray-600 mt-2 block">答案:{{ record.answer }}</text>
+							<text class="text-gray-500 mt-2 text-sm block">分析:{{ record.analysis }}</text>
+							
+							<!-- 视频回放 -->
+							<view v-if="record.videoUrl" class="mt-4">
+								<view class="video-container">
+									<video
+										class="w-full h-full"
+										:src="record.videoUrl"
+										:poster="record.thumbnail"
+										controls
+									></video>
+								</view>
+							</view>
+							
+							<view class="mt-2 flex justify-end">
+								<text class="text-blue-500">得分:{{ record.score }}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 视频记录 -->
+			<view class="mb-8">
+				<text class="text-xl font-bold mb-6 block">4. 视频记录</text>
+				<view class="space-y-8">
+					<view v-for="(category, index) in candidateInfo.videoRecords" :key="index">
+						<text class="text-lg font-semibold mb-4 block">{{ category.category }}</text>
+						<view class="grid grid-cols-1 gap-4">
+							<view v-for="(video, vIndex) in category.videos" :key="vIndex" class="bg-gray-50 rounded-lg p-4">
+								<view class="video-container">
+									<video
+										class="w-full h-full"
+										:src="video.url"
+										:poster="video.thumbnail"
+										controls
+									></video>
+								</view>
+								<text class="text-sm text-gray-600 mt-2 block">{{ video.description }}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 其他信息 -->
+			<view class="mb-8">
+				<text class="text-xl font-bold mb-6 block">5. 其他信息</text>
+				<view class="space-y-6">
+					<!-- 验证状态 -->
+					<view class="grid grid-cols-1 gap-4 mb-4">
+						<view class="flex items-center space-x-2">
+							<text class="text-gray-600">身份证验证:</text>
+							<text class="text-green-500">{{ candidateInfo.additionalInfo.idVerification }}</text>
+						</view>
+						<view class="flex items-center space-x-2">
+							<text class="text-gray-600">手机号验证:</text>
+							<text class="text-green-500">{{ candidateInfo.additionalInfo.phoneVerification }}</text>
+						</view>
+					</view>
+
+					<!-- 手势验证 -->
+					<view class="mb-6">
+						<text class="font-semibold mb-4 block">手势验证</text>
+						<view class="grid grid-cols-2 gap-8">
+							<!-- 左手手势 -->
+							<view>
+								<text class="text-sm font-medium mb-2 block">左手手势验证</text>
+								<view class="grid grid-cols-3 gap-2">
+									<view v-for="(image, index) in candidateInfo.additionalInfo.leftHandGestures.images" :key="index"
+										class="aspect-square rounded-lg overflow-hidden bg-gray-100">
+										<image :src="image" mode="aspectFill" class="w-full h-full"></image>
+									</view>
+								</view>
+							</view>
+							<!-- 右手手势 -->
+							<view>
+								<text class="text-sm font-medium mb-2 block">右手手势验证</text>
+								<view class="grid grid-cols-3 gap-2">
+									<view v-for="(image, index) in candidateInfo.additionalInfo.rightHandGestures.images" :key="index"
+										class="aspect-square rounded-lg overflow-hidden bg-gray-100">
+										<image :src="image" mode="aspectFill" class="w-full h-full"></image>
+									</view>
+								</view>
+							</view>
+						</view>
+					</view>
+
+					<!-- 人脸验证 -->
+					<view>
+						<text class="font-semibold mb-4 block">人脸验证</text>
+						<view class="grid grid-cols-3 gap-2">
+							<view v-for="(image, index) in candidateInfo.additionalInfo.faceVerification.images" :key="index"
+								class="aspect-square rounded-lg overflow-hidden bg-gray-100">
+								<image :src="image" mode="aspectFill" class="w-full h-full"></image>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 评估表单 -->
+			<view>
+				<!-- 评分 -->
+				<view class="mb-8">
+					<text class="text-xl font-bold mb-4 block">6. 面试评分</text>
+					<view class="form-item">
+						<text class="block mb-2">请为候选人打分</text>
+						<uni-rate v-model="evaluationScore" :max="5" allow-half />
+					</view>
+				</view>
+
+				<!-- 评价 -->
+				<view class="mb-8">
+					<text class="text-xl font-bold mb-4 block">评价意见</text>
+					<view class="form-item">
+						<text class="block mb-2">请输入评价意见</text>
+						<textarea
+							v-model="evaluationComments"
+							class="w-full border p-2 rounded"
+							placeholder="请输入您的评价意见..."
+							:rows="4"
+						></textarea>
+					</view>
+				</view>
+
+				<!-- 提交按钮 -->
+				<view class="flex justify-end">
+					<button 
+						class="bg-blue-500 text-white px-4 py-2 rounded"
+						@click="handleSubmit"
+					>提交评估</button>
+				</view>
+			</view>
+		</view>
+
+		<!-- 悬浮按钮 -->
+		<!-- <view class="fixed-buttons">
+			<button class="action-button share-button" @click="handleShare">
+				<text class="iconfont">&#xe600;</text>
+			</button>
+			<button class="action-button download-button" @click="handleDownload">
+				<text class="iconfont">&#xe601;</text>
+			</button>
+			<button class="action-button top-button" @click="scrollToTop">
+				<text class="iconfont">&#xe602;</text>
+			</button>
+		</view> -->
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				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'
+							]
+						}
+					}
+				},
+				evaluationScore: 0,
+				evaluationComments: '',
+				dimensionLabels: {
+					teamwork: '团队合作能力',
+					learningAbility: '学习能力',
+					attention: '细致严谨',
+					workAdaptability: '工作适应性',
+					serviceAwareness: '服务意识'
+				}
+			}
+		},
+		methods: {
+			handleSubmit() {
+				if (this.evaluationScore === 0 || !this.evaluationComments.trim()) {
+					uni.showToast({
+						title: '请完成所有必填项',
+						icon: 'none'
+					});
+					return;
+				}
+				
+				uni.showToast({
+					title: '评估已提交',
+					icon: 'success'
+				});
+			},
+			
+			scrollToTop() {
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: 300
+				});
+			},
+			
+			handleShare() {
+				uni.showToast({
+					title: '分享链接已复制',
+					icon: 'success'
+				});
+			},
+			
+			handleDownload() {
+				uni.showToast({
+					title: '报告下载中...',
+					icon: 'loading'
+				});
+			}
+		}
+	}
+</script>
+
+<style>
+.max-w-4xl {
+	max-width: 56rem;
+	margin: 0 auto;
+}
+
+.p-6 {
+	padding: 1.5rem;
+}
+
+.relative {
+	position: relative;
+}
+
+/* .bg-\[\#1d1e3a\] {
+	background-color: #1d1e3a;
+} */
+
+.text-white {
+	color: white;
+}
+
+.p-4 {
+	padding: 1rem;
+}
+
+.rounded-t-lg {
+	border-top-left-radius: 0.5rem;
+	border-top-right-radius: 0.5rem;
+}
+
+.flex {
+	display: flex;
+}
+
+.justify-between {
+	justify-content: space-between;
+}
+
+.items-center {
+	align-items: center;
+}
+
+.text-sm {
+	font-size: 0.875rem;
+}
+
+.bg-white {
+	background-color: white;
+}
+
+.rounded-b-lg {
+	border-bottom-left-radius: 0.5rem;
+	border-bottom-right-radius: 0.5rem;
+}
+
+.shadow-lg {
+	box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
+
+.mb-8 {
+	margin-bottom: 2rem;
+}
+
+.pb-4 {
+	padding-bottom: 1rem;
+}
+
+.border-b {
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	border-bottom-color: #e5e7eb;
+}
+
+/* .space-x-4 > * + * {
+	margin-left: 1rem;
+} */
+
+.w-20 {
+	width: 5rem;
+}
+
+.h-20 {
+	height: 5rem;
+}
+
+.bg-gray-200 {
+	background-color: #e5e7eb;
+}
+
+.rounded-lg {
+	border-radius: 0.5rem;
+}
+
+.overflow-hidden {
+	overflow: hidden;
+}
+
+.w-full {
+	width: 100%;
+}
+
+.h-full {
+	height: 100%;
+}
+
+.object-cover {
+	object-fit: cover;
+}
+
+.text-2xl {
+	font-size: 1.5rem;
+}
+
+.font-bold {
+	font-weight: bold;
+}
+
+.text-gray-600 {
+	color: #4b5563;
+}
+
+.text-right {
+	text-align: right;
+}
+
+.text-4xl {
+	font-size: 2.25rem;
+}
+
+.text-yellow-500 {
+	color: #f59e0b;
+}
+
+.ml-1 {
+	margin-left: 0.25rem;
+}
+
+.text-base {
+	font-size: 1rem;
+}
+
+.grid {
+	display: grid;
+}
+
+.grid-cols-1 {
+	grid-template-columns: repeat(1, minmax(0, 1fr));
+}
+
+.grid-cols-2 {
+	grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.grid-cols-3 {
+	grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.gap-4 {
+	gap: 1rem;
+}
+
+.gap-2 {
+	gap: 0.5rem;
+}
+
+.gap-8 {
+	gap: 2rem;
+}
+
+.text-xl {
+	font-size: 1.25rem;
+}
+
+.mb-6 {
+	margin-bottom: 1.5rem;
+}
+
+.mb-4 {
+	margin-bottom: 1rem;
+}
+
+.mb-2 {
+	margin-bottom: 0.5rem;
+}
+
+/* .space-y-6 > * + * {
+	margin-top: 1.5rem;
+}
+
+.space-y-4 > * + * {
+	margin-top: 1rem;
+}
+
+.space-y-8 > * + * {
+	margin-top: 2rem;
+} */
+
+.w-32 {
+	width: 8rem;
+}
+
+.text-red-500 {
+	color: #ef4444;
+}
+
+.text-green-500 {
+	color: #10b981;
+}
+
+.text-sm {
+	font-size: 0.875rem;
+}
+
+.font-semibold {
+	font-weight: 600;
+}
+
+.mt-2 {
+	margin-top: 0.5rem;
+}
+
+.text-gray-500 {
+	color: #6b7280;
+}
+
+.mt-4 {
+	margin-top: 1rem;
+}
+
+.video-container {
+	position: relative;
+	width: 100%;
+	max-width: 240px;
+	aspect-ratio: 3/4;
+	margin: 0 auto;
+	background: #000;
+	border-radius: 12px;
+	overflow: hidden;
+}
+
+.justify-end {
+	justify-content: flex-end;
+}
+
+.text-blue-500 {
+	color: #3b82f6;
+}
+
+.text-lg {
+	font-size: 1.125rem;
+}
+
+.bg-gray-50 {
+	background-color: #f9fafb;
+}
+
+.aspect-square {
+	aspect-ratio: 1 / 1;
+}
+
+.bg-gray-100 {
+	background-color: #f3f4f6;
+}
+
+.form-item {
+	margin-bottom: 1.5rem;
+}
+
+.border {
+	border-width: 1px;
+	border-style: solid;
+	border-color: #e5e7eb;
+}
+
+.rounded {
+	border-radius: 0.25rem;
+}
+
+.bg-blue-500 {
+	background-color: #3b82f6;
+}
+
+.px-4 {
+	padding-left: 1rem;
+	padding-right: 1rem;
+}
+
+.py-2 {
+	padding-top: 0.5rem;
+	padding-bottom: 0.5rem;
+}
+
+.fixed-buttons {
+	position: fixed;
+	right: 2rem;
+	bottom: 6rem;
+	display: flex;
+	flex-direction: column;
+	gap: 1rem;
+	z-index: 100;
+}
+
+.action-button {
+	width: 40px;
+	height: 40px;
+	border-radius: 50%;
+	background-color: #3b82f6;
+	color: white;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+}
+
+/* 为了模拟图标,你可能需要引入字体图标或使用其他方式 */
+@font-face {
+	font-family: "iconfont";
+	src: url('data:font/woff2;charset=utf-8;base64,...') format('woff2');
+}
+
+.iconfont {
+	font-family: "iconfont" !important;
+	font-style: normal;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+}
+
+/* 媒体查询适配移动端 */
+@media (max-width: 768px) {
+	.p-6 {
+		padding: 1rem;
+	}
+	
+	.fixed-buttons {
+		right: 1rem;
+	}
+}
+</style>

+ 58 - 9
pages/camera/camera.vue

@@ -35,7 +35,7 @@
 			<view class="interview-content">
 				<!-- 问题编号和进度 -->
 				<view class="question-number">
-					<text>{{currentQuestionIndex + 1}}-{{currentQuestion.id}}/{{questions.length}}</text>
+					<text>{{currentQuestionIndex + 1}}/{{questions.length}}</text>
 				</view>
 				
 				<!-- 问题和选项区域 -->
@@ -113,8 +113,8 @@
 				<!-- <view class="score-display">{{score}}/{{totalQuestions}}</view>
 				<view class="modal-message">您在本次测试中答了{{score}}道题目,共{{totalQuestions}}道题目。</view> -->
 				<view class="modal-buttons">
-					<button type="default" class="modal-button" @click="restartTest">重新测试</button>
-					<button type="primary" class="modal-button" @click="back">返回首页</button>
+					<!-- <button type="default" class="modal-button" @click="restartTest">重新测试</button> -->
+					<button type="primary" class="modal-button" @click="navigateToInterview">进入面试</button>
 				</view>
 			</view>
 		</view>
@@ -420,9 +420,22 @@
 				}
 				
 				// 如果已经显示结果,点击"下一题"按钮时执行
-				// 如果是最后一题,显示结果页面
+				// 如果是最后一题,直接跳转到手部照片采集页面
 				if (this.currentQuestionIndex >= this.questions.length - 1) {
-					this.showEndModal = true;
+					// 直接跳转到手部照片采集页面,不显示结束模态框
+					uni.navigateTo({
+						url: '/pages/interview/interview', // 假设这是手部照片采集页面的路径
+						success: () => {
+							console.log('成功跳转到手部照片采集页面');
+						},
+						fail: (err) => {
+							console.error('跳转失败:', err);
+							uni.showToast({
+								title: '跳转失败,请手动返回首页',
+								icon: 'none'
+							});
+						}
+					});
 					return;
 				}
 				
@@ -635,13 +648,21 @@
 				});
 			},
 
-			back() {
+			back(target) {
 				// 清除计时器
 				if (this.timerInterval) {
 					clearInterval(this.timerInterval);
 				}
 				
-				// 尝试返回上一页,如果失败则跳转到首页
+				// 如果指定了跳转目标
+				if (target) {
+					uni.navigateTo({
+						url: target
+					});
+					return;
+				}
+				
+				// 否则尝试返回上一页,如果失败则跳转到首页
 				try {
 					const pages = getCurrentPages();
 					if (pages.length > 1) {
@@ -691,8 +712,8 @@
 				}
 			},
 			
-			// 重新开始测试
-			restartTest() {
+			// 修改 restartTest 方法,添加可选的跳转目标
+			restartTest(target) {
 				this.currentQuestionIndex = 0;
 				this.score = 0;
 				this.showEndModal = false;
@@ -700,6 +721,13 @@
 				this.selectedOption = null;
 				this.selectedOptions = [];
 				this.resetTimer();
+				
+				// 如果指定了跳转目标
+				if (target) {
+					uni.navigateTo({
+						url: target
+					});
+				}
 			},
 
 			// 在methods中添加测试方法
@@ -730,6 +758,27 @@
 				if (webview) {
 					webview.evalJS(`receiveMessage('${message}')`);
 				}
+			},
+
+			// 添加 navigateToInterview 方法
+			navigateToInterview() {
+				// 关闭模态框
+				this.showEndModal = false;
+				
+				// 跳转到interview页面
+				uni.navigateTo({
+					url: '/pages/interview/interview',
+					success: () => {
+						console.log('成功跳转到interview页面');
+					},
+					fail: (err) => {
+						console.error('跳转失败:', err);
+						uni.showToast({
+							title: '跳转失败,请手动返回首页',
+							icon: 'none'
+						});
+					}
+				});
 			}
 		},
 		// 添加生命周期钩子,确保在组件销毁时清除计时器

+ 79 - 4
pages/identity-verify/identity-verify.vue

@@ -104,6 +104,13 @@
         开始作答
       </button>
     </div>
+
+    <!-- 添加重试上传按钮 -->
+    <div class="retry-button-container" v-if="showRetryButton">
+      <button class="retry-button" @click="retryVideoUpload">
+        重新上传
+      </button>
+    </div>
   </div>
 </template>
 
@@ -196,6 +203,8 @@ export default {
       recorder: null,
       lastUploadedVideoUrl: '',
       showStartRecordingButton: false,
+      showRetryButton: false, // 控制重试按钮显示
+      lastVideoToRetry: null, // 存储上次失败的视频URL,用于重试
     }
   },
   mounted() {
@@ -1245,6 +1254,12 @@ export default {
     submitVideoToInterview(videoUrl) {
       console.log('提交视频URL到面试接口:', videoUrl);
       
+      // 显示加载提示
+      uni.showLoading({
+        title: '正在提交...',
+        mask: true
+      });
+      
       // 根据当前视频索引映射到正确的问题ID
       let questionId;
       switch(this.currentVideoIndex) {
@@ -1269,17 +1284,16 @@ export default {
         application_id: 1,
         question_id: questionId,
         video_url: videoUrl,
-        video_duration: 0,
         tenant_id: uni.getStorageSync('tenant_id') || '1'
       };
       
-      // 发送请求到面试接口
+      // 发送请求到面试接口,使用form-data格式
       uni.request({
         url: 'http://192.168.66.187:8083/api/job/upload_question_video',
         method: 'POST',
-        data: requestData,
+        data: requestData,  // 使用data而不是formData
         header: {
-          'content-type': 'application/json'
+          'content-type': 'application/x-www-form-urlencoded'  // 修改为form-data格式的content-type
         },
         success: (res) => {
           console.log('面试接口提交成功:', res);
@@ -1295,6 +1309,10 @@ export default {
             // 保存最后上传的视频URL
             this.lastUploadedVideoUrl = videoUrl;
             
+            // 隐藏重试按钮(如果之前显示了)
+            this.showRetryButton = false;
+            this.lastVideoToRetry = null;
+            
             // 延迟一下再进入下一题,让用户看到成功提示
             setTimeout(() => {
               // 检查是否完成了第五个视频问题(索引为4)
@@ -1314,6 +1332,12 @@ export default {
               title: res.data.msg || '提交失败,请重试',
               icon: 'none'
             });
+            
+            // 保存失败的视频URL,用于重试
+            this.lastVideoToRetry = videoUrl;
+            
+            // 显示重试按钮
+            this.showRetryButton = true;
           }
         },
         fail: (err) => {
@@ -1323,6 +1347,12 @@ export default {
             title: '网络错误,请重试',
             icon: 'none'
           });
+          
+          // 保存失败的视频URL,用于重试
+          this.lastVideoToRetry = videoUrl;
+          
+          // 显示重试按钮
+          this.showRetryButton = true;
         }
       });
     },
@@ -1640,6 +1670,28 @@ export default {
         }
       });
     },
+
+    // 添加重试上传方法
+    retryVideoUpload() {
+      if (this.lastVideoToRetry) {
+        // 隐藏重试按钮
+        this.showRetryButton = false;
+        
+        // 显示加载提示
+        uni.showLoading({
+          title: '正在重新提交...',
+          mask: true
+        });
+        
+        // 重新提交视频
+        this.submitVideoToInterview(this.lastVideoToRetry);
+      } else {
+        uni.showToast({
+          title: '没有可重试的视频',
+          icon: 'none'
+        });
+      }
+    }
   }
 }
 </script>
@@ -1942,4 +1994,27 @@ video::-webkit-media-controls-fullscreen-button {
 }
 
 /* 可以删除pulse-green动画,直接使用与开始面试按钮相同的pulse动画 */
+
+.retry-button-container {
+  position: absolute;
+  bottom: 50px;
+  left: 50%;
+  transform: translateX(-50%);
+  z-index: 20;
+}
+
+.retry-button {
+  padding: 10px 20px;
+  background-color: #3498db; /* 蓝色背景 */
+  color: white;
+  font-size: 16px;
+  border: none;
+  border-radius: 4px;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+  cursor: pointer;
+}
+
+.retry-button:hover {
+  background-color: #2980b9;
+}
 </style>

+ 730 - 155
pages/interview/interview.vue

@@ -1,21 +1,88 @@
 <template>
 	<view class="container">
-		<view class="header">
-			<text class="title">语音对话</text>
-		</view>
+		<!-- <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 class="photo-container">
+			<view class="instruction">
+				<text>{{ instructions[currentStep] }}</text>
+			</view>
+			
+			<view class="preview-container">
+				<image v-if="photos[currentStep]" :src="photos[currentStep]" mode="aspectFit" class="preview-image"></image>
+				<view v-else class="empty-preview">
+					<text>{{ previewTexts[currentStep] }}</text>
+				</view>
+			</view>
+			
+			<view class="thumbnails">
+				<view v-for="(photo, index) in photos" :key="index" 
+					class="thumbnail-item" 
+					:class="{ active: currentStep === index, completed: photo }">
+					<image v-if="photo" :src="photo" mode="aspectFill" class="thumbnail-image" @click="previewPhoto(index)"></image>
+					<view v-else class="thumbnail-placeholder">{{ index + 1 }}</view>
 				</view>
-			</scroll-view>
+			</view>
 		</view>
 		
 		<view class="controls">
-			<button class="voice-btn" :class="{ recording: isRecording }" @touchstart="startRecording" @touchend="stopRecording">
-				<text>{{ isRecording ? '松开结束' : '按住说话' }}</text>
-			</button>
+			<button class="camera-btn" @click="takePhoto">{{ photos[currentStep] ? '重新拍摄' : '拍摄照片' }}</button>
+			
+			<view class="navigation-btns">
+				<button class="nav-btn prev" :disabled="currentStep === 0" @click="prevStep">上一步</button>
+				<button class="nav-btn next" :disabled="!canGoNext" @click="nextStep">
+					{{ currentStep === 5 && photos[5] ? '完成' : '下一步' }}
+				</button>
+			</view>
+		</view>
+		
+		<!-- 为小程序环境添加camera组件 -->
+		<view v-if="!isH5 && showCamera" class="camera-container">
+			<camera 
+				device-position="back" 
+				flash="auto" 
+				@error="handleCameraError" 
+				class="camera-component"
+				:mode="cameraMode"
+				frame-size="medium"
+			></camera>
+			
+			<!-- 添加手势引导蒙层 -->
+			<cover-view class="gesture-overlay">
+				<cover-image 
+					:src="gestureOverlays[currentStep]" 
+					class="gesture-image"
+				></cover-image>
+				<cover-view class="gesture-hint">
+					<cover-view class="gesture-hint-text">{{ gestureHints[currentStep] }}</cover-view>
+				</cover-view>
+			</cover-view>
+			
+			<cover-view class="camera-controls">
+				<cover-view class="capture-btn" @tap="capturePhotoMP"></cover-view>
+				<cover-view class="cancel-btn" @tap="cancelCamera">取消</cover-view>
+			</cover-view>
+		</view>
+		
+		<!-- 为浏览器环境添加视频元素 -->
+		<view v-if="isH5" class="camera-container" v-show="showCamera">
+			<video ref="videoElement" class="camera-video" autoplay></video>
+			<canvas ref="canvasElement" class="camera-canvas" style="display: none;"></canvas>
+			
+			<!-- 添加H5环境的手势引导蒙层 -->
+			<view class="gesture-overlay">
+				<image 
+					:src="gestureOverlays[currentStep]" 
+					class="gesture-image"
+				></image>
+				<view class="gesture-hint">
+					<text class="gesture-hint-text">{{ gestureHints[currentStep] }}</text>
+				</view>
+			</view>
+			
+			<button class="capture-btn" @click="capturePhoto">拍照</button>
+			<button class="cancel-btn" @click="cancelCamera">取消</button>
 		</view>
 	</view>
 </template>
@@ -24,167 +91,512 @@
 	export default {
 		data() {
 			return {
-				messages: [],
-				isRecording: false,
-				recorderManager: null,
-				innerAudioContext: null,
-				scrollTop: 0,
-				apiUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions'
+				currentStep: 0,
+				photos: [null, null, null, null, null, null], // 存储6张照片:左手正面、左手反面、左手握拳、右手正面、右手反面、右手握拳
+				instructions: [
+					'请将左手掌正面朝上,保持手指自然伸展,确保整个手掌清晰可见',
+					'请将左手掌反面朝上,保持手指自然伸展,确保整个手背清晰可见',
+					'请握紧左手拳头,使拳面朝向相机,确保整个拳头清晰可见',
+					'请将右手掌正面朝上,保持手指自然伸展,确保整个手掌清晰可见',
+					'请将右手掌反面朝上,保持手指自然伸展,确保整个手背清晰可见',
+					'请握紧右手拳头,使拳面朝向相机,确保整个拳头清晰可见'
+				],
+				previewTexts: [
+					'左手掌正面照片预览区',
+					'左手掌反面照片预览区',
+					'左手握拳照片预览区',
+					'右手掌正面照片预览区',
+					'右手掌反面照片预览区',
+					'右手握拳照片预览区'
+				],
+				photoTypes: ['left_palm', 'left_back', 'left_fist', 'right_palm', 'right_back', 'right_fist'], // 照片类型标识
+				isH5: false,
+				showCamera: false,
+				mediaStream: null,
+				cameraContext: null,
+				cameraMode: 'normal', // 添加相机模式
+				cameraInitRetries: 0, // 添加重试计数器
+				gestureOverlays: [
+					/* '/static/images/palm_overlay.png',  // 手掌正面引导图
+					'/static/images/back_overlay.png',  // 手掌反面引导图
+					'/static/images/fist_overlay.png'   // 握拳引导图 */
+				],
+				gestureHints: [
+					'请将左手掌对准轮廓线',
+					'请将左手背对准轮廓线',
+					'请将左手拳头对准轮廓线',
+					'请将右手掌对准轮廓线',
+					'请将右手背对准轮廓线',
+					'请将右手拳头对准轮廓线'
+				],
+				photoLinks: [null, null, null, null, null, null], // 存储上传后的图片链接
+			}
+		},
+		computed: {
+			canGoNext() {
+				// 只有当前步骤的照片已拍摄才能进入下一步
+				return this.photos[this.currentStep] !== null;
 			}
 		},
 		onLoad() {
-			// 初始化录音管理器
-			this.recorderManager = uni.getRecorderManager();
-			this.innerAudioContext = uni.createInnerAudioContext();
+			// 判断当前平台
+			// #ifdef H5
+			this.isH5 = true;
+			// #endif
 			
-			// 监听录音结束事件
-			this.recorderManager.onStop((res) => {
-				this.processAudio(res.tempFilePath);
+			// 检查相机权限
+			// #ifdef MP-WEIXIN
+			uni.authorize({
+				scope: 'scope.camera',
+				success: () => {
+					console.log('相机权限已授权');
+				},
+				fail: () => {
+					uni.showModal({
+						title: '提示',
+						content: '请授权相机权限以完成手部照片采集',
+						success: (res) => {
+							if (res.confirm) {
+								uni.openSetting();
+							}
+						}
+					});
+				}
 			});
+			// #endif
+		},
+		beforeDestroy() {
+			// 在组件销毁前停止摄像头流
+			this.stopMediaStream();
 		},
 		methods: {
-			startRecording() {
-				this.isRecording = true;
-				// 开始录音
-				this.recorderManager.start({
-					format: 'mp3',
-					sampleRate: 16000,
-					numberOfChannels: 1
-				});
-			},
-			stopRecording() {
-				this.isRecording = false;
-				// 停止录音
-				this.recorderManager.stop();
+			takePhoto() {
+				// 根据平台选择不同的拍照方法
+				if (this.isH5) {
+					this.startCamera();
+				} else {
+					// 小程序环境使用camera组件
+					this.showCamera = true;
+					this.cameraInitRetries = 0; // 重置重试计数器
+					
+					this.$nextTick(() => {
+						// 创建相机上下文
+						// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
+						try {
+							this.cameraContext = uni.createCameraContext();
+							console.log('相机上下文创建成功');
+						} catch (err) {
+							console.error('创建相机上下文失败:', err);
+							this.handleCameraInitError();
+						}
+						// #endif
+					});
+				}
 			},
-			processAudio(filePath) {
-				// 显示加载状态
+			uploadPhoto(filePathOrData, type) {
+				console.log(`准备上传${type}类型的手部照片`);
+				
 				uni.showLoading({
-					title: '处理中...'
+					title: '上传中...'
 				});
 				
-				// 将录音文件转换为文本(语音识别)
-				// 这里需要接入语音识别服务,以下为示例
-				this.speechToText(filePath).then(text => {
-					// 添加用户消息
-					this.addMessage('user', text);
-					
-					// 调用DashScope API
-					this.callDashScopeAPI(text);
-				}).catch(err => {
+				// 获取用户openid
+				const userInfo = uni.getStorageSync('userInfo');
+				const openid = userInfo ? JSON.parse(userInfo).openid || '' : '';
+				const tenant_id = uni.getStorageSync('tenant_id') || '1';
+				
+				// 检查用户信息是否完整
+				if (!tenant_id) {
 					uni.hideLoading();
 					uni.showToast({
-						title: '语音识别失败',
+						title: '用户信息不完整,请重新登录',
 						icon: 'none'
 					});
-					console.error(err);
-				});
-			},
-			speechToText(filePath) {
-				// 临时解决方案:直接返回模拟的语音识别结果
-				return new Promise((resolve) => {
-					console.log('录音文件路径:', filePath);
-					// 模拟语音识别结果
-					setTimeout(() => {
-						resolve('你好,我需要帮助');
-					}, 500);
-				});
+					return;
+				}
 				
-				// 注释掉原来的实现,等待后续接入真实的语音识别服务
-				/*
-				return new Promise((resolve, reject) => {
+				// 如果是H5环境且是base64数据
+				if (this.isH5 && filePathOrData.startsWith('data:image')) {
+					// 将base64转为blob对象
+					const base64Data = filePathOrData.split(',')[1];
+					const blob = this.base64ToBlob(base64Data, 'image/jpeg');
+					
+					const formData = new FormData();
+					// 确保文件参数名称正确
+					formData.append('photo_file', blob, `${type}.jpg`);
+					formData.append('application_id', '1');
+					formData.append('tenant_id', tenant_id);
+					formData.append('description', `手部照片-${type}`);
+					
+					console.log('H5上传参数:', {
+						application_id: '1',
+						tenant_id: tenant_id,
+						description: `手部照片-${type}`
+					});
+					
+					// 使用fetch直接上传到 job/upload_posture_photo 接口
+					fetch('http://192.168.66.187:8083/job/upload_posture_photo', {
+						method: 'POST',
+						body: formData
+					})
+					.then(response => response.json())
+					.then(result => {
+						console.log('照片上传成功:', result);
+						
+						// 保存图片链接到对应类型的索引位置
+						const index = this.photoTypes.indexOf(type);
+						if (index !== -1 && result.data && result.data.url) {
+							this.$set(this.photoLinks, index, result.data.url);
+							console.log(`已保存${type}类型照片链接:`, result.data.url);
+						}
+						
+						uni.hideLoading();
+						uni.showToast({
+							title: '照片上传成功',
+							icon: 'success'
+						});
+					})
+					.catch(error => {
+						console.error('照片上传失败:', error);
+						uni.hideLoading();
+						uni.showToast({
+							title: '照片上传失败,请重试',
+							icon: 'none'
+						});
+					});
+				} else {
+					// 小程序环境的上传逻辑
+					console.log(`小程序环境上传文件路径:`, filePathOrData);
+					console.log('小程序上传参数:', {
+						application_id: '1',
+						tenant_id: tenant_id,
+						description: `手部照片-${type}`
+					});
+					
 					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('语音识别失败'));
+						url: 'http://192.168.66.187:8083/job/upload_posture_photo',
+						filePath: filePathOrData,
+						name: 'photo_file',  // 确保文件参数名称正确
+						formData: {  // 使用formData替代data以确保参数正确传递
+							'application_id': '1',
+							'tenant_id': tenant_id,
+							'description': `手部照片-${type}`
+						},
+						success: (uploadRes) => {
+							console.log('照片上传成功:', uploadRes);
+							
+							// 解析返回的JSON字符串
+							let result;
+							try {
+								if (typeof uploadRes.data === 'string') {
+									result = JSON.parse(uploadRes.data);
+								} else {
+									result = uploadRes.data;
+								}
+								console.log('解析后的上传结果:', result);
+								
+								// 保存图片链接到对应类型的索引位置
+								const index = this.photoTypes.indexOf(type);
+								if (index !== -1 && result.data && result.data.url) {
+									this.$set(this.photoLinks, index, result.data.url);
+									console.log(`已保存${type}类型照片链接:`, result.data.url);
+								}
+							} catch (e) {
+								console.error('解析上传结果失败:', e);
 							}
+							
+							uni.hideLoading();
+							uni.showToast({
+								title: '照片上传成功',
+								icon: 'success'
+							});
 						},
-						fail: reject
-					});
-				});
-				*/
-			},
-			callDashScopeAPI(userInput) {
-				// 构建请求体
-				const requestBody = {
-					model: "qwen-plus",
-					messages: [
-						{
-							role: "system",
-							content: "You are a helpful assistant."
+						fail: (err) => {
+							console.error('照片上传失败:', err);
+							uni.hideLoading();
+							uni.showToast({
+								title: '照片上传失败,请重试',
+								icon: 'none'
+							});
 						},
-						{
-							role: "user",
-							content: userInput
+						complete: () => {
+							uni.hideLoading();
 						}
-					]
-				};
-				
-				// 发送请求到DashScope API
-				uni.request({
-					url: this.apiUrl,
-					method: 'POST',
-					header: {
-						'Content-Type': 'application/json',
-						'Authorization': 'Bearer YOUR_API_KEY' // 替换为您的API密钥
-					},
-					data: requestBody,
-					success: (res) => {
+					});
+				}
+			},
+			prevStep() {
+				if (this.currentStep > 0) {
+					this.currentStep--;
+				}
+			},
+			nextStep() {
+				if (this.currentStep < 5) {
+					this.currentStep++;
+				} else if (this.currentStep === 5 && this.photos[5]) {
+					// 所有照片都已拍摄完成,可以进行提交或跳转
+					this.submitAllPhotos();
+				}
+			},
+			previewPhoto(index) {
+				// 如果照片存在,则预览
+				if (this.photos[index]) {
+					uni.previewImage({
+						urls: [this.photos[index]],
+						current: this.photos[index]
+					});
+				}
+			},
+			submitAllPhotos() {
+				// 检查是否所有照片都已拍摄
+				if (this.photos.every(photo => photo !== null)) {
+					uni.showLoading({
+						title: '处理中...'
+					});
+					
+					// 所有照片已经单独上传,这里只需要处理跳转
+					setTimeout(() => {
 						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);
+						uni.showToast({
+							title: '手部照片采集完成',
+							icon: 'success',
+							duration: 2000,
+							success: () => {
+								// 延迟跳转,让用户看到成功提示
+								setTimeout(() => {
+									//uni.navigateBack();
+									// 或者跳转到下一个页面
+									uni.navigateTo({
+									    url: '/pages/ResumeEvaluation/ResumeEvaluation'
+									});
+								}, 2000);
+							}
+						});
+					}, 500);
+				} else {
+					uni.showToast({
+						title: '请完成所有照片拍摄',
+						icon: 'none'
+					});
+				}
+			},
+			// H5环境下启动摄像头
+			startCamera() {
+				this.showCamera = true;
+				
+				// 确保浏览器支持getUserMedia
+				if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+					navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
+						.then(stream => {
+							this.mediaStream = stream;
+							// 等待DOM更新后再设置视频源
+							this.$nextTick(() => {
+								const videoElement = this.$refs.videoElement;
+								if (videoElement) {
+									videoElement.srcObject = stream;
+								}
+							});
+						})
+						.catch(error => {
+							console.error('获取摄像头失败:', error);
+							uni.showToast({
+								title: '无法访问摄像头,请检查权限设置',
+								icon: 'none'
+							});
+							this.showCamera = false;
+						});
+				} else {
+					uni.showToast({
+						title: '您的浏览器不支持摄像头功能',
+						icon: 'none'
+					});
+					this.showCamera = false;
+				}
+			},
+			
+			// H5环境下拍照
+			capturePhoto() {
+				const videoElement = this.$refs.videoElement;
+				const canvasElement = this.$refs.canvasElement;
+				
+				if (videoElement && canvasElement) {
+					const context = canvasElement.getContext('2d');
+					
+					// 设置canvas尺寸与视频一致
+					canvasElement.width = videoElement.videoWidth;
+					canvasElement.height = videoElement.videoHeight;
+					
+					// 在canvas上绘制当前视频帧
+					context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
+					
+					// 将canvas内容转为base64图片
+					const photoData = canvasElement.toDataURL('image/jpeg');
+					
+					// 更新照片数据
+					this.$set(this.photos, this.currentStep, photoData);
+					
+					// 上传照片
+					this.uploadPhoto(photoData, this.photoTypes[this.currentStep]);
+					
+					// 关闭摄像头
+					this.stopMediaStream();
+					this.showCamera = false;
+				}
+			},
+			
+			// 小程序环境下拍照
+			capturePhotoMP() {
+				if (!this.cameraContext) {
+					console.error('相机上下文不存在');
+					this.handleCameraInitError();
+					return;
+				}
+				
+				try {
+					this.cameraContext.takePhoto({
+						quality: 'high',
+						success: (res) => {
+							console.log('拍照成功:', res);
+							// 更新当前步骤的照片
+							this.$set(this.photos, this.currentStep, res.tempImagePath);
+							
+							// 上传照片
+							this.uploadPhoto(res.tempImagePath, this.photoTypes[this.currentStep]);
 							
-							// 可选:将回复转为语音(文本转语音)
-							this.textToSpeech(assistantMessage);
-						} else {
+							// 关闭相机
+							this.showCamera = false;
+						},
+						fail: (err) => {
+							console.error('拍照失败:', err);
 							uni.showToast({
-								title: '获取回复失败',
+								title: '拍照失败,请重试',
 								icon: 'none'
 							});
-							console.error('API响应错误:', res);
+							// 尝试使用替代方法
+							this.fallbackToChooseImage();
 						}
+					});
+				} catch (err) {
+					console.error('takePhoto方法执行失败:', err);
+					this.fallbackToChooseImage();
+				}
+			},
+			
+			// 添加相机初始化错误处理
+			handleCameraInitError() {
+				if (this.cameraInitRetries < 3) {
+					this.cameraInitRetries++;
+					uni.showToast({
+						title: `相机初始化失败,正在重试(${this.cameraInitRetries}/3)`,
+						icon: 'none'
+					});
+					
+					setTimeout(() => {
+						this.showCamera = false;
+						setTimeout(() => {
+							this.takePhoto();
+						}, 500);
+					}, 1000);
+				} else {
+					uni.showToast({
+						title: '相机初始化失败,将使用系统相机',
+						icon: 'none'
+					});
+					this.fallbackToChooseImage();
+				}
+			},
+			
+			// 添加备用的系统相机方法
+			fallbackToChooseImage() {
+				this.showCamera = false;
+				
+				// 使用系统相机作为备选方案
+				uni.chooseImage({
+					count: 1,
+					sourceType: ['camera'],
+					success: (res) => {
+						// 更新当前步骤的照片
+						this.$set(this.photos, this.currentStep, res.tempFilePaths[0]);
+						
+						// 上传照片
+						this.uploadPhoto(res.tempFilePaths[0], this.photoTypes[this.currentStep]);
 					},
 					fail: (err) => {
-						uni.hideLoading();
+						console.error('选择图片失败:', err);
 						uni.showToast({
-							title: '网络请求失败',
+							title: '拍照失败,请重试',
 							icon: 'none'
 						});
-						console.error(err);
 					}
 				});
 			},
-			addMessage(role, content) {
-				this.messages.push({ role, content });
-				// 滚动到底部
-				this.$nextTick(() => {
-					this.scrollTop = 9999;
+			
+			// 修改相机错误处理方法
+			handleCameraError(e) {
+				console.error('相机错误:', e.detail);
+				
+				// 记录更详细的错误信息
+				const errorInfo = JSON.stringify(e.detail);
+				console.log('详细错误信息:', errorInfo);
+				
+				uni.showToast({
+					title: '相机启动失败,将使用系统相机',
+					icon: 'none'
 				});
+				
+				// 使用备用方法
+				this.fallbackToChooseImage();
 			},
-			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();
-						}
+			
+			// 取消拍照 - 更新为同时支持H5和小程序
+			cancelCamera() {
+				if (this.isH5) {
+					this.stopMediaStream();
+				}
+				this.showCamera = false;
+			},
+			
+			// 停止摄像头流
+			stopMediaStream() {
+				if (this.mediaStream) {
+					this.mediaStream.getTracks().forEach(track => {
+						track.stop();
+					});
+					this.mediaStream = null;
+				}
+			},
+			
+			// base64转blob工具方法
+			base64ToBlob(base64, mimeType) {
+				const byteCharacters = atob(base64);
+				const byteArrays = [];
+				
+				for (let offset = 0; offset < byteCharacters.length; offset += 512) {
+					const slice = byteCharacters.slice(offset, offset + 512);
+					
+					const byteNumbers = new Array(slice.length);
+					for (let i = 0; i < slice.length; i++) {
+						byteNumbers[i] = slice.charCodeAt(i);
 					}
-				});
+					
+					const byteArray = new Uint8Array(byteNumbers);
+					byteArrays.push(byteArray);
+				}
+				
+				return new Blob(byteArrays, { type: mimeType });
+			},
+			
+			retryCamera() {
+				this.showCamera = false;
+				setTimeout(() => {
+					this.takePhoto();
+				}, 500);
+			},
+			
+			// 可以添加一个方法来切换蒙层显示
+			toggleGestureOverlay() {
+				// 实现蒙层显示/隐藏的逻辑
+				// 如果需要的话
 			}
 		}
 	}
@@ -194,7 +606,7 @@
 .container {
 	display: flex;
 	flex-direction: column;
-	height: 100vh;
+	height: 94vh;
 	background-color: #f5f5f5;
 }
 
@@ -210,54 +622,217 @@
 	font-weight: bold;
 }
 
-.chat-container {
+.photo-container {
 	flex: 1;
 	padding: 20rpx;
+	display: flex;
+	flex-direction: column;
+}
+
+.instruction {
+	padding: 20rpx;
+	background-color: #E5E5EA;
+	border-radius: 10rpx;
+	margin-bottom: 20rpx;
+}
+
+.preview-container {
+	flex: 1;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	background-color: #E5E5EA;
+	border-radius: 10rpx;
+	margin-bottom: 20rpx;
 	overflow: hidden;
 }
 
-.chat-messages {
+.preview-image {
+	width: 100%;
 	height: 100%;
 }
 
-.message {
+.empty-preview {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	width: 100%;
+	height: 100%;
+	color: #999;
+}
+
+.thumbnails {
+	display: flex;
+	justify-content: space-around;
 	margin-bottom: 20rpx;
-	padding: 20rpx;
+}
+
+.thumbnail-item {
+	width: 150rpx;
+	height: 150rpx;
 	border-radius: 10rpx;
-	max-width: 80%;
-	word-break: break-word;
+	overflow: hidden;
+	background-color: #E5E5EA;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	border: 4rpx solid transparent;
 }
 
-.message.user {
-	align-self: flex-end;
-	background-color: #007AFF;
-	color: white;
-	margin-left: auto;
+.thumbnail-item.active {
+	border-color: #007AFF;
 }
 
-.message.assistant {
-	align-self: flex-start;
-	background-color: #E5E5EA;
-	color: #333;
+.thumbnail-item.completed {
+	background-color: #ffffff;
+}
+
+.thumbnail-image {
+	width: 100%;
+	height: 100%;
+}
+
+.thumbnail-placeholder {
+	font-size: 40rpx;
+	color: #999;
 }
 
 .controls {
 	padding: 20rpx;
 	background-color: #ffffff;
-	border-top: 1px solid #e0e0e0;
+	border-top: 1px solid #6c5ce7;
 }
 
-.voice-btn {
+.camera-btn {
 	width: 100%;
 	height: 80rpx;
 	line-height: 80rpx;
 	text-align: center;
-	background-color: #007AFF;
+	background-color: #6c5ce7;
 	color: white;
 	border-radius: 40rpx;
+	margin-bottom: 20rpx;
 }
 
-.voice-btn.recording {
-	background-color: #FF3B30;
+.navigation-btns {
+	display: flex;
+	justify-content: space-between;
+}
+
+.nav-btn {
+	width: 48%;
+	height: 80rpx;
+	line-height: 80rpx;
+	text-align: center;
+	border-radius: 40rpx;
+}
+
+.nav-btn.prev {
+	background-color: #E5E5EA;
+	color: #333;
+}
+
+.nav-btn.next {
+	background-color: #34C759;
+	color: white;
+}
+
+.nav-btn[disabled] {
+	opacity: 0.5;
+}
+
+/* 浏览器摄像头相关样式 */
+.camera-container {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background-color: #000;
+	z-index: 999;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+}
+
+.camera-video {
+	width: 100%;
+	height: 70%;
+	object-fit: cover;
+}
+
+.camera-canvas {
+	display: none;
+}
+
+.capture-btn {
+	width: 200rpx;
+	height: 200rpx;
+	border-radius: 50%;
+	background-color: #ffffff;
+	border: 10rpx solid #6c5ce7;
+	margin: 30rpx 0;
+}
+
+.cancel-btn {
+	width: 200rpx;
+	height: 80rpx;
+	line-height: 80rpx;
+	background-color: rgba(255, 255, 255, 0.3);
+	color: #ffffff;
+	text-align: center;
+	border-radius: 40rpx;
+}
+
+/* 小程序相机组件样式 */
+.camera-component {
+	width: 100%;
+	height: 80%;
+	display: block;
+}
+
+.camera-controls {
+	width: 100%;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	position: absolute;
+	bottom: 50rpx;
+}
+
+/* 添加手势引导蒙层样式 */
+.gesture-overlay {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 80%;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	pointer-events: none; /* 允许点击穿透 */
+	z-index: 10;
+}
+
+.gesture-image {
+	width: 70%;
+	height: 70%;
+	opacity: 0.6;
+}
+
+.gesture-hint {
+	background-color: rgba(0, 0, 0, 0.5);
+	padding: 10rpx 20rpx;
+	border-radius: 10rpx;
+	margin-top: 20rpx;
+}
+
+.gesture-hint-text {
+	color: white;
+	font-size: 28rpx;
+	text-align: center;
 }
 </style>
+

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

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

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

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

+ 249 - 0
unpackage/dist/dev/mp-weixin/pages/ResumeEvaluation/ResumeEvaluation.js

@@ -0,0 +1,249 @@
+"use strict";
+const common_vendor = require("../../common/vendor.js");
+const _sfc_main = {
+  data() {
+    return {
+      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"
+            ]
+          }
+        }
+      },
+      evaluationScore: 0,
+      evaluationComments: "",
+      dimensionLabels: {
+        teamwork: "团队合作能力",
+        learningAbility: "学习能力",
+        attention: "细致严谨",
+        workAdaptability: "工作适应性",
+        serviceAwareness: "服务意识"
+      }
+    };
+  },
+  methods: {
+    handleSubmit() {
+      if (this.evaluationScore === 0 || !this.evaluationComments.trim()) {
+        common_vendor.index.showToast({
+          title: "请完成所有必填项",
+          icon: "none"
+        });
+        return;
+      }
+      common_vendor.index.showToast({
+        title: "评估已提交",
+        icon: "success"
+      });
+    },
+    scrollToTop() {
+      common_vendor.index.pageScrollTo({
+        scrollTop: 0,
+        duration: 300
+      });
+    },
+    handleShare() {
+      common_vendor.index.showToast({
+        title: "分享链接已复制",
+        icon: "success"
+      });
+    },
+    handleDownload() {
+      common_vendor.index.showToast({
+        title: "报告下载中...",
+        icon: "loading"
+      });
+    }
+  }
+};
+if (!Array) {
+  const _component_uni_rate = common_vendor.resolveComponent("uni-rate");
+  _component_uni_rate();
+}
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return {
+    a: common_vendor.t($data.candidateInfo.idNumber),
+    b: $data.candidateInfo.additionalInfo.faceVerification.images[0],
+    c: common_vendor.t($data.candidateInfo.name),
+    d: common_vendor.t($data.candidateInfo.phoneNumber),
+    e: common_vendor.t($data.candidateInfo.score),
+    f: common_vendor.t($data.candidateInfo.idNumber),
+    g: common_vendor.t($data.candidateInfo.availabilityPeriod),
+    h: common_vendor.t($data.candidateInfo.onboardingTime),
+    i: common_vendor.f($data.candidateInfo.dimensions, (value, key, i0) => {
+      return {
+        a: common_vendor.t($data.dimensionLabels[key]),
+        b: common_vendor.t(value),
+        c: value === "欠佳" ? 1 : "",
+        d: value === "优秀" ? 1 : "",
+        e: value === "中等" ? 1 : "",
+        f: common_vendor.t($data.candidateInfo.dimensionDetails[key]),
+        g: key
+      };
+    }),
+    j: common_vendor.f($data.candidateInfo.duvAnalysis, (item, index, i0) => {
+      return {
+        a: common_vendor.t(item.title),
+        b: common_vendor.t(item.score),
+        c: item.type === "positive" ? 1 : "",
+        d: item.type === "negative" ? 1 : "",
+        e: item.type === "neutral" ? 1 : "",
+        f: common_vendor.t(item.content),
+        g: index
+      };
+    }),
+    k: common_vendor.f($data.candidateInfo.interviewRecord, (record, index, i0) => {
+      return common_vendor.e({
+        a: common_vendor.t(record.question),
+        b: common_vendor.t(record.answer),
+        c: common_vendor.t(record.analysis),
+        d: record.videoUrl
+      }, record.videoUrl ? {
+        e: record.videoUrl,
+        f: record.thumbnail
+      } : {}, {
+        g: common_vendor.t(record.score),
+        h: index
+      });
+    }),
+    l: common_vendor.f($data.candidateInfo.videoRecords, (category, index, i0) => {
+      return {
+        a: common_vendor.t(category.category),
+        b: common_vendor.f(category.videos, (video, vIndex, i1) => {
+          return {
+            a: video.url,
+            b: video.thumbnail,
+            c: common_vendor.t(video.description),
+            d: vIndex
+          };
+        }),
+        c: index
+      };
+    }),
+    m: common_vendor.t($data.candidateInfo.additionalInfo.idVerification),
+    n: common_vendor.t($data.candidateInfo.additionalInfo.phoneVerification),
+    o: common_vendor.f($data.candidateInfo.additionalInfo.leftHandGestures.images, (image, index, i0) => {
+      return {
+        a: image,
+        b: index
+      };
+    }),
+    p: common_vendor.f($data.candidateInfo.additionalInfo.rightHandGestures.images, (image, index, i0) => {
+      return {
+        a: image,
+        b: index
+      };
+    }),
+    q: common_vendor.f($data.candidateInfo.additionalInfo.faceVerification.images, (image, index, i0) => {
+      return {
+        a: image,
+        b: index
+      };
+    }),
+    r: common_vendor.o(($event) => $data.evaluationScore = $event),
+    s: common_vendor.p({
+      max: 5,
+      ["allow-half"]: true,
+      modelValue: $data.evaluationScore
+    }),
+    t: $data.evaluationComments,
+    v: common_vendor.o(($event) => $data.evaluationComments = $event.detail.value),
+    w: common_vendor.o((...args) => $options.handleSubmit && $options.handleSubmit(...args))
+  };
+}
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
+wx.createPage(MiniProgramPage);

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

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

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/ResumeEvaluation/ResumeEvaluation.wxml


+ 271 - 0
unpackage/dist/dev/mp-weixin/pages/ResumeEvaluation/ResumeEvaluation.wxss

@@ -0,0 +1,271 @@
+
+.max-w-4xl {
+	max-width: 56rem;
+	margin: 0 auto;
+}
+.p-6 {
+	padding: 1.5rem;
+}
+.relative {
+	position: relative;
+}
+
+/* .bg-\[\#1d1e3a\] {
+	background-color: #1d1e3a;
+} */
+.text-white {
+	color: white;
+}
+.p-4 {
+	padding: 1rem;
+}
+.rounded-t-lg {
+	border-top-left-radius: 0.5rem;
+	border-top-right-radius: 0.5rem;
+}
+.flex {
+	display: flex;
+}
+.justify-between {
+	justify-content: space-between;
+}
+.items-center {
+	align-items: center;
+}
+.text-sm {
+	font-size: 0.875rem;
+}
+.bg-white {
+	background-color: white;
+}
+.rounded-b-lg {
+	border-bottom-left-radius: 0.5rem;
+	border-bottom-right-radius: 0.5rem;
+}
+.shadow-lg {
+	box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
+.mb-8 {
+	margin-bottom: 2rem;
+}
+.pb-4 {
+	padding-bottom: 1rem;
+}
+.border-b {
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	border-bottom-color: #e5e7eb;
+}
+
+/* .space-x-4 > * + * {
+	margin-left: 1rem;
+} */
+.w-20 {
+	width: 5rem;
+}
+.h-20 {
+	height: 5rem;
+}
+.bg-gray-200 {
+	background-color: #e5e7eb;
+}
+.rounded-lg {
+	border-radius: 0.5rem;
+}
+.overflow-hidden {
+	overflow: hidden;
+}
+.w-full {
+	width: 100%;
+}
+.h-full {
+	height: 100%;
+}
+.object-cover {
+	object-fit: cover;
+}
+.text-2xl {
+	font-size: 1.5rem;
+}
+.font-bold {
+	font-weight: bold;
+}
+.text-gray-600 {
+	color: #4b5563;
+}
+.text-right {
+	text-align: right;
+}
+.text-4xl {
+	font-size: 2.25rem;
+}
+.text-yellow-500 {
+	color: #f59e0b;
+}
+.ml-1 {
+	margin-left: 0.25rem;
+}
+.text-base {
+	font-size: 1rem;
+}
+.grid {
+	display: grid;
+}
+.grid-cols-1 {
+	grid-template-columns: repeat(1, minmax(0, 1fr));
+}
+.grid-cols-2 {
+	grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+.grid-cols-3 {
+	grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+.gap-4 {
+	gap: 1rem;
+}
+.gap-2 {
+	gap: 0.5rem;
+}
+.gap-8 {
+	gap: 2rem;
+}
+.text-xl {
+	font-size: 1.25rem;
+}
+.mb-6 {
+	margin-bottom: 1.5rem;
+}
+.mb-4 {
+	margin-bottom: 1rem;
+}
+.mb-2 {
+	margin-bottom: 0.5rem;
+}
+
+/* .space-y-6 > * + * {
+	margin-top: 1.5rem;
+}
+
+.space-y-4 > * + * {
+	margin-top: 1rem;
+}
+
+.space-y-8 > * + * {
+	margin-top: 2rem;
+} */
+.w-32 {
+	width: 8rem;
+}
+.text-red-500 {
+	color: #ef4444;
+}
+.text-green-500 {
+	color: #10b981;
+}
+.text-sm {
+	font-size: 0.875rem;
+}
+.font-semibold {
+	font-weight: 600;
+}
+.mt-2 {
+	margin-top: 0.5rem;
+}
+.text-gray-500 {
+	color: #6b7280;
+}
+.mt-4 {
+	margin-top: 1rem;
+}
+.video-container {
+	position: relative;
+	width: 100%;
+	max-width: 240px;
+	aspect-ratio: 3/4;
+	margin: 0 auto;
+	background: #000;
+	border-radius: 12px;
+	overflow: hidden;
+}
+.justify-end {
+	justify-content: flex-end;
+}
+.text-blue-500 {
+	color: #3b82f6;
+}
+.text-lg {
+	font-size: 1.125rem;
+}
+.bg-gray-50 {
+	background-color: #f9fafb;
+}
+.aspect-square {
+	aspect-ratio: 1 / 1;
+}
+.bg-gray-100 {
+	background-color: #f3f4f6;
+}
+.form-item {
+	margin-bottom: 1.5rem;
+}
+.border {
+	border-width: 1px;
+	border-style: solid;
+	border-color: #e5e7eb;
+}
+.rounded {
+	border-radius: 0.25rem;
+}
+.bg-blue-500 {
+	background-color: #3b82f6;
+}
+.px-4 {
+	padding-left: 1rem;
+	padding-right: 1rem;
+}
+.py-2 {
+	padding-top: 0.5rem;
+	padding-bottom: 0.5rem;
+}
+.fixed-buttons {
+	position: fixed;
+	right: 2rem;
+	bottom: 6rem;
+	display: flex;
+	flex-direction: column;
+	gap: 1rem;
+	z-index: 100;
+}
+.action-button {
+	width: 40px;
+	height: 40px;
+	border-radius: 50%;
+	background-color: #3b82f6;
+	color: white;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+}
+
+/* 为了模拟图标,你可能需要引入字体图标或使用其他方式 */
+@font-face {
+	font-family: "iconfont";
+	src: url('data:font/woff2;charset=utf-8;base64,...') format('woff2');
+}
+.iconfont {
+	font-family: "iconfont" !important;
+	font-style: normal;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+}
+
+/* 媒体查询适配移动端 */
+@media (max-width: 768px) {
+.p-6 {
+		padding: 1rem;
+}
+.fixed-buttons {
+		right: 1rem;
+}
+}

+ 67 - 28
unpackage/dist/dev/mp-weixin/pages/camera/camera.js

@@ -228,7 +228,20 @@ const _sfc_main = {
         return;
       }
       if (this.currentQuestionIndex >= this.questions.length - 1) {
-        this.showEndModal = true;
+        common_vendor.index.navigateTo({
+          url: "/pages/interview/interview",
+          // 假设这是手部照片采集页面的路径
+          success: () => {
+            console.log("成功跳转到手部照片采集页面");
+          },
+          fail: (err) => {
+            console.error("跳转失败:", err);
+            common_vendor.index.showToast({
+              title: "跳转失败,请手动返回首页",
+              icon: "none"
+            });
+          }
+        });
         return;
       }
       this.goToNextQuestion();
@@ -372,10 +385,16 @@ const _sfc_main = {
         icon: "none"
       });
     },
-    back() {
+    back(target) {
       if (this.timerInterval) {
         clearInterval(this.timerInterval);
       }
+      if (target) {
+        common_vendor.index.navigateTo({
+          url: target
+        });
+        return;
+      }
       try {
         const pages = getCurrentPages();
         if (pages.length > 1) {
@@ -415,8 +434,8 @@ const _sfc_main = {
         this.aiVideoContext.pause();
       }
     },
-    // 重新开始测试
-    restartTest() {
+    // 修改 restartTest 方法,添加可选的跳转目标
+    restartTest(target) {
       this.currentQuestionIndex = 0;
       this.score = 0;
       this.showEndModal = false;
@@ -424,6 +443,11 @@ const _sfc_main = {
       this.selectedOption = null;
       this.selectedOptions = [];
       this.resetTimer();
+      if (target) {
+        common_vendor.index.navigateTo({
+          url: target
+        });
+      }
     },
     // 在methods中添加测试方法
     testEndScreen() {
@@ -440,6 +464,23 @@ const _sfc_main = {
       if (webview) {
         webview.evalJS(`receiveMessage('${message}')`);
       }
+    },
+    // 添加 navigateToInterview 方法
+    navigateToInterview() {
+      this.showEndModal = false;
+      common_vendor.index.navigateTo({
+        url: "/pages/interview/interview",
+        success: () => {
+          console.log("成功跳转到interview页面");
+        },
+        fail: (err) => {
+          console.error("跳转失败:", err);
+          common_vendor.index.showToast({
+            title: "跳转失败,请手动返回首页",
+            icon: "none"
+          });
+        }
+      });
     }
   },
   // 添加生命周期钩子,确保在组件销毁时清除计时器
@@ -462,19 +503,18 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     c: common_assets._imports_0
   }, {
     d: common_vendor.t($data.currentQuestionIndex + 1),
-    e: common_vendor.t($options.currentQuestion.id),
-    f: common_vendor.t($data.questions.length),
-    g: $options.currentQuestion.isImportant
+    e: common_vendor.t($data.questions.length),
+    f: $options.currentQuestion.isImportant
   }, $options.currentQuestion.isImportant ? {} : {}, {
-    h: common_vendor.t($options.currentQuestion.questionTypeName),
-    i: common_vendor.t($options.currentQuestion.text),
-    j: $options.currentQuestion.questionType == 0
+    g: common_vendor.t($options.currentQuestion.questionTypeName),
+    h: common_vendor.t($options.currentQuestion.text),
+    i: $options.currentQuestion.questionType == 0
   }, $options.currentQuestion.questionType == 0 ? {
-    k: $data.openQuestionAnswer,
-    l: common_vendor.o(($event) => $data.openQuestionAnswer = $event.detail.value),
-    m: common_vendor.t($data.openQuestionAnswer.length)
+    j: $data.openQuestionAnswer,
+    k: common_vendor.o(($event) => $data.openQuestionAnswer = $event.detail.value),
+    l: common_vendor.t($data.openQuestionAnswer.length)
   } : {
-    n: common_vendor.f($options.currentQuestion.options, (option, index, i0) => {
+    m: common_vendor.f($options.currentQuestion.options, (option, index, i0) => {
       return {
         a: common_vendor.t(option.option_text || (typeof option === "string" ? option : JSON.stringify(option))),
         b: index,
@@ -484,33 +524,32 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
         f: common_vendor.o(($event) => $options.selectOption(index), index)
       };
     }),
-    o: common_vendor.t($options.currentQuestion.questionType === 1 ? "●" : "☐")
+    n: common_vendor.t($options.currentQuestion.questionType === 1 ? "●" : "☐")
   }, {
-    p: common_vendor.t($data.showResult ? "下一题" : "提交答案"),
-    q: common_vendor.o(($event) => $options.nextQuestion(_ctx.option)),
-    r: $options.currentQuestion.questionType === 0 && $data.openQuestionAnswer.trim() === "" || $options.currentQuestion.questionType === 1 && $data.selectedOption === null || $options.currentQuestion.questionType === 2 && $data.selectedOptions.length === 0,
-    s: $data.showEndModal
+    o: common_vendor.t($data.showResult ? "下一题" : "提交答案"),
+    p: common_vendor.o(($event) => $options.nextQuestion(_ctx.option)),
+    q: $options.currentQuestion.questionType === 0 && $data.openQuestionAnswer.trim() === "" || $options.currentQuestion.questionType === 1 && $data.selectedOption === null || $options.currentQuestion.questionType === 2 && $data.selectedOptions.length === 0,
+    r: $data.showEndModal
   }, $data.showEndModal ? {
-    t: common_vendor.o((...args) => $options.restartTest && $options.restartTest(...args)),
-    v: common_vendor.o((...args) => $options.back && $options.back(...args))
+    s: common_vendor.o((...args) => $options.navigateToInterview && $options.navigateToInterview(...args))
   } : {}, {
-    w: $data.interviewCompleted
+    t: $data.interviewCompleted
   }, $data.interviewCompleted ? {
-    x: common_assets._imports_0,
-    y: common_vendor.o((...args) => $options.back && $options.back(...args))
+    v: common_assets._imports_0,
+    w: common_vendor.o((...args) => $options.back && $options.back(...args))
   } : {}, {
-    z: $data.loading
+    x: $data.loading
   }, $data.loading ? {
-    A: common_vendor.p({
+    y: common_vendor.p({
       status: "loading",
       contentText: {
         contentdown: "加载中..."
       }
     })
   } : {}, {
-    B: !$data.loading && $data.loadError
+    z: !$data.loading && $data.loadError
   }, !$data.loading && $data.loadError ? {
-    C: common_vendor.o((...args) => $options.fetchInterviewData && $options.fetchInterviewData(...args))
+    A: common_vendor.o((...args) => $options.fetchInterviewData && $options.fetchInterviewData(...args))
   } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/camera/camera.wxml


+ 38 - 3
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.js

@@ -101,7 +101,11 @@ const _sfc_main = {
       recordedChunks: [],
       recorder: null,
       lastUploadedVideoUrl: "",
-      showStartRecordingButton: false
+      showStartRecordingButton: false,
+      showRetryButton: false,
+      // 控制重试按钮显示
+      lastVideoToRetry: null
+      // 存储上次失败的视频URL,用于重试
     };
   },
   mounted() {
@@ -914,6 +918,10 @@ const _sfc_main = {
     // 修改 submitVideoToInterview 方法
     submitVideoToInterview(videoUrl) {
       console.log("提交视频URL到面试接口:", videoUrl);
+      common_vendor.index.showLoading({
+        title: "正在提交...",
+        mask: true
+      });
       let questionId;
       switch (this.currentVideoIndex) {
         case 1:
@@ -935,15 +943,16 @@ const _sfc_main = {
         application_id: 1,
         question_id: questionId,
         video_url: videoUrl,
-        video_duration: 0,
         tenant_id: common_vendor.index.getStorageSync("tenant_id") || "1"
       };
       common_vendor.index.request({
         url: "http://192.168.66.187:8083/api/job/upload_question_video",
         method: "POST",
         data: requestData,
+        // 使用data而不是formData
         header: {
-          "content-type": "application/json"
+          "content-type": "application/x-www-form-urlencoded"
+          // 修改为form-data格式的content-type
         },
         success: (res) => {
           console.log("面试接口提交成功:", res);
@@ -954,6 +963,8 @@ const _sfc_main = {
               icon: "success"
             });
             this.lastUploadedVideoUrl = videoUrl;
+            this.showRetryButton = false;
+            this.lastVideoToRetry = null;
             setTimeout(() => {
               if (this.currentVideoIndex === 4) {
                 common_vendor.index.navigateTo({
@@ -968,6 +979,8 @@ const _sfc_main = {
               title: res.data.msg || "提交失败,请重试",
               icon: "none"
             });
+            this.lastVideoToRetry = videoUrl;
+            this.showRetryButton = true;
           }
         },
         fail: (err) => {
@@ -977,6 +990,8 @@ const _sfc_main = {
             title: "网络错误,请重试",
             icon: "none"
           });
+          this.lastVideoToRetry = videoUrl;
+          this.showRetryButton = true;
         }
       });
     },
@@ -1210,6 +1225,22 @@ const _sfc_main = {
           }
         }
       });
+    },
+    // 添加重试上传方法
+    retryVideoUpload() {
+      if (this.lastVideoToRetry) {
+        this.showRetryButton = false;
+        common_vendor.index.showLoading({
+          title: "正在重新提交...",
+          mask: true
+        });
+        this.submitVideoToInterview(this.lastVideoToRetry);
+      } else {
+        common_vendor.index.showToast({
+          title: "没有可重试的视频",
+          icon: "none"
+        });
+      }
     }
   }
 };
@@ -1266,6 +1297,10 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     v: $data.showStartRecordingButton
   }, $data.showStartRecordingButton ? {
     w: common_vendor.o((...args) => $options.handleStartRecordingClick && $options.handleStartRecordingClick(...args))
+  } : {}, {
+    x: $data.showRetryButton
+  }, $data.showRetryButton ? {
+    y: common_vendor.o((...args) => $options.retryVideoUpload && $options.retryVideoUpload(...args))
   } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-464e78c6"]]);

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/identity-verify/identity-verify.wxml


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

@@ -269,3 +269,23 @@ video.data-v-464e78c6::-webkit-media-controls-fullscreen-button {
 }
 
 /* 可以删除pulse-green动画,直接使用与开始面试按钮相同的pulse动画 */
+.retry-button-container.data-v-464e78c6 {
+  position: absolute;
+  bottom: 50px;
+  left: 50%;
+  transform: translateX(-50%);
+  z-index: 20;
+}
+.retry-button.data-v-464e78c6 {
+  padding: 10px 20px;
+  background-color: #3498db; /* 蓝色背景 */
+  color: white;
+  font-size: 16px;
+  border: none;
+  border-radius: 4px;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+  cursor: pointer;
+}
+.retry-button.data-v-464e78c6:hover {
+  background-color: #2980b9;
+}

+ 423 - 97
unpackage/dist/dev/mp-weixin/pages/interview/interview.js

@@ -3,141 +3,467 @@ 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"
+      currentStep: 0,
+      photos: [null, null, null, null, null, null],
+      // 存储6张照片:左手正面、左手反面、左手握拳、右手正面、右手反面、右手握拳
+      instructions: [
+        "请将左手掌正面朝上,保持手指自然伸展,确保整个手掌清晰可见",
+        "请将左手掌反面朝上,保持手指自然伸展,确保整个手背清晰可见",
+        "请握紧左手拳头,使拳面朝向相机,确保整个拳头清晰可见",
+        "请将右手掌正面朝上,保持手指自然伸展,确保整个手掌清晰可见",
+        "请将右手掌反面朝上,保持手指自然伸展,确保整个手背清晰可见",
+        "请握紧右手拳头,使拳面朝向相机,确保整个拳头清晰可见"
+      ],
+      previewTexts: [
+        "左手掌正面照片预览区",
+        "左手掌反面照片预览区",
+        "左手握拳照片预览区",
+        "右手掌正面照片预览区",
+        "右手掌反面照片预览区",
+        "右手握拳照片预览区"
+      ],
+      photoTypes: ["left_palm", "left_back", "left_fist", "right_palm", "right_back", "right_fist"],
+      // 照片类型标识
+      isH5: false,
+      showCamera: false,
+      mediaStream: null,
+      cameraContext: null,
+      cameraMode: "normal",
+      // 添加相机模式
+      cameraInitRetries: 0,
+      // 添加重试计数器
+      gestureOverlays: [
+        /* '/static/images/palm_overlay.png',  // 手掌正面引导图
+        '/static/images/back_overlay.png',  // 手掌反面引导图
+        '/static/images/fist_overlay.png'   // 握拳引导图 */
+      ],
+      gestureHints: [
+        "请将左手掌对准轮廓线",
+        "请将左手背对准轮廓线",
+        "请将左手拳头对准轮廓线",
+        "请将右手掌对准轮廓线",
+        "请将右手背对准轮廓线",
+        "请将右手拳头对准轮廓线"
+      ],
+      photoLinks: [null, null, null, null, null, null]
+      // 存储上传后的图片链接
     };
   },
+  computed: {
+    canGoNext() {
+      return this.photos[this.currentStep] !== null;
+    }
+  },
   onLoad() {
-    this.recorderManager = common_vendor.index.getRecorderManager();
-    this.innerAudioContext = common_vendor.index.createInnerAudioContext();
-    this.recorderManager.onStop((res) => {
-      this.processAudio(res.tempFilePath);
+    common_vendor.index.authorize({
+      scope: "scope.camera",
+      success: () => {
+        console.log("相机权限已授权");
+      },
+      fail: () => {
+        common_vendor.index.showModal({
+          title: "提示",
+          content: "请授权相机权限以完成手部照片采集",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.openSetting();
+            }
+          }
+        });
+      }
     });
   },
+  beforeDestroy() {
+    this.stopMediaStream();
+  },
   methods: {
-    startRecording() {
-      this.isRecording = true;
-      this.recorderManager.start({
-        format: "mp3",
-        sampleRate: 16e3,
-        numberOfChannels: 1
-      });
-    },
-    stopRecording() {
-      this.isRecording = false;
-      this.recorderManager.stop();
+    takePhoto() {
+      if (this.isH5) {
+        this.startCamera();
+      } else {
+        this.showCamera = true;
+        this.cameraInitRetries = 0;
+        this.$nextTick(() => {
+          try {
+            this.cameraContext = common_vendor.index.createCameraContext();
+            console.log("相机上下文创建成功");
+          } catch (err) {
+            console.error("创建相机上下文失败:", err);
+            this.handleCameraInitError();
+          }
+        });
+      }
     },
-    processAudio(filePath) {
+    uploadPhoto(filePathOrData, type) {
+      console.log(`准备上传${type}类型的手部照片`);
       common_vendor.index.showLoading({
-        title: "处理中..."
+        title: "上传中..."
       });
-      this.speechToText(filePath).then((text) => {
-        this.addMessage("user", text);
-        this.callDashScopeAPI(text);
-      }).catch((err) => {
+      const userInfo = common_vendor.index.getStorageSync("userInfo");
+      userInfo ? JSON.parse(userInfo).openid || "" : "";
+      const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
+      if (!tenant_id) {
         common_vendor.index.hideLoading();
         common_vendor.index.showToast({
-          title: "语音识别失败",
+          title: "用户信息不完整,请重新登录",
           icon: "none"
         });
-        console.error(err);
-      });
+        return;
+      }
+      if (this.isH5 && filePathOrData.startsWith("data:image")) {
+        const base64Data = filePathOrData.split(",")[1];
+        const blob = this.base64ToBlob(base64Data, "image/jpeg");
+        const formData = new FormData();
+        formData.append("photo_file", blob, `${type}.jpg`);
+        formData.append("application_id", "1");
+        formData.append("tenant_id", tenant_id);
+        formData.append("description", `手部照片-${type}`);
+        console.log("H5上传参数:", {
+          application_id: "1",
+          tenant_id,
+          description: `手部照片-${type}`
+        });
+        fetch("http://192.168.66.187:8083/job/upload_posture_photo", {
+          method: "POST",
+          body: formData
+        }).then((response) => response.json()).then((result) => {
+          console.log("照片上传成功:", result);
+          const index = this.photoTypes.indexOf(type);
+          if (index !== -1 && result.data && result.data.url) {
+            this.$set(this.photoLinks, index, result.data.url);
+            console.log(`已保存${type}类型照片链接:`, result.data.url);
+          }
+          common_vendor.index.hideLoading();
+          common_vendor.index.showToast({
+            title: "照片上传成功",
+            icon: "success"
+          });
+        }).catch((error) => {
+          console.error("照片上传失败:", error);
+          common_vendor.index.hideLoading();
+          common_vendor.index.showToast({
+            title: "照片上传失败,请重试",
+            icon: "none"
+          });
+        });
+      } else {
+        console.log(`小程序环境上传文件路径:`, filePathOrData);
+        console.log("小程序上传参数:", {
+          application_id: "1",
+          tenant_id,
+          description: `手部照片-${type}`
+        });
+        common_vendor.index.uploadFile({
+          url: "http://192.168.66.187:8083/job/upload_posture_photo",
+          filePath: filePathOrData,
+          name: "photo_file",
+          // 确保文件参数名称正确
+          formData: {
+            // 使用formData替代data以确保参数正确传递
+            "application_id": "1",
+            "tenant_id": tenant_id,
+            "description": `手部照片-${type}`
+          },
+          success: (uploadRes) => {
+            console.log("照片上传成功:", uploadRes);
+            let result;
+            try {
+              if (typeof uploadRes.data === "string") {
+                result = JSON.parse(uploadRes.data);
+              } else {
+                result = uploadRes.data;
+              }
+              console.log("解析后的上传结果:", result);
+              const index = this.photoTypes.indexOf(type);
+              if (index !== -1 && result.data && result.data.url) {
+                this.$set(this.photoLinks, index, result.data.url);
+                console.log(`已保存${type}类型照片链接:`, result.data.url);
+              }
+            } catch (e) {
+              console.error("解析上传结果失败:", e);
+            }
+            common_vendor.index.hideLoading();
+            common_vendor.index.showToast({
+              title: "照片上传成功",
+              icon: "success"
+            });
+          },
+          fail: (err) => {
+            console.error("照片上传失败:", err);
+            common_vendor.index.hideLoading();
+            common_vendor.index.showToast({
+              title: "照片上传失败,请重试",
+              icon: "none"
+            });
+          },
+          complete: () => {
+            common_vendor.index.hideLoading();
+          }
+        });
+      }
+    },
+    prevStep() {
+      if (this.currentStep > 0) {
+        this.currentStep--;
+      }
+    },
+    nextStep() {
+      if (this.currentStep < 5) {
+        this.currentStep++;
+      } else if (this.currentStep === 5 && this.photos[5]) {
+        this.submitAllPhotos();
+      }
+    },
+    previewPhoto(index) {
+      if (this.photos[index]) {
+        common_vendor.index.previewImage({
+          urls: [this.photos[index]],
+          current: this.photos[index]
+        });
+      }
     },
-    speechToText(filePath) {
-      return new Promise((resolve) => {
-        console.log("录音文件路径:", filePath);
+    submitAllPhotos() {
+      if (this.photos.every((photo) => photo !== null)) {
+        common_vendor.index.showLoading({
+          title: "处理中..."
+        });
         setTimeout(() => {
-          resolve("你好,我需要帮助");
+          common_vendor.index.hideLoading();
+          common_vendor.index.showToast({
+            title: "手部照片采集完成",
+            icon: "success",
+            duration: 2e3,
+            success: () => {
+              setTimeout(() => {
+                common_vendor.index.navigateTo({
+                  url: "/pages/ResumeEvaluation/ResumeEvaluation"
+                });
+              }, 2e3);
+            }
+          });
         }, 500);
-      });
+      } else {
+        common_vendor.index.showToast({
+          title: "请完成所有照片拍摄",
+          icon: "none"
+        });
+      }
+    },
+    // H5环境下启动摄像头
+    startCamera() {
+      this.showCamera = true;
+      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+        navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then((stream) => {
+          this.mediaStream = stream;
+          this.$nextTick(() => {
+            const videoElement = this.$refs.videoElement;
+            if (videoElement) {
+              videoElement.srcObject = stream;
+            }
+          });
+        }).catch((error) => {
+          console.error("获取摄像头失败:", error);
+          common_vendor.index.showToast({
+            title: "无法访问摄像头,请检查权限设置",
+            icon: "none"
+          });
+          this.showCamera = false;
+        });
+      } else {
+        common_vendor.index.showToast({
+          title: "您的浏览器不支持摄像头功能",
+          icon: "none"
+        });
+        this.showCamera = false;
+      }
     },
-    callDashScopeAPI(userInput) {
-      const requestBody = {
-        model: "qwen-plus",
-        messages: [
-          {
-            role: "system",
-            content: "You are a helpful assistant."
+    // H5环境下拍照
+    capturePhoto() {
+      const videoElement = this.$refs.videoElement;
+      const canvasElement = this.$refs.canvasElement;
+      if (videoElement && canvasElement) {
+        const context = canvasElement.getContext("2d");
+        canvasElement.width = videoElement.videoWidth;
+        canvasElement.height = videoElement.videoHeight;
+        context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
+        const photoData = canvasElement.toDataURL("image/jpeg");
+        this.$set(this.photos, this.currentStep, photoData);
+        this.uploadPhoto(photoData, this.photoTypes[this.currentStep]);
+        this.stopMediaStream();
+        this.showCamera = false;
+      }
+    },
+    // 小程序环境下拍照
+    capturePhotoMP() {
+      if (!this.cameraContext) {
+        console.error("相机上下文不存在");
+        this.handleCameraInitError();
+        return;
+      }
+      try {
+        this.cameraContext.takePhoto({
+          quality: "high",
+          success: (res) => {
+            console.log("拍照成功:", res);
+            this.$set(this.photos, this.currentStep, res.tempImagePath);
+            this.uploadPhoto(res.tempImagePath, this.photoTypes[this.currentStep]);
+            this.showCamera = false;
           },
-          {
-            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 {
+          fail: (err) => {
+            console.error("拍照失败:", err);
             common_vendor.index.showToast({
-              title: "获取回复失败",
+              title: "拍照失败,请重试",
               icon: "none"
             });
-            console.error("API响应错误:", res);
+            this.fallbackToChooseImage();
           }
+        });
+      } catch (err) {
+        console.error("takePhoto方法执行失败:", err);
+        this.fallbackToChooseImage();
+      }
+    },
+    // 添加相机初始化错误处理
+    handleCameraInitError() {
+      if (this.cameraInitRetries < 3) {
+        this.cameraInitRetries++;
+        common_vendor.index.showToast({
+          title: `相机初始化失败,正在重试(${this.cameraInitRetries}/3)`,
+          icon: "none"
+        });
+        setTimeout(() => {
+          this.showCamera = false;
+          setTimeout(() => {
+            this.takePhoto();
+          }, 500);
+        }, 1e3);
+      } else {
+        common_vendor.index.showToast({
+          title: "相机初始化失败,将使用系统相机",
+          icon: "none"
+        });
+        this.fallbackToChooseImage();
+      }
+    },
+    // 添加备用的系统相机方法
+    fallbackToChooseImage() {
+      this.showCamera = false;
+      common_vendor.index.chooseImage({
+        count: 1,
+        sourceType: ["camera"],
+        success: (res) => {
+          this.$set(this.photos, this.currentStep, res.tempFilePaths[0]);
+          this.uploadPhoto(res.tempFilePaths[0], this.photoTypes[this.currentStep]);
         },
         fail: (err) => {
-          common_vendor.index.hideLoading();
+          console.error("选择图片失败:", err);
           common_vendor.index.showToast({
-            title: "网络请求失败",
+            title: "拍照失败,请重试",
             icon: "none"
           });
-          console.error(err);
         }
       });
     },
-    addMessage(role, content) {
-      this.messages.push({ role, content });
-      this.$nextTick(() => {
-        this.scrollTop = 9999;
+    // 修改相机错误处理方法
+    handleCameraError(e) {
+      console.error("相机错误:", e.detail);
+      const errorInfo = JSON.stringify(e.detail);
+      console.log("详细错误信息:", errorInfo);
+      common_vendor.index.showToast({
+        title: "相机启动失败,将使用系统相机",
+        icon: "none"
       });
+      this.fallbackToChooseImage();
     },
-    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();
-          }
+    // 取消拍照 - 更新为同时支持H5和小程序
+    cancelCamera() {
+      if (this.isH5) {
+        this.stopMediaStream();
+      }
+      this.showCamera = false;
+    },
+    // 停止摄像头流
+    stopMediaStream() {
+      if (this.mediaStream) {
+        this.mediaStream.getTracks().forEach((track) => {
+          track.stop();
+        });
+        this.mediaStream = null;
+      }
+    },
+    // base64转blob工具方法
+    base64ToBlob(base64, mimeType) {
+      const byteCharacters = atob(base64);
+      const byteArrays = [];
+      for (let offset = 0; offset < byteCharacters.length; offset += 512) {
+        const slice = byteCharacters.slice(offset, offset + 512);
+        const byteNumbers = new Array(slice.length);
+        for (let i = 0; i < slice.length; i++) {
+          byteNumbers[i] = slice.charCodeAt(i);
         }
-      });
+        const byteArray = new Uint8Array(byteNumbers);
+        byteArrays.push(byteArray);
+      }
+      return new Blob(byteArrays, { type: mimeType });
+    },
+    retryCamera() {
+      this.showCamera = false;
+      setTimeout(() => {
+        this.takePhoto();
+      }, 500);
+    },
+    // 可以添加一个方法来切换蒙层显示
+    toggleGestureOverlay() {
     }
   }
 };
 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)
-      };
+  return common_vendor.e({
+    a: common_vendor.t($data.instructions[$data.currentStep]),
+    b: $data.photos[$data.currentStep]
+  }, $data.photos[$data.currentStep] ? {
+    c: $data.photos[$data.currentStep]
+  } : {
+    d: common_vendor.t($data.previewTexts[$data.currentStep])
+  }, {
+    e: common_vendor.f($data.photos, (photo, index, i0) => {
+      return common_vendor.e({
+        a: photo
+      }, photo ? {
+        b: photo,
+        c: common_vendor.o(($event) => $options.previewPhoto(index), index)
+      } : {
+        d: common_vendor.t(index + 1)
+      }, {
+        e: index,
+        f: $data.currentStep === index ? 1 : "",
+        g: photo ? 1 : ""
+      });
     }),
-    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))
-  };
+    f: common_vendor.t($data.photos[$data.currentStep] ? "重新拍摄" : "拍摄照片"),
+    g: common_vendor.o((...args) => $options.takePhoto && $options.takePhoto(...args)),
+    h: $data.currentStep === 0,
+    i: common_vendor.o((...args) => $options.prevStep && $options.prevStep(...args)),
+    j: common_vendor.t($data.currentStep === 5 && $data.photos[5] ? "完成" : "下一步"),
+    k: !$options.canGoNext,
+    l: common_vendor.o((...args) => $options.nextStep && $options.nextStep(...args)),
+    m: !$data.isH5 && $data.showCamera
+  }, !$data.isH5 && $data.showCamera ? {
+    n: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args)),
+    o: $data.cameraMode,
+    p: $data.gestureOverlays[$data.currentStep],
+    q: common_vendor.t($data.gestureHints[$data.currentStep]),
+    r: common_vendor.o((...args) => $options.capturePhotoMP && $options.capturePhotoMP(...args)),
+    s: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args))
+  } : {}, {
+    t: $data.isH5
+  }, $data.isH5 ? {
+    v: $data.gestureOverlays[$data.currentStep],
+    w: common_vendor.t($data.gestureHints[$data.currentStep]),
+    x: common_vendor.o((...args) => $options.capturePhoto && $options.capturePhoto(...args)),
+    y: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args)),
+    z: $data.showCamera
+  } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
 wx.createPage(MiniProgramPage);

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

@@ -1,4 +1,4 @@
 {
-  "navigationBarTitleText": "",
+  "navigationBarTitleText": "手部照片采集",
   "usingComponents": {}
 }

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

@@ -1 +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>
+<view class="container"><view class="photo-container"><view class="instruction"><text>{{a}}</text></view><view class="preview-container"><image wx:if="{{b}}" src="{{c}}" mode="aspectFit" class="preview-image"></image><view wx:else class="empty-preview"><text>{{d}}</text></view></view><view class="thumbnails"><view wx:for="{{e}}" wx:for-item="photo" wx:key="e" class="{{['thumbnail-item', photo.f && 'active', photo.g && 'completed']}}"><image wx:if="{{photo.a}}" src="{{photo.b}}" mode="aspectFill" class="thumbnail-image" bindtap="{{photo.c}}"></image><view wx:else class="thumbnail-placeholder">{{photo.d}}</view></view></view></view><view class="controls"><button class="camera-btn" bindtap="{{g}}">{{f}}</button><view class="navigation-btns"><button class="nav-btn prev" disabled="{{h}}" bindtap="{{i}}">上一步</button><button class="nav-btn next" disabled="{{k}}" bindtap="{{l}}">{{j}}</button></view></view><view wx:if="{{m}}" class="camera-container"><camera device-position="back" flash="auto" binderror="{{n}}" class="camera-component" mode="{{o}}" frame-size="medium"></camera><cover-view class="gesture-overlay"><cover-image src="{{p}}" class="gesture-image"></cover-image><cover-view class="gesture-hint"><cover-view class="gesture-hint-text">{{q}}</cover-view></cover-view></cover-view><cover-view class="camera-controls"><cover-view class="capture-btn" bindtap="{{r}}"></cover-view><cover-view class="cancel-btn" bindtap="{{s}}">取消</cover-view></cover-view></view><view wx:if="{{t}}" class="camera-container" hidden="{{!z}}"><video ref="videoElement" class="camera-video" autoplay></video><canvas ref="canvasElement" class="camera-canvas" style="display:none"></canvas><view class="gesture-overlay"><image src="{{v}}" class="gesture-image"></image><view class="gesture-hint"><text class="gesture-hint-text">{{w}}</text></view></view><button class="capture-btn" bindtap="{{x}}">拍照</button><button class="cancel-btn" bindtap="{{y}}">取消</button></view></view>

+ 165 - 21
unpackage/dist/dev/mp-weixin/pages/interview/interview.wxss

@@ -2,7 +2,7 @@
 .container {
 	display: flex;
 	flex-direction: column;
-	height: 100vh;
+	height: 94vh;
 	background-color: #f5f5f5;
 }
 .header {
@@ -15,46 +15,190 @@
 	font-size: 36rpx;
 	font-weight: bold;
 }
-.chat-container {
+.photo-container {
 	flex: 1;
 	padding: 20rpx;
+	display: flex;
+	flex-direction: column;
+}
+.instruction {
+	padding: 20rpx;
+	background-color: #E5E5EA;
+	border-radius: 10rpx;
+	margin-bottom: 20rpx;
+}
+.preview-container {
+	flex: 1;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	background-color: #E5E5EA;
+	border-radius: 10rpx;
+	margin-bottom: 20rpx;
 	overflow: hidden;
 }
-.chat-messages {
+.preview-image {
+	width: 100%;
+	height: 100%;
+}
+.empty-preview {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	width: 100%;
 	height: 100%;
+	color: #999;
 }
-.message {
+.thumbnails {
+	display: flex;
+	justify-content: space-around;
 	margin-bottom: 20rpx;
-	padding: 20rpx;
+}
+.thumbnail-item {
+	width: 150rpx;
+	height: 150rpx;
 	border-radius: 10rpx;
-	max-width: 80%;
-	word-break: break-word;
+	overflow: hidden;
+	background-color: #E5E5EA;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	border: 4rpx solid transparent;
 }
-.message.user {
-	align-self: flex-end;
-	background-color: #007AFF;
-	color: white;
-	margin-left: auto;
+.thumbnail-item.active {
+	border-color: #007AFF;
 }
-.message.assistant {
-	align-self: flex-start;
-	background-color: #E5E5EA;
-	color: #333;
+.thumbnail-item.completed {
+	background-color: #ffffff;
+}
+.thumbnail-image {
+	width: 100%;
+	height: 100%;
+}
+.thumbnail-placeholder {
+	font-size: 40rpx;
+	color: #999;
 }
 .controls {
 	padding: 20rpx;
 	background-color: #ffffff;
-	border-top: 1px solid #e0e0e0;
+	border-top: 1px solid #6c5ce7;
 }
-.voice-btn {
+.camera-btn {
 	width: 100%;
 	height: 80rpx;
 	line-height: 80rpx;
 	text-align: center;
-	background-color: #007AFF;
+	background-color: #6c5ce7;
+	color: white;
+	border-radius: 40rpx;
+	margin-bottom: 20rpx;
+}
+.navigation-btns {
+	display: flex;
+	justify-content: space-between;
+}
+.nav-btn {
+	width: 48%;
+	height: 80rpx;
+	line-height: 80rpx;
+	text-align: center;
+	border-radius: 40rpx;
+}
+.nav-btn.prev {
+	background-color: #E5E5EA;
+	color: #333;
+}
+.nav-btn.next {
+	background-color: #34C759;
 	color: white;
+}
+.nav-btn[disabled] {
+	opacity: 0.5;
+}
+
+/* 浏览器摄像头相关样式 */
+.camera-container {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background-color: #000;
+	z-index: 999;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+}
+.camera-video {
+	width: 100%;
+	height: 70%;
+	object-fit: cover;
+}
+.camera-canvas {
+	display: none;
+}
+.capture-btn {
+	width: 200rpx;
+	height: 200rpx;
+	border-radius: 50%;
+	background-color: #ffffff;
+	border: 10rpx solid #6c5ce7;
+	margin: 30rpx 0;
+}
+.cancel-btn {
+	width: 200rpx;
+	height: 80rpx;
+	line-height: 80rpx;
+	background-color: rgba(255, 255, 255, 0.3);
+	color: #ffffff;
+	text-align: center;
 	border-radius: 40rpx;
 }
-.voice-btn.recording {
-	background-color: #FF3B30;
+
+/* 小程序相机组件样式 */
+.camera-component {
+	width: 100%;
+	height: 80%;
+	display: block;
+}
+.camera-controls {
+	width: 100%;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	position: absolute;
+	bottom: 50rpx;
+}
+
+/* 添加手势引导蒙层样式 */
+.gesture-overlay {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 80%;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	pointer-events: none; /* 允许点击穿透 */
+	z-index: 10;
+}
+.gesture-image {
+	width: 70%;
+	height: 70%;
+	opacity: 0.6;
+}
+.gesture-hint {
+	background-color: rgba(0, 0, 0, 0.5);
+	padding: 10rpx 20rpx;
+	border-radius: 10rpx;
+	margin-top: 20rpx;
+}
+.gesture-hint-text {
+	color: white;
+	font-size: 28rpx;
+	text-align: center;
 }

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác