Browse Source

feat page-job-hunting

0235702 21 hours ago
parent
commit
1d777d40a7

+ 1 - 0
interview-web/angular.json

@@ -95,4 +95,5 @@
       }
     }
   }
+  
 }

+ 42 - 0
interview-web/package-lock.json

@@ -15,8 +15,10 @@
         "@angular/platform-browser": "^20.0.0",
         "@angular/router": "^20.0.0",
         "@fortawesome/angular-fontawesome": "^2.0.1",
+        "@fortawesome/fontawesome-free": "^6.7.2",
         "@fortawesome/fontawesome-svg-core": "^6.7.2",
         "@fortawesome/free-solid-svg-icons": "^6.7.2",
+        "echarts": "^5.6.0",
         "rxjs": "~7.8.0",
         "swiper": "^11.2.10",
         "tslib": "^2.3.0",
@@ -1238,6 +1240,15 @@
         "node": ">=6"
       }
     },
+    "node_modules/@fortawesome/fontawesome-free": {
+      "version": "6.7.2",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
+      "integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
+      "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/@fortawesome/fontawesome-svg-core": {
       "version": "6.7.2",
       "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
@@ -4384,6 +4395,22 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/echarts": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
+      "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.1"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -8971,6 +8998,21 @@
       "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
       "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
       "license": "MIT"
+    },
+    "node_modules/zrender": {
+      "version": "5.6.1",
+      "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
+      "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
     }
   }
 }

+ 2 - 0
interview-web/package.json

@@ -27,8 +27,10 @@
     "@angular/platform-browser": "^20.0.0",
     "@angular/router": "^20.0.0",
     "@fortawesome/angular-fontawesome": "^2.0.1",
+    "@fortawesome/fontawesome-free": "^6.7.2",
     "@fortawesome/fontawesome-svg-core": "^6.7.2",
     "@fortawesome/free-solid-svg-icons": "^6.7.2",
+    "echarts": "^5.6.0",
     "rxjs": "~7.8.0",
     "swiper": "^11.2.10",
     "tslib": "^2.3.0",

+ 130 - 1
interview-web/src/modules/interview/mobile/page-interview/page-interview.html

@@ -1 +1,130 @@
-<p>page-interview works!</p>
+<div class="interview-container">
+    <!-- 头部区域 -->
+    <div class="interview-header">
+        <div class="logo">AI面试官</div>
+        <div class="timer">
+            <i class="fas fa-clock"></i> 剩余时间: {{ remainingTime }}
+        </div>
+    </div>
+    
+    <!-- 数字人区域 -->
+    <div class="avatar-section">
+        <div class="avatar-container">
+            <img [src]="avatarImage" alt="AI头像" class="avatar-img" 
+            (error)="avatarImage = 'assets/default-avatar.svg'">
+            <div class="voice-wave" [style.animation]="isListening ? 'wave 2s infinite' : 'none'"></div>
+        </div>
+        <div class="avatar-expression">
+            <span class="expression-dot"></span>
+            <span>{{ expressionText }}</span>
+        </div>
+    </div>
+    
+    <!-- 对话区域 -->
+    <div class="dialog-container" #dialogContainer>
+        @for (message of messages; track message) {
+        <div class="message" [ngClass]="{'message-ai': message.sender === 'ai', 'message-user': message.sender === 'user'}">
+            <div class="message-bubble" [ngClass]="{'ai-bubble': message.sender === 'ai', 'user-bubble': message.sender === 'user'}">
+                {{ message.text }}
+            </div>
+            <div class="message-meta" [ngClass]="{'ai-meta': message.sender === 'ai', 'user-meta': message.sender === 'user'}">
+                <i [class]="message.sender === 'ai' ? 'fas fa-robot' : 'fas fa-user'"></i> 
+                {{ message.sender === 'ai' ? 'AI面试官' : '您' }}
+            </div>
+        </div>}
+    </div>
+    
+    <!-- 问题卡片 (保留但隐藏) -->
+     @if (showQuestionCard) {
+    <div class="question-card">
+        <div class="question-text">
+            {{ currentQuestion }}
+        </div>
+        <div class="question-progress">
+            <span>问题 {{ currentQuestionIndex + 1 }}/{{ questions.length }}</span>
+            <div class="progress-bar">
+                <div class="progress-fill" [style.width]="progress + '%'"></div>
+            </div>
+            <span>{{ progress }}%</span>
+        </div>
+    </div>}
+    
+    <!-- 语音输入区域 -->
+    <div class="voice-input-section">
+        <div class="voice-controls">
+            <button class="voice-btn" 
+                    (mousedown)="startVoiceInput()" 
+                    (mouseup)="stopVoiceInput()"
+                    [class.active]="isListening">
+                <i class="fas fa-microphone"></i>
+                <div class="voice-wave"></div>
+            </button>
+            <button class="voice-btn" 
+                    (click)="playQuestion()"
+                    style="background: linear-gradient(135deg, #48BB78, #38A169);">
+                <i class="fas fa-play"></i>
+            </button>
+        </div>
+        @if (showSubmitButton) {
+        <button class="submit-btn" (click)="submitAnswer()" [disabled]="isAnalyzing">
+            <svg class="check-icon" viewBox="0 0 24 24" width="16" height="16">
+                <path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" style="color: burlywood;"/>
+            </svg>
+                    <div style="color: black;">确认上传并分析</div>
+        </button>
+  }
+        <div class="voice-hint">
+            点击播放按钮可重复听取问题<br>
+            按住麦克风按钮开始回答
+        </div>
+    </div>
+    
+    <!-- 分析仪表盘 -->
+    <div class="dashboard-section">
+        <div class="dashboard-title">
+            <h3>实时分析面板</h3>
+            <button class="toggle-btn" (click)="toggleRadarChart()">
+                <i [class]="showRadarChart ? 'fas fa-chart-bar' : 'fas fa-chart-radar'"></i> 
+                {{ showRadarChart ? '隐藏雷达图' : '显示雷达图' }}
+            </button>
+        </div>
+        
+        <div class="dashboard-content">
+            <div class="metric-item expressiveness">
+                <div class="metric-header">
+                    <span class="metric-name">表达能力</span>
+                    <span class="metric-value">{{ metrics.expressiveness }}/100</span>
+                </div>
+                <div class="metric-bar">
+                    <div class="metric-fill" [style.width]="metrics.expressiveness + '%'"></div>
+                </div>
+            </div>
+            
+            <div class="metric-item professionalism">
+                <div class="metric-header">
+                    <span class="metric-name">专业度</span>
+                    <span class="metric-value">{{ metrics.professionalism }}/100</span>
+                </div>
+                <div class="metric-bar">
+                    <div class="metric-fill" [style.width]="metrics.professionalism + '%'"></div>
+                </div>
+            </div>
+            
+            <div class="metric-item relevance">
+                <div class="metric-header">
+                    <span class="metric-name">岗位匹配度</span>
+                    <span class="metric-value">{{ metrics.relevance }}/100</span>
+                </div>
+                <div class="metric-bar">
+                    <div class="metric-fill" [style.width]="metrics.relevance + '%'"></div>
+                </div>
+            </div>
+        </div>
+        
+        <!-- 雷达图容器 -->
+         @if (showRadarChart){
+            <div id="radarChart" class="radar-chart" *ngIf="showRadarChart"></div>
+         }
+        
+    </div>
+</div>

+ 426 - 0
interview-web/src/modules/interview/mobile/page-interview/page-interview.scss

@@ -0,0 +1,426 @@
+:root {
+  --primary-blue: #2A5CAA;
+  --accent-orange: #FF6B35;
+  --bg-light: #F5F7FA;
+  --text-dark: #2D3748;
+  --success-green: #48BB78;
+}
+
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
+}
+
+body {
+  background-color: var(--bg-light);
+  color: var(--text-dark);
+  line-height: 1.6;
+  padding: 0;
+}
+
+.interview-container {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+/* 头部区域 */
+.interview-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.timer {
+  background: white;
+  padding: 8px 15px;
+  border-radius: 20px;
+  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+  font-weight: bold;
+  color: var(--primary-blue);
+}
+
+/* 数字人区域 */
+.avatar-section {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 20px 0;
+  position: relative;
+}
+
+.avatar-container {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, var(--primary-blue), #3A7BD5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-shadow: 0 8px 20px rgba(42, 92, 170, 0.3);
+  position: relative;
+  overflow: hidden;
+}
+
+.avatar-img {
+  width: 90%;
+  height: 90%;
+  object-fit: contain;
+  transition: all 0.3s ease;
+}
+
+.avatar-expression {
+  position: absolute;
+  bottom: -10px;
+  background: white;
+  padding: 5px 15px;
+  border-radius: 20px;
+  font-size: 14px;
+  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+  display: flex;
+  align-items: center;
+}
+
+.expression-dot {
+  width: 8px;
+  height: 8px;
+  background: var(--success-green);
+  border-radius: 50%;
+  margin-right: 8px;
+  animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+  0% { opacity: 1; }
+  50% { opacity: 0.3; }
+  100% { opacity: 1; }
+}
+
+/* 对话区域 */
+.dialog-container {
+  background: white;
+  border-radius: 15px;
+  padding: 20px;
+  margin: 20px 0;
+  box-shadow: 0 5px 15px rgba(0,0,0,0.05);
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.message {
+  margin-bottom: 15px;
+  display: flex;
+  flex-direction: column;
+}
+
+.message-ai {
+  align-items: flex-start;
+}
+
+.message-user {
+  align-items: flex-end;
+}
+
+.message-bubble {
+  max-width: 80%;
+  padding: 12px 16px;
+  border-radius: 18px;
+  margin-bottom: 5px;
+  position: relative;
+  animation: fadeIn 0.3s ease;
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; transform: translateY(10px); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+.ai-bubble {
+  background: #EDF2F7;
+  border-top-left-radius: 5px;
+  color: var(--text-dark);
+}
+
+.user-bubble {
+  background: var(--primary-blue);
+  border-top-right-radius: 5px;
+  color: white;
+}
+
+.message-meta {
+  font-size: 12px;
+  color: #718096;
+  display: flex;
+  align-items: center;
+}
+
+.ai-meta {
+  justify-content: flex-start;
+}
+
+.user-meta {
+  justify-content: flex-end;
+}
+
+.message-meta i {
+  margin-right: 5px;
+}
+
+/* 问题卡片 */
+.question-card {
+  background: white;
+  border-radius: 15px;
+  padding: 25px;
+  margin: 20px 0;
+  box-shadow: 0 5px 15px rgba(0,0,0,0.05);
+  position: relative;
+  overflow: hidden;
+}
+
+.question-card::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 5px;
+  height: 100%;
+  background: linear-gradient(to bottom, var(--primary-blue), var(--accent-orange));
+}
+
+.question-text {
+  font-size: 18px;
+  margin-bottom: 15px;
+  font-weight: 500;
+}
+
+.question-progress {
+  font-size: 14px;
+  color: #718096;
+  display: flex;
+  align-items: center;
+}
+
+.progress-bar {
+  flex-grow: 1;
+  height: 6px;
+  background: #EDF2F7;
+  border-radius: 3px;
+  margin: 0 10px;
+  overflow: hidden;
+}
+
+.progress-fill {
+  height: 100%;
+  border-radius: 3px;
+  transition: width 0.5s ease;
+  background: linear-gradient(to right, var(--primary-blue), var(--accent-orange));
+}
+
+/* 语音输入区域 */
+.voice-input-section {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 30px 0;
+}
+
+.voice-controls {
+  display: flex;
+  gap: 15px;
+  margin-bottom: 20px;
+}
+
+.voice-btn {
+  width: 80px;
+  height: 80px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, var(--primary-blue), var(--accent-orange));
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: white;
+  font-size: 28px;
+  cursor: pointer;
+  box-shadow: 0 5px 20px rgba(255, 107, 53, 0.4);
+  transition: all 0.3s ease;
+  position: relative;
+  border: none;
+  outline: none;
+}
+
+.voice-btn:hover {
+  transform: scale(1.05);
+}
+
+.voice-btn.active {
+  animation: pulse 1.5s infinite;
+}
+
+.voice-hint {
+  margin-top: 15px;
+  font-size: 14px;
+  color: #718096;
+  text-align: center;
+}
+
+.voice-wave {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  border-radius: 50%;
+  background: rgba(255, 107, 53, 0.3);
+  animation: wave 2s infinite;
+  opacity: 0;
+}
+
+@keyframes wave {
+  0% { transform: scale(0.8); opacity: 1; }
+  100% { transform: scale(1.5); opacity: 0; }
+}
+
+/* 分析仪表盘 */
+.dashboard-section {
+  background: white;
+  border-radius: 15px;
+  padding: 20px;
+  margin-top: 20px;
+  box-shadow: 0 5px 15px rgba(0,0,0,0.05);
+}
+
+.dashboard-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15px;
+}
+
+.dashboard-title h3 {
+  font-size: 18px;
+  color: var(--primary-blue);
+}
+
+.toggle-btn {
+  background: none;
+  border: none;
+  color: #718096;
+  cursor: pointer;
+}
+
+.dashboard-content {
+  display: flex;
+  flex-direction: column;
+}
+
+.metric-item {
+  margin-bottom: 15px;
+}
+
+.metric-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 5px;
+}
+
+.metric-name {
+  font-weight: 500;
+}
+
+.metric-value {
+  font-weight: bold;
+  color: var(--primary-blue);
+}
+
+.metric-bar {
+  height: 8px;
+  background: #EDF2F7;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.metric-fill {
+  height: 100%;
+  border-radius: 4px;
+}
+
+.expressiveness .metric-fill {
+  background: linear-gradient(to right, #4FD1C5, #319795);
+}
+
+.professionalism .metric-fill {
+  background: linear-gradient(to right, #F6AD55, #DD6B20);
+}
+
+.relevance .metric-fill {
+  background: linear-gradient(to right, #9F7AEA, #6B46C1);
+}
+
+/* 雷达图容器 */
+.radar-chart {
+  width: 100%;
+  height: 300px;
+  margin-top: 20px;
+}
+
+/* 响应式调整 */
+@media (max-width: 600px) {
+  .interview-container {
+    padding: 15px;
+  }
+  
+  .question-text {
+    font-size: 16px;
+  }
+  
+  .avatar-container {
+    width: 100px;
+    height: 100px;
+  }
+  
+  .message-bubble {
+    max-width: 90%;
+  }
+}
+/* 提交按钮样式 */
+.submit-btn {
+  background: linear-gradient(135deg, var(--primary-blue), var(--accent-orange));
+  color: white;
+  border: none;
+  padding: 12px 24px;
+  border-radius: 30px;
+  font-size: 16px;
+  font-weight: 500;
+  margin-top: 15px;
+  cursor: pointer;
+  box-shadow: 0 4px 12px rgba(42, 92, 170, 0.3);
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 6px 16px rgba(42, 92, 170, 0.4);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+
+  &:disabled {
+    background: #ccc;
+    cursor: not-allowed;
+    transform: none;
+    box-shadow: none;
+  }
+}
+.check-icon {
+  margin-right: 8px;
+  vertical-align: middle;
+}

+ 296 - 3
interview-web/src/modules/interview/mobile/page-interview/page-interview.ts

@@ -1,11 +1,304 @@
-import { Component } from '@angular/core';
+import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import * as echarts from 'echarts';
 
 @Component({
   selector: 'app-page-interview',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule],
   templateUrl: './page-interview.html',
   styleUrl: './page-interview.scss'
 })
-export class PageInterview {
+export class PageInterview implements OnInit, AfterViewInit {
+remainingTime = '04:32';
+isAnalyzing = false;
+showSubmitButton = false;
+userAnswer = '';
+// 在组件中定义Base64编码的SVG
+avatarImages = {
+  default: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNiAzNiI+PHBhdGggZmlsbD0iI0ZGQjkwMCIgZD0iTTM2IDE4YzAgOS45NDEtOC4wNTkgMTgtMTggMThTMCAyNy45NDEgMCAxOCA4LjA1OSAwIDE4IDBzMTggOC4wNTkgMTggMTgiLz48cGF0aCBmaWxsPSIjNjYyMjEyIiBkPSJNMTguODQ1IDEzLjE0OGMuMTM0LS4yNDYuMzU2LS4zNjkuNjU3LS4zNjkuMzA5IDAgLjUyOS4xMjMuNjY0LjM2OS4xMzYuMjQ1LjIwNC41NzkuMjA0IDEuMDAzIDAgLjQxNS0uMDY4Ljc0LS4yMDQuOTg1LS4xMzUuMjQ0LS4zNTUuMzY2LS42NjQuMzY2LS4zMDEgMC0uNTIzLS4xMjItLjY1Ny0uMzY2LS4xMzUtLjI0NS0uMjAyLS41Ny0uMjAyLS45ODUgMC0uNDI0LjA2Ny0uNzU4LjIwMi0xLjAwM3ptLTUuNDQ4IDBjLjEzNS0uMjQ2LjM1Ni0uMzY5LjY1OC0uMzY5LjMwOSAwIC41MjkuMTIzLjY2NC4zNjkuMTM2LjI0NS4yMDQuNTc5LjIwNCAxLjAwMyAwIC40MTUtLjA2OC43NC0uMjA0Ljk4NS0uMTM1LjI0NC0uMzU1LjM2Ni0uNjY0LjM2Ni0uMzAyIDAtLjUyMy0uMTIyLS42NTgtLjM2Ni0uMTM0LS4yNDUtLjIwMS0uNTctLjIwMS0uOTg1IDAtLjQyNC4wNjctLjc1OC4yMDEtMS4wMDN6Ii8+PHBhdGggZmlsbD0iIzY2MjIxMiIgZD0iTTE4IDIxLjUzYy0yLjc2NCAwLTQuOTUxLS40MTktNC45NTEtMS4wNTQgMC0uNjM1IDIuMTg3LTEuMDU0IDQuOTUxLTEuMDU0IDIuNzYzIDAgNC45NTEuNDE5IDQuOTUxIDEuMDU0IDAgLjYzNS0yLjE4OCAxLjA1NC00Ljk1MSAxLjA1NHptMC0yLjEwOGMtMy4wMTcgMC01LjQ1MS0uNDk0LTUuNDUxLTEuMDU0czIuNDM0LTEuMDU0IDUuNDUxLTEuMDU0YzMuMDE4IDAgNS40NTEuNDk0IDUuNDUxIDEuMDU0cy0yLjQzMyAxLjA1NC01LjQ1MSAxLjA1NHoiLz48L3N2Zz4=',
+  speaking: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNiAzNiI+PHBhdGggZmlsbD0iI0ZGQjkwMCIgZD0iTTM2IDE4YzAgOS45NDEtOC4wNTkgMTgtMTggMThTMCAyNy45NDEgMCAxOCA4LjA1OSAwIDE4IDBzMTggOC4wNTkgMTggMTgiLz48cGF0aCBmaWxsPSIjNjYyMjEyIiBkPSJNMTggMjEuNTNjLTIuNzY0IDAtNC45NTEtLjQxOS00Ljk1MS0xLjA1NCAwLS42MzUgMi4xODctMS4wNTQgNC45NTEtMS4wNTQgMi43NjMgMCA0Ljk1MS40MTkgNC45NTEgMS4wNTQgMCAuNjM1LTIuMTg4IDEuMDU0LTQuOTUxIDEuMDU0em0wLTIuMTA4Yy0zLjAxNyAwLTUuNDUxLS40OTQtNS40NTEtMS4wNTRzMi40MzQtMS4wNTQgNS40NTEtMS4wNTRjMy4wMTggMCA1LjQ1MS40OTQgNS40NTEgMS4wNTRzLTIuNDMzIDEuMDU0LTUuNDUxIDEuMDU0eiIvPjwvc3ZnPg==',
+  // 其他表情...
+};
+  // 问题相关
+  questions = [
+    "欢迎参加本次AI面试,我是您的面试官AI助手。",
+    "首先,请简单介绍一下您自己。",
+    "您在过去工作中遇到的最大技术挑战是什么?您是如何解决的?",
+    "请描述一个您与团队意见不合时,您是如何处理的案例。"
+  ];
+  currentQuestionIndex = 0;
+  currentQuestion = this.questions[0];
+  showQuestionCard = false;
+  progress = 30;
+  
+  // 对话相关
+  messages: {sender: 'ai' | 'user', text: string}[] = [];
+  
+  // 语音相关
+  isListening = false;
+  isSpeaking = false;
+  
+  // 头像和表情相关
+  avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f913.svg";
+  expressionText = "正在聆听您的回答...";
+  
+  // 仪表盘相关
+  showRadarChart = false;
+  metrics = {
+    expressiveness: 82,
+    professionalism: 65,
+    relevance: 73
+  };
+  
+  // 预设回答
+  presetAnswers = [
+    "我是张伟,有5年Java开发经验,擅长分布式系统设计。",
+    "我们曾遇到高并发下的数据库瓶颈,我通过引入Redis缓存和分库分表解决了问题。",
+    "有一次在技术方案选择上,我通过数据对比和原型验证说服了团队采用我的方案。"
+  ];
+  
+  @ViewChild('dialogContainer') dialogContainer!: ElementRef;
+  private radarChart: any;
 
+  ngOnInit(): void {
+    this.initConversation();
+    this.startProgressTimer();
+  }
+  
+  ngAfterViewInit(): void {
+    // 初始化雷达图
+    this.initRadarChart();
+  }
+  
+  // 初始化对话
+  initConversation(): void {
+    this.addAIMessage(this.questions[0]);
+    setTimeout(() => {
+      this.askQuestion(1);
+    }, 1500);
+  }
+  
+  // 提问问题
+  askQuestion(index: number): void {
+    if (index >= this.questions.length) return;
+    
+    this.currentQuestionIndex = index;
+    this.currentQuestion = this.questions[index];
+    
+    // 添加到对话框
+    this.addAIMessage(this.currentQuestion);
+    
+    // 更新头像状态
+    this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f4ac.svg";
+    this.expressionText = "等待您的回答...";
+  }
+  
+  // 添加AI消息
+  addAIMessage(text: string): void {
+    this.messages.push({
+      sender: 'ai',
+      text: text
+    });
+    
+    // 滚动到底部
+    setTimeout(() => {
+      this.dialogContainer.nativeElement.scrollTop = this.dialogContainer.nativeElement.scrollHeight;
+    }, 0);
+    
+    // 自动语音播报
+    this.speakText(text);
+  }
+  
+  // 添加用户消息
+  addUserMessage(text: string): void {
+    this.messages.push({
+      sender: 'user',
+      text: text
+    });
+    
+    // 滚动到底部
+    setTimeout(() => {
+      this.dialogContainer.nativeElement.scrollTop = this.dialogContainer.nativeElement.scrollHeight;
+    }, 0);
+  }
+  
+  // 语音播报
+  speakText(text: string): void {
+    if (this.isSpeaking) return;
+    
+    this.isSpeaking = true;
+    this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f5e3.svg";
+    this.expressionText = "正在播报问题...";
+    
+    // 使用Web Speech API
+    const utterance = new SpeechSynthesisUtterance(text);
+    utterance.lang = 'zh-CN';
+    utterance.rate = 0.9;
+    utterance.pitch = 1;
+    
+    utterance.onend = () => {
+      this.isSpeaking = false;
+      this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f4ac.svg";
+      this.expressionText = "等待您的回答...";
+    };
+    
+    speechSynthesis.speak(utterance);
+  }
+  
+  // 开始语音输入
+  startVoiceInput(): void {
+    this.isListening = true;
+    this.expressionText = "正在聆听您的回答...";
+    console.log('语音输入开始');
+  }
+  
+  // 停止语音输入
+  stopVoiceInput(): void {
+    this.isListening = false;
+    
+    // 模拟语音识别结果
+    this.userAnswer = this.presetAnswers[this.currentQuestionIndex - 1] || "这是我的回答...";
+    this.showSubmitButton = true; // 显示提交按钮
+    
+    // 不再自动提交,等待用户点击
+    this.avatarImage = this.avatarImages.default;
+    this.expressionText = "请确认您的回答";
+  }
+  submitAnswer(): void {
+    this.showSubmitButton = false;
+    this.addUserMessage(this.userAnswer);
+    
+    // 开始分析
+    this.isAnalyzing = true;
+    this.avatarImage = this.avatarImages.default;
+    this.expressionText = "正在分析您的回答...";
+    
+    // 模拟分析过程 (2秒)
+    setTimeout(() => {
+      this.isAnalyzing = false;
+      
+      // 问下一题或结束
+      if (this.currentQuestionIndex < this.questions.length - 1) {
+        this.askQuestion(this.currentQuestionIndex + 1);
+      } else {
+        this.addAIMessage("感谢您的回答,面试即将结束,正在生成最终报告...");
+        this.avatarImage = this.avatarImages.speaking;
+        this.expressionText = "生成最终评估中...";
+      }
+    }, 2000);
+  }
+  
+  // 播放问题
+  playQuestion(): void {
+    if (!this.isSpeaking) {
+      this.speakText(this.questions[this.currentQuestionIndex]);
+    }
+  }
+  
+  // 切换雷达图
+  toggleRadarChart(): void {
+    this.showRadarChart = !this.showRadarChart;
+    if (this.showRadarChart) {
+      setTimeout(() => {
+        this.initRadarChart();
+      }, 0);
+    }
+  }
+  
+  // 初始化雷达图
+  initRadarChart(): void {
+    if (!this.showRadarChart) return;
+    
+    const chartDom = document.getElementById('radarChart');
+    if (!chartDom) return;
+    
+    if (this.radarChart) {
+      this.radarChart.dispose();
+    }
+    
+    this.radarChart = echarts.init(chartDom);
+    
+    const option = {
+      backgroundColor: 'transparent',
+      tooltip: {},
+      radar: {
+        shape: 'circle',
+        indicator: [
+          { name: '表达能力', max: 100 },
+          { name: '专业深度', max: 100 },
+          { name: '岗位匹配', max: 100 },
+          { name: '应变能力', max: 100 },
+          { name: '沟通技巧', max: 100 },
+          { name: '知识广度', max: 100 }
+        ],
+        radius: '65%',
+        axisName: {
+          color: '#4A5568'
+        },
+        splitArea: {
+          areaStyle: {
+            color: ['rgba(42, 92, 170, 0.1)']
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: 'rgba(42, 92, 170, 0.3)'
+          }
+        },
+        splitLine: {
+          lineStyle: {
+            color: 'rgba(42, 92, 170, 0.3)'
+          }
+        }
+      },
+      series: [{
+        type: 'radar',
+        data: [
+          {
+            value: [82, 65, 73, 68, 75, 60],
+            name: '当前表现',
+            areaStyle: {
+              color: 'rgba(42, 92, 170, 0.4)'
+            },
+            lineStyle: {
+              width: 2,
+              color: 'rgba(42, 92, 170, 0.8)'
+            },
+            itemStyle: {
+              color: '#2A5CAA'
+            }
+          },
+          {
+            value: [70, 80, 85, 75, 65, 70],
+            name: '岗位要求',
+            areaStyle: {
+              color: 'rgba(255, 107, 53, 0.2)'
+            },
+            lineStyle: {
+              width: 2,
+              color: 'rgba(255, 107, 53, 0.8)'
+            },
+            itemStyle: {
+              color: '#FF6B35'
+            }
+          }
+        ]
+      }]
+    };
+    
+    this.radarChart.setOption(option);
+    
+    // 响应式调整
+    window.addEventListener('resize', () => {
+      this.radarChart?.resize();
+    });
+  }
+  
+  // 进度条计时器
+  startProgressTimer(): void {
+    setInterval(() => {
+      this.progress = Math.min(this.progress + 10, 100);
+    }, 5000);
+  }
 }

+ 67 - 1
interview-web/src/modules/interview/mobile/page-job-hunting/page-job-hunting.html

@@ -1 +1,67 @@
-<p>page-job-hunting works!</p>
+<div class="container">
+  <header>
+    <h1>招聘信息平台</h1>
+    <p>找到您心仪的工作机会</p>
+  </header>
+  
+  <div class="search-section">
+    <div class="search-box">
+      <input 
+        type="text" 
+        [(ngModel)]="searchTerm" 
+        placeholder="输入职位、公司或关键词..."
+        (keyup.enter)="filteredJobs">
+      <button (click)="filteredJobs">搜索</button>
+    </div>
+    
+    <div class="filter-section">
+      <div class="filter-group">
+        <label for="job-type">岗位类型</label>
+        <select id="job-type" [(ngModel)]="selectedType">
+          <option value="">所有类型</option>
+          <option value="技术">技术</option>
+          <option value="产品">产品</option>
+          <option value="设计">设计</option>
+          <option value="运营">运营</option>
+          <option value="市场">市场</option>
+          <option value="销售">销售</option>
+          <option value="人事">人事</option>
+          <option value="财务">财务</option>
+        </select>
+      </div>
+      
+      <div class="filter-group">
+        <label for="location">工作地点</label>
+        <select id="location" [(ngModel)]="selectedLocation">
+          <option value="">所有地点</option>
+          <option value="北京">北京</option>
+          <option value="上海">上海</option>
+          <option value="广州">广州</option>
+          <option value="深圳">深圳</option>
+          <option value="杭州">杭州</option>
+          <option value="成都">成都</option>
+          <option value="武汉">武汉</option>
+          <option value="远程">远程</option>
+        </select>
+      </div>
+    </div>
+  </div>
+  
+  <div class="job-list">
+    <ng-container *ngIf="filteredJobs.length > 0; else noResults">
+      <div class="job-item" *ngFor="let job of filteredJobs">
+        <div class="job-title">{{ job.title }}</div>
+        <div class="job-company">{{ job.company }}</div>
+        <div class="job-location">{{ job.location }}</div>
+        <div class="job-type">{{ job.type }}</div>
+        <div class="job-salary">{{ job.salary }}</div>
+        <div class="job-description">{{ job.description }}</div>
+        <button class="apply-btn" (click)="applyForJob(job.id)">立即申请</button>
+      </div>
+    </ng-container>
+    
+    <ng-template #noResults>
+      <div class="no-results">没有找到符合条件的招聘信息</div>
+    </ng-template>
+  </div>
+</div>

+ 189 - 0
interview-web/src/modules/interview/mobile/page-job-hunting/page-job-hunting.scss

@@ -0,0 +1,189 @@
+@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap');
+
+* {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  font-family: 'Noto Sans SC', sans-serif;
+}
+
+body {
+  background-color: #f5f5f5;
+  color: #333;
+  line-height: 1.6;
+}
+
+.container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+header {
+  background-color: #2c3e50;
+  color: white;
+  padding: 20px 0;
+  text-align: center;
+  margin-bottom: 30px;
+  border-radius: 5px;
+}
+
+.search-section {
+  background-color: white;
+  padding: 20px;
+  border-radius: 5px;
+  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+  margin-bottom: 20px;
+}
+
+.search-box {
+  display: flex;
+  margin-bottom: 15px;
+
+  input {
+    flex: 1;
+    padding: 10px;
+    border: 1px solid #ddd;
+    border-radius: 4px 0 0 4px;
+    font-size: 16px;
+  }
+
+  button {
+    padding: 10px 20px;
+    background-color: #3498db;
+    color: white;
+    border: none;
+    border-radius: 0 4px 4px 0;
+    cursor: pointer;
+    font-size: 16px;
+    transition: background-color 0.3s;
+
+    &:hover {
+      background-color: #2980b9;
+    }
+  }
+}
+
+.filter-section {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 15px;
+}
+
+.filter-group {
+  flex: 1;
+  min-width: 200px;
+
+  label {
+    display: block;
+    margin-bottom: 5px;
+    font-weight: bold;
+  }
+
+  select {
+    width: 100%;
+    padding: 8px;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    background-color: white;
+  }
+}
+
+.job-list {
+  background-color: white;
+  border-radius: 5px;
+  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+  max-height: 500px;
+  overflow-y: auto;
+}
+
+.job-item {
+  padding: 20px;
+  border-bottom: 1px solid #eee;
+  transition: background-color 0.3s;
+
+  &:hover {
+    background-color: #f9f9f9;
+  }
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+
+.job-title {
+  font-size: 18px;
+  font-weight: bold;
+  color: #2c3e50;
+  margin-bottom: 5px;
+}
+
+.job-company {
+  color: #7f8c8d;
+  margin-bottom: 5px;
+}
+
+.job-location {
+  display: inline-block;
+  background-color: #e8f4fc;
+  color: #3498db;
+  padding: 3px 8px;
+  border-radius: 4px;
+  font-size: 14px;
+  margin-right: 10px;
+  margin-bottom: 10px;
+}
+
+.job-type {
+  display: inline-block;
+  background-color: #e8f8f0;
+  color: #27ae60;
+  padding: 3px 8px;
+  border-radius: 4px;
+  font-size: 14px;
+  margin-bottom: 10px;
+}
+
+.job-salary {
+  font-weight: bold;
+  color: #e74c3c;
+  margin-bottom: 10px;
+}
+
+.job-description {
+  color: #555;
+  margin-bottom: 10px;
+}
+
+.apply-btn {
+  display: inline-block;
+  padding: 8px 15px;
+  background-color: #2ecc71;
+  color: white;
+  text-decoration: none;
+  border-radius: 4px;
+  font-size: 14px;
+  border: none;
+  cursor: pointer;
+  transition: background-color 0.3s;
+
+  &:hover {
+    background-color: #27ae60;
+  }
+}
+
+.no-results {
+  padding: 20px;
+  text-align: center;
+  color: #7f8c8d;
+}
+
+@media (max-width: 768px) {
+  .filter-section {
+    flex-direction: column;
+  }
+  
+  .filter-group {
+    width: 100%;
+  }
+}

+ 136 - 4
interview-web/src/modules/interview/mobile/page-job-hunting/page-job-hunting.ts

@@ -1,11 +1,143 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+interface Job {
+  id: number;
+  title: string;
+  company: string;
+  location: string;
+  type: string;
+  salary: string;
+  description: string;
+}
 
 @Component({
   selector: 'app-page-job-hunting',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule,FormsModule],
   templateUrl: './page-job-hunting.html',
-  styleUrl: './page-job-hunting.scss'
+  styleUrls: ['./page-job-hunting.scss']
 })
 export class PageJobHunting {
+  searchTerm: string = '';
+  selectedType: string = '';
+  selectedLocation: string = '';
 
-}
+  jobs: Job[] = [
+    {
+      id: 1,
+      title: "前端开发工程师",
+      company: "ABC科技有限公司",
+      location: "北京",
+      type: "技术",
+      salary: "15k-25k",
+      description: "负责公司产品的前端开发工作,参与需求讨论和技术方案设计。"
+    },
+    {
+      id: 2,
+      title: "产品经理",
+      company: "XYZ互联网公司",
+      location: "上海",
+      type: "产品",
+      salary: "20k-30k",
+      description: "负责产品规划、需求分析和项目管理,协调各方资源推动产品迭代。"
+    },
+    {
+      id: 3,
+      title: "UI设计师",
+      company: "创意设计工作室",
+      location: "深圳",
+      type: "设计",
+      salary: "12k-20k",
+      description: "负责公司产品的界面设计和用户体验优化,输出高质量设计稿。"
+    },
+    {
+      id: 4,
+      title: "市场营销专员",
+      company: "快消品集团",
+      location: "广州",
+      type: "市场",
+      salary: "8k-15k",
+      description: "负责市场推广活动的策划与执行,分析市场数据并提出优化建议。"
+    },
+    {
+      id: 5,
+      title: "Java开发工程师",
+      company: "金融科技公司",
+      location: "杭州",
+      type: "技术",
+      salary: "18k-30k",
+      description: "负责金融系统后端开发,参与架构设计和性能优化。"
+    },
+    {
+      id: 6,
+      title: "人力资源专员",
+      company: "跨国企业",
+      location: "北京",
+      type: "人事",
+      salary: "10k-18k",
+      description: "负责招聘、员工关系管理和人力资源相关事务。"
+    },
+    {
+      id: 7,
+      title: "数据分析师",
+      company: "电商平台",
+      location: "远程",
+      type: "技术",
+      salary: "15k-25k",
+      description: "负责业务数据分析,建立数据模型并提供决策支持。"
+    },
+    {
+      id: 8,
+      title: "销售经理",
+      company: "医疗器械公司",
+      location: "成都",
+      type: "销售",
+      salary: "底薪+提成",
+      description: "负责区域市场开发,维护客户关系并完成销售目标。"
+    },
+    {
+      id: 9,
+      title: "财务主管",
+      company: "上市公司",
+      location: "上海",
+      type: "财务",
+      salary: "20k-35k",
+      description: "负责公司财务核算、报表编制和税务筹划工作。"
+    },
+    {
+      id: 10,
+      title: "运维工程师",
+      company: "云计算服务商",
+      location: "深圳",
+      type: "技术",
+      salary: "16k-28k",
+      description: "负责服务器和网络设备的维护,保障系统稳定运行。"
+    }
+  ];
+
+  get filteredJobs(): Job[] {
+    const searchTerm = this.searchTerm.toLowerCase();
+    const selectedType = this.selectedType;
+    const selectedLocation = this.selectedLocation;
+
+    return this.jobs.filter(job => {
+      const matchesSearch = 
+        job.title.toLowerCase().includes(searchTerm) ||
+        job.company.toLowerCase().includes(searchTerm) ||
+        job.description.toLowerCase().includes(searchTerm);
+      
+      const matchesType = selectedType === '' || job.type === selectedType;
+      const matchesLocation = selectedLocation === '' || job.location === selectedLocation;
+      
+      return matchesSearch && matchesType && matchesLocation;
+    });
+  }
+
+  applyForJob(jobId: number): void {
+    // 这里可以添加申请职位的逻辑
+    console.log(`Applying for job ID: ${jobId}`);
+    alert(`已申请职位ID: ${jobId}`);
+  }
+  
+}