浏览代码

feat:ai-interview

0235477 2 天之前
父节点
当前提交
f7f0e58b68
共有 100 个文件被更改,包括 6389 次插入400 次删除
  1. 33 3
      ai-interview/angular.json
  2. 488 130
      ai-interview/package-lock.json
  3. 24 1
      ai-interview/package.json
  4. 12 3
      ai-interview/src/app/app.config.ts
  5. 2 2
      ai-interview/src/app/app.html
  6. 9 8
      ai-interview/src/app/app.routes.ts
  7. 13 5
      ai-interview/src/app/app.ts
  8. 62 0
      ai-interview/src/app/hr-auth/auth.html
  9. 222 0
      ai-interview/src/app/hr-auth/auth.scss
  10. 6 6
      ai-interview/src/app/hr-auth/auth.spec.ts
  11. 45 0
      ai-interview/src/app/hr-auth/auth.ts
  12. 14 0
      ai-interview/src/app/hr-auth/hr-auth.routes.ts
  13. 63 0
      ai-interview/src/app/hr/company-profile/company-profile.html
  14. 229 0
      ai-interview/src/app/hr/company-profile/company-profile.scss
  15. 6 6
      ai-interview/src/app/hr/company-profile/company-profile.spec.ts
  16. 43 0
      ai-interview/src/app/hr/company-profile/company-profile.ts
  17. 29 0
      ai-interview/src/app/hr/dashboard/dashboard.html
  18. 75 0
      ai-interview/src/app/hr/dashboard/dashboard.scss
  19. 6 6
      ai-interview/src/app/hr/dashboard/dashboard.spec.ts
  20. 24 0
      ai-interview/src/app/hr/dashboard/dashboard.ts
  21. 10 0
      ai-interview/src/app/hr/dashboard/hr.routes.ts
  22. 14 0
      ai-interview/src/app/hr/models/company.model.ts
  23. 10 0
      ai-interview/src/app/hr/models/resume.model.ts
  24. 79 0
      ai-interview/src/app/hr/resume-filter/resume-filter.html
  25. 212 0
      ai-interview/src/app/hr/resume-filter/resume-filter.scss
  26. 6 6
      ai-interview/src/app/hr/resume-filter/resume-filter.spec.ts
  27. 47 0
      ai-interview/src/app/hr/resume-filter/resume-filter.ts
  28. 40 0
      ai-interview/src/app/hr/services/company.service.ts
  29. 51 0
      ai-interview/src/app/hr/services/resume.service.ts
  30. 14 0
      ai-interview/src/app/hr/status-card/status-card.html
  31. 56 0
      ai-interview/src/app/hr/status-card/status-card.scss
  32. 23 0
      ai-interview/src/app/hr/status-card/status-card.spec.ts
  33. 19 0
      ai-interview/src/app/hr/status-card/status-card.ts
  34. 67 0
      ai-interview/src/app/interviewee-auth/auth.html
  35. 13 0
      ai-interview/src/app/interviewee-auth/auth.routes.ts
  36. 223 0
      ai-interview/src/app/interviewee-auth/auth.scss
  37. 24 0
      ai-interview/src/app/interviewee-auth/auth.spec.ts
  38. 81 0
      ai-interview/src/app/interviewee-auth/auth.ts
  39. 4 0
      ai-interview/src/app/interviewee-auth/environment.ts
  40. 104 0
      ai-interview/src/app/interviewee-auth/server.js
  41. 二进制
      ai-interview/src/app/interviewer/home/asstes/user-avatar.png
  42. 55 0
      ai-interview/src/app/interviewer/home/company-filter/company-filter.html
  43. 175 0
      ai-interview/src/app/interviewer/home/company-filter/company-filter.scss
  44. 23 0
      ai-interview/src/app/interviewer/home/company-filter/company-filter.spec.ts
  45. 133 0
      ai-interview/src/app/interviewer/home/company-filter/company-filter.ts
  46. 47 0
      ai-interview/src/app/interviewer/home/home.html
  47. 14 0
      ai-interview/src/app/interviewer/home/home.routes.ts
  48. 146 0
      ai-interview/src/app/interviewer/home/home.scss
  49. 23 0
      ai-interview/src/app/interviewer/home/home.spec.ts
  50. 21 0
      ai-interview/src/app/interviewer/home/home.ts
  51. 57 0
      ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.html
  52. 209 0
      ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.scss
  53. 23 0
      ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.spec.ts
  54. 108 0
      ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.ts
  55. 80 0
      ai-interview/src/app/interviewer/home/resume-upload/resume-upload.html
  56. 316 0
      ai-interview/src/app/interviewer/home/resume-upload/resume-upload.scss
  57. 23 0
      ai-interview/src/app/interviewer/home/resume-upload/resume-upload.spec.ts
  58. 121 0
      ai-interview/src/app/interviewer/home/resume-upload/resume-upload.ts
  59. 0 0
      ai-interview/src/app/interviewer/interviewer.html
  60. 25 0
      ai-interview/src/app/interviewer/interviewer.routes.ts
  61. 184 0
      ai-interview/src/app/interviewer/interviewer.scss
  62. 23 0
      ai-interview/src/app/interviewer/interviewer.spec.ts
  63. 54 0
      ai-interview/src/app/interviewer/interviewer.ts
  64. 88 0
      ai-interview/src/app/interviewer/steps/interview-step/interview-step.html
  65. 354 0
      ai-interview/src/app/interviewer/steps/interview-step/interview-step.scss
  66. 23 0
      ai-interview/src/app/interviewer/steps/interview-step/interview-step.spec.ts
  67. 225 0
      ai-interview/src/app/interviewer/steps/interview-step/interview-step.ts
  68. 64 0
      ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.html
  69. 160 0
      ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.scss
  70. 23 0
      ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.spec.ts
  71. 163 0
      ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.ts
  72. 67 0
      ai-interview/src/app/interviewer/steps/result-step/result-step.html
  73. 295 0
      ai-interview/src/app/interviewer/steps/result-step/result-step.scss
  74. 23 0
      ai-interview/src/app/interviewer/steps/result-step/result-step.spec.ts
  75. 193 0
      ai-interview/src/app/interviewer/steps/result-step/result-step.ts
  76. 42 0
      ai-interview/src/app/login-choice/login-choice.html
  77. 192 0
      ai-interview/src/app/login-choice/login-choice.scss
  78. 23 0
      ai-interview/src/app/login-choice/login-choice.spec.ts
  79. 67 0
      ai-interview/src/app/login-choice/login-choice.ts
  80. 0 17
      ai-interview/src/app/material.provider.ts
  81. 18 0
      ai-interview/src/app/shard/directives/swiper.directive.ts
  82. 2 0
      ai-interview/src/index.html
  83. 5 1
      ai-interview/src/main.ts
  84. 0 2
      ai-interview/src/moudles/interviewee/interviewee-home/interviewee-home.html
  85. 0 13
      ai-interview/src/moudles/interviewee/interviewee-home/interviewee-home.ts
  86. 0 2
      ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.html
  87. 0 0
      ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.scss
  88. 0 23
      ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.spec.ts
  89. 0 13
      ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.ts
  90. 0 2
      ai-interview/src/moudles/interviewee/interviewee-mine/interviewee-mine.html
  91. 0 0
      ai-interview/src/moudles/interviewee/interviewee-mine/interviewee-mine.scss
  92. 0 13
      ai-interview/src/moudles/interviewee/interviewee-mine/interviewee-mine.ts
  93. 0 28
      ai-interview/src/moudles/interviewee/interviewee.routes.ts
  94. 0 17
      ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.html
  95. 0 30
      ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.scss
  96. 0 23
      ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.spec.ts
  97. 0 28
      ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.ts
  98. 0 1
      ai-interview/src/moudles/interviewer/interviewer-home/interviewer-home.html
  99. 0 0
      ai-interview/src/moudles/interviewer/interviewer-home/interviewer-home.scss
  100. 0 11
      ai-interview/src/moudles/interviewer/interviewer-home/interviewer-home.ts

+ 33 - 3
ai-interview/angular.json

@@ -18,6 +18,17 @@
           "builder": "@angular/build:application",
           "options": {
             "browser": "src/main.ts",
+             "allowedCommonJsDependencies": [
+              "@angular/common",
+              "@angular/core",
+              "@angular/compiler",
+              "swiper",
+              "echarts"
+            ],
+            "scripts": [
+              "node_modules/swiper/swiper.min.js",
+              "node_modules/echarts/dist/echarts.min.js"
+            ],
             "polyfills": [
               "zone.js"
             ],
@@ -30,8 +41,24 @@
               }
             ],
             "styles": [
+              
+              "node_modules/font-awesome/css/font-awesome.min.css",
+              "node_modules/swiper/swiper-bundle.min.css",
+             "node_modules/swiper/swiper.min.css",
               "@angular/material/prebuilt-themes/azure-blue.css",
-              "src/styles.scss"
+             "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
+              "node_modules/animate.css/animate.min.css",
+              "src/styles.scss",
+              {
+                "input": "https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css",
+                "inject": false,
+                "bundleName": "font-awesome"
+              },
+              {
+                "input": "https://cdn.bootcdn.net/ajax/libs/animate.css/4.1.1/animate.min.css",
+                "inject": false,
+                "bundleName": "animate-css"
+              }
             ]
           },
           "configurations": {
@@ -39,8 +66,8 @@
               "budgets": [
                 {
                   "type": "initial",
-                  "maximumWarning": "500kB",
-                  "maximumError": "1MB"
+                  "maximumWarning": "2MB",
+                  "maximumError": "3MB"
                 },
                 {
                   "type": "anyComponentStyle",
@@ -84,6 +111,7 @@
             "inlineStyleLanguage": "scss",
             "assets": [
               {
+                
                 "glob": "**/*",
                 "input": "public"
               }
@@ -97,4 +125,6 @@
       }
     }
   }
+  
 }
+

文件差异内容过多而无法显示
+ 488 - 130
ai-interview/package-lock.json


+ 24 - 1
ai-interview/package.json

@@ -20,6 +20,7 @@
   },
   "private": true,
   "dependencies": {
+    "@angular/animations": "^20.0.5",
     "@angular/cdk": "^20.0.4",
     "@angular/common": "^20.0.0",
     "@angular/compiler": "^20.0.0",
@@ -28,15 +29,37 @@
     "@angular/material": "^20.0.4",
     "@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-regular-svg-icons": "^6.7.2",
+    "@fortawesome/free-solid-svg-icons": "^6.7.2",
+    "@types/chart.js": "^2.9.41",
+    "@types/chrome": "^0.0.328",
+    "@types/echarts": "^4.9.22",
+    "angular-swiper": "^0.4.0",
+    "animate.css": "^4.1.1",
+    "body-parser": "^2.2.0",
+    "chart.js": "^4.5.0",
+    "cors": "^2.8.5",
+    "echarts": "^5.6.0",
+    "express": "^5.1.0",
+    "font-awesome": "^4.7.0",
+    "mysql2": "^3.14.1",
+    "ngx-echarts": "^20.0.1",
+    "ngx-swiper-wrapper": "^10.0.0",
     "rxjs": "~7.8.0",
+    "swiper": "^11.2.10",
     "tslib": "^2.3.0",
     "zone.js": "~0.15.0"
   },
+  "type": "commonjs",
   "devDependencies": {
     "@angular/build": "^20.0.4",
     "@angular/cli": "^20.0.4",
     "@angular/compiler-cli": "^20.0.0",
     "@types/jasmine": "~5.1.0",
+    "@types/swiper": "^6.0.0",
     "jasmine-core": "~5.7.0",
     "karma": "~6.4.0",
     "karma-chrome-launcher": "~3.2.0",
@@ -45,4 +68,4 @@
     "karma-jasmine-html-reporter": "~2.1.0",
     "typescript": "~5.8.2"
   }
-}
+}

+ 12 - 3
ai-interview/src/app/app.config.ts

@@ -1,12 +1,21 @@
+// src/app/app.config.ts
 import { ApplicationConfig } from '@angular/core';
 import { provideRouter } from '@angular/router';
-import { provideMaterial } from './material.provider';
 import { routes } from './app.routes';
+import { provideAnimations } from '@angular/platform-browser/animations';
+import { provideHttpClient, withRequestsMadeViaParent } from '@angular/common/http';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faUserTie, faChartLine } from '@fortawesome/free-solid-svg-icons';
+
+// 添加图标到库
+library.add(faUserTie, faChartLine);
 
 export const appConfig: ApplicationConfig = {
   providers: [
     provideRouter(routes),
-    provideMaterial(),
-    // 其他 providers...
+    provideAnimations(),
+    provideHttpClient(withRequestsMadeViaParent()) as any,
+    FontAwesomeModule
   ]
 };

+ 2 - 2
ai-interview/src/app/app.html

@@ -1,2 +1,2 @@
-
-<router-outlet />
+<app-login-choice></app-login-choice>
+<router-outlet></router-outlet>

+ 9 - 8
ai-interview/src/app/app.routes.ts

@@ -1,13 +1,14 @@
 import { Routes } from '@angular/router';
+import {LoginChoice } from './login-choice/login-choice';
 
 export const routes: Routes = [
-  {
-    path: 'interviewee',
-    loadChildren: () => import('../moudles/interviewee/interviewee.routes').then(m => m.intervieweeRoutes),
+  { path: '', component: LoginChoice},
+  { 
+    path: 'intervieweeauth', 
+    loadChildren: () => import('./interviewee-auth/auth.routes').then(m=>m.AuthRoutes)
+  },
+  { 
+    path: 'hrauth', 
+    loadChildren: () => import('./hr-auth/hr-auth.routes').then(m => m.HrAuthRoutes) 
   },
-  {
-    path:'interviewer',
-    loadChildren: () => import('../moudles/interviewer/interviewer.routes').then(m => m.interviewerRoutes),
-  }
-  // 其他路由...
 ];

+ 13 - 5
ai-interview/src/app/app.ts

@@ -1,12 +1,20 @@
 import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
 import { RouterOutlet } from '@angular/router';
+import 'swiper/css';
+import 'swiper/css/effect-cards';
+import 'font-awesome/css/font-awesome.min.css';
+import 'animate.css';
 
 @Component({
   selector: 'app-root',
-  imports: [RouterOutlet],
-  templateUrl: './app.html',
-  styleUrl: './app.scss'
+  standalone: true,
+  imports: [CommonModule, RouterOutlet],
+  template: `
+    <router-outlet></router-outlet>
+  `,
+  styles: []
 })
 export class App {
-  protected title = 'ai-interview';
-}
+  title = 'ai-interview';
+}

+ 62 - 0
ai-interview/src/app/hr-auth/auth.html

@@ -0,0 +1,62 @@
+<div class="auth-container">
+  <!-- 科幻风格装饰元素 -->
+  <div class="decorator decorator-1"></div>
+  <div class="decorator decorator-2"></div>
+  
+  <div class="auth-header">
+    <h1 class="auth-title">企业登录</h1>
+    <p class="auth-subtitle">欢迎回来,请使用您的凭证登录系统</p>
+  </div>
+
+  <!-- 错误消息 -->
+  @if (errorMessage()) {
+    <div class="message error-message">
+      {{ errorMessage() }}
+    </div>
+  }
+
+  <!-- 成功消息 -->
+  @if (successMessage()) {
+    <div class="message success-message">
+      {{ successMessage() }}
+    </div>
+  }
+
+  <form class="auth-form" (ngSubmit)="onSubmit()">
+    <div class="form-group">
+      <label for="username" class="form-label">企业凭证</label>
+      <input
+        type="text"
+        id="username"
+        [(ngModel)]="username"
+        name="username"
+        class="form-input"
+        placeholder="输入您的企业凭证"
+        required
+      />
+    </div>
+
+    <div class="form-group">
+      <label for="password" class="form-label">密码</label>
+      <input
+        type="password"
+        id="password"
+        [(ngModel)]="password"
+        name="password"
+        class="form-input"
+        placeholder="输入您的密码"
+        required
+      />
+    </div>
+
+    <button type="submit" class="submit-btn" [disabled]="isLoading()">
+      @if (isLoading()) {
+        <span>登录中...</span>
+      } @else {
+        <span>登 录</span>
+      }
+    </button>
+  </form>
+
+  
+</div>

+ 222 - 0
ai-interview/src/app/hr-auth/auth.scss

@@ -0,0 +1,222 @@
+:host {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-height: 100vh;
+  background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
+  font-family: 'Inter', 'Alibaba PuHuiTi', sans-serif;
+  color: white;
+}
+
+.auth-container {
+  width: 100%;
+  max-width: 420px;
+  padding: 2.5rem;
+  background: rgba(30, 41, 59, 0.8);
+  border-radius: 1rem;
+  box-shadow: 0 0 30px rgba(37, 99, 235, 0.3);
+  border: 1px solid rgba(37, 99, 235, 0.2);
+  backdrop-filter: blur(10px);
+  position: relative;
+  overflow: hidden;
+}
+
+.auth-container::before {
+  content: '';
+  position: absolute;
+  top: -50%;
+  left: -50%;
+  width: 200%;
+  height: 200%;
+  background: radial-gradient(circle, rgba(37, 99, 235, 0.1) 0%, transparent 70%);
+  animation: rotate 20s linear infinite;
+  z-index: -1;
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.auth-header {
+  text-align: center;
+  margin-bottom: 2rem;
+}
+
+.auth-title {
+  font-size: 1.8rem;
+  font-weight: 600;
+  margin-bottom: 0.5rem;
+  background: linear-gradient(90deg, #2563EB, #7C3AED);
+  -webkit-background-clip: text;
+  background-clip: text;
+  color: transparent;
+}
+
+.auth-subtitle {
+  font-size: 0.9rem;
+  color: #94a3b8;
+}
+
+.auth-form {
+  display: flex;
+  flex-direction: column;
+  gap: 1.5rem;
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+}
+
+.form-label {
+  font-size: 0.9rem;
+  color: #e2e8f0;
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+}
+
+.form-label::before {
+  content: '>';
+  color: #7C3AED;
+  font-weight: bold;
+}
+
+.form-input {
+  padding: 0.8rem 1rem;
+  background: rgba(15, 23, 42, 0.5);
+  border: 1px solid rgba(37, 99, 235, 0.3);
+  border-radius: 0.5rem;
+  color: white;
+  font-family: inherit;
+  font-size: 1rem;
+  transition: all 0.3s ease;
+}
+
+.form-input:focus {
+  outline: none;
+  border-color: #2563EB;
+  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
+}
+
+.submit-btn {
+  padding: 0.8rem;
+  background: linear-gradient(90deg, #2563EB, #7C3AED);
+  color: white;
+  border: none;
+  border-radius: 0.5rem;
+  font-size: 1rem;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  margin-top: 0.5rem;
+  position: relative;
+  overflow: hidden;
+}
+
+.submit-btn::after {
+  content: '';
+  position: absolute;
+  top: -50%;
+  left: -50%;
+  width: 200%;
+  height: 200%;
+  background: linear-gradient(
+    to bottom right,
+    rgba(255, 255, 255, 0.3) 0%,
+    rgba(255, 255, 255, 0) 60%
+  );
+  transform: rotate(30deg);
+  transition: all 0.3s ease;
+}
+
+.submit-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 5px 15px rgba(37, 99, 235, 0.4);
+}
+
+.submit-btn:hover::after {
+  left: 100%;
+}
+
+.submit-btn:active {
+  transform: translateY(0);
+}
+
+.mode-toggle {
+  text-align: center;
+  margin-top: 1.5rem;
+  font-size: 0.9rem;
+  color: #94a3b8;
+}
+
+.toggle-link {
+  color: #7C3AED;
+  cursor: pointer;
+  font-weight: 600;
+  text-decoration: none;
+  transition: color 0.3s ease;
+}
+
+.toggle-link:hover {
+  color: #2563EB;
+  text-decoration: underline;
+}
+
+.message {
+  padding: 0.8rem;
+  border-radius: 0.5rem;
+  margin-bottom: 1rem;
+  font-size: 0.9rem;
+  text-align: center;
+}
+
+.error-message {
+  background-color: rgba(245, 158, 11, 0.1);
+  border: 1px solid rgba(245, 158, 11, 0.3);
+  color: #F59E0B;
+}
+
+.success-message {
+  background-color: rgba(16, 185, 129, 0.1);
+  border: 1px solid rgba(16, 185, 129, 0.3);
+  color: #10B981;
+}
+
+/* 科幻风格的装饰元素 */
+.decorator {
+  position: absolute;
+  background: rgba(37, 99, 235, 0.1);
+  border: 1px solid rgba(37, 99, 235, 0.3);
+}
+
+.decorator-1 {
+  width: 100px;
+  height: 100px;
+  border-radius: 50%;
+  top: -50px;
+  right: -50px;
+}
+
+.decorator-2 {
+  width: 60px;
+  height: 60px;
+  border-radius: 10px;
+  bottom: -30px;
+  left: -30px;
+  transform: rotate(45deg);
+}
+
+/* 响应式设计 */
+@media (max-width: 480px) {
+  .auth-container {
+    padding: 1.5rem;
+    margin: 0 1rem;
+  }
+}

+ 6 - 6
ai-interview/src/moudles/interviewer/interviewer-mine/interviewer-mine.spec.ts → ai-interview/src/app/hr-auth/auth.spec.ts

@@ -1,18 +1,18 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { InterviewerMine } from './interviewer-mine';
+import { Auth } from './auth';
 
-describe('InterviewerMine', () => {
-  let component: InterviewerMine;
-  let fixture: ComponentFixture<InterviewerMine>;
+describe('Auth', () => {
+  let component: Auth;
+  let fixture: ComponentFixture<Auth>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      imports: [InterviewerMine]
+      imports: [Auth]
     })
     .compileComponents();
 
-    fixture = TestBed.createComponent(InterviewerMine);
+    fixture = TestBed.createComponent(Auth);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });

+ 45 - 0
ai-interview/src/app/hr-auth/auth.ts

@@ -0,0 +1,45 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { Router, RouterModule } from '@angular/router';
+
+@Component({
+  selector: 'app-auth',
+  standalone: true,
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule],
+  templateUrl: './auth.html',
+  styleUrls: ['./auth.scss']
+})
+export class Auth implements OnInit {
+  // 表单数据
+  username = signal('');
+  password = signal('');
+  
+  // 状态管理
+  isLoading = signal(false);
+  errorMessage = signal('');
+  successMessage = signal('');
+
+  ngOnInit() {
+    // 初始化时可以添加一些动画效果
+  }
+
+  onSubmit() {
+    this.isLoading.set(true);
+    this.errorMessage.set('');
+    this.successMessage.set('');
+
+    // 模拟登录请求
+    setTimeout(() => {
+      if (this.username() === 'admin' && this.password() === 'password') {
+        this.successMessage.set('登录成功!');
+        this.router.navigate(['/hrauth/hr']);
+        // 这里实际项目中应该是导航到主页
+      } else {
+        this.errorMessage.set('企业凭证或密码错误,请重试');
+      }
+      this.isLoading.set(false);
+    }, 1500);
+  }
+  constructor(private router: Router){}
+}

+ 14 - 0
ai-interview/src/app/hr-auth/hr-auth.routes.ts

@@ -0,0 +1,14 @@
+import { Routes } from '@angular/router';
+
+
+export const HrAuthRoutes: Routes = [
+  { 
+    path: '', 
+    loadComponent: () => import('./auth').then(m=>m.Auth)
+  },
+  {
+     path:'hr',
+     loadComponent: () => import('../hr/dashboard/dashboard').then(m=>m.Dashboard)
+  },
+  
+];

+ 63 - 0
ai-interview/src/app/hr/company-profile/company-profile.html

@@ -0,0 +1,63 @@
+<!-- company-profile.component.html -->
+<div class="company-profile-container">
+  <div class="profile-header">
+    <h2>企业资料</h2>
+    <button class="edit-button" (click)="toggleEdit()">
+      {{ isEditing ? '保存更改' : '编辑资料' }}
+    </button>
+  </div>
+
+  <div class="company-info">
+    <div class="info-row">
+      <label>企业名称</label>
+      <span *ngIf="!isEditing">{{ company.name }}</span>
+      <input *ngIf="isEditing" [(ngModel)]="company.name">
+    </div>
+
+    <div class="info-row">
+      <label>企业地址</label>
+      <span *ngIf="!isEditing">{{ company.address }}</span>
+      <input *ngIf="isEditing" [(ngModel)]="company.address">
+    </div>
+
+    <div class="info-row">
+      <label>行业方向</label>
+      <span *ngIf="!isEditing">{{ company.industry }}</span>
+      <input *ngIf="isEditing" [(ngModel)]="company.industry">
+    </div>
+
+    <div class="info-row">
+      <label>企业简介</label>
+      <span *ngIf="!isEditing">{{ company.description }}</span>
+      <textarea *ngIf="isEditing" [(ngModel)]="company.description" rows="4"></textarea>
+    </div>
+  </div>
+
+  <div class="job-openings">
+    <h3>招聘职位</h3>
+    
+    <div *ngIf="isEditing" class="add-job-form">
+      <div class="form-row">
+        <input [(ngModel)]="newJob.title" placeholder="职位名称">
+        <input [(ngModel)]="newJob.requirements" placeholder="职位要求">
+      </div>
+      <textarea [(ngModel)]="newJob.description" placeholder="职位描述" rows="3"></textarea>
+      <button class="add-button" (click)="addJob()">添加职位</button>
+    </div>
+
+    <div class="job-list">
+      <div *ngFor="let job of company.jobOpenings; let i = index" class="job-card">
+        <div class="job-header">
+          <h4>{{ job.title }}</h4>
+          <button *ngIf="isEditing" class="remove-button" (click)="removeJob(i)">删除</button>
+        </div>
+        <div class="job-requirements">
+          <strong>要求:</strong> {{ job.requirements || '无特定要求' }}
+        </div>
+        <div class="job-description">
+          {{ job.description }}
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 229 - 0
ai-interview/src/app/hr/company-profile/company-profile.scss

@@ -0,0 +1,229 @@
+/* company-profile.component.scss */
+.company-profile-container {
+  display: flex;
+  flex-direction: column;
+  gap: 2rem;
+}
+
+.profile-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 1rem;
+
+  h2 {
+    margin: 0;
+    font-size: 1.5rem;
+    font-weight: 600;
+    color: white;
+    background: linear-gradient(90deg, #2563EB 0%, #7C3AED 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+  }
+}
+
+.edit-button {
+  background: #2563EB;
+  color: white;
+  border: none;
+  padding: 0.6rem 1.2rem;
+  border-radius: 0.5rem;
+  font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+
+  &:hover {
+    background: #1d4ed8;
+    box-shadow: 0 0 15px rgba(37, 99, 235, 0.5);
+  }
+}
+
+.company-info {
+  display: flex;
+  flex-direction: column;
+  gap: 1.2rem;
+  background: rgba(15, 23, 42, 0.7);
+  border-radius: 0.8rem;
+  padding: 1.5rem;
+  border: 1px solid rgba(37, 99, 235, 0.2);
+}
+
+.info-row {
+  display: grid;
+  grid-template-columns: 120px 1fr;
+  gap: 1rem;
+  align-items: center;
+
+  label {
+    font-weight: 500;
+    color: rgba(255, 255, 255, 0.8);
+  }
+
+  span {
+    color: white;
+  }
+
+  input, textarea {
+    width: 100%;
+    padding: 0.7rem 1rem;
+    background: rgba(30, 41, 59, 0.8);
+    border: 1px solid rgba(37, 99, 235, 0.3);
+    border-radius: 0.5rem;
+    color: white;
+    font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+    transition: all 0.3s ease;
+
+    &:focus {
+      outline: none;
+      border-color: #7C3AED;
+      box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.3);
+    }
+  }
+
+  textarea {
+    resize: vertical;
+    min-height: 100px;
+  }
+}
+
+.job-openings {
+  display: flex;
+  flex-direction: column;
+  gap: 1.5rem;
+
+  h3 {
+    margin: 0;
+    font-size: 1.3rem;
+    font-weight: 600;
+    color: white;
+  }
+}
+
+.add-job-form {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  background: rgba(15, 23, 42, 0.7);
+  border-radius: 0.8rem;
+  padding: 1.5rem;
+  border: 1px solid rgba(37, 99, 235, 0.2);
+  margin-bottom: 1rem;
+
+  .form-row {
+    display: flex;
+    gap: 1rem;
+
+    input {
+      flex: 1;
+    }
+  }
+
+  input, textarea {
+    width: 100%;
+    padding: 0.7rem 1rem;
+    background: rgba(30, 41, 59, 0.8);
+    border: 1px solid rgba(37, 99, 235, 0.3);
+    border-radius: 0.5rem;
+    color: white;
+    font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+    transition: all 0.3s ease;
+
+    &:focus {
+      outline: none;
+      border-color: #7C3AED;
+      box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.3);
+    }
+  }
+
+  textarea {
+    resize: vertical;
+    min-height: 80px;
+  }
+}
+
+.add-button {
+  align-self: flex-end;
+  background: #10B981;
+  color: white;
+  border: none;
+  padding: 0.6rem 1.2rem;
+  border-radius: 0.5rem;
+  font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+
+  &:hover {
+    background: #0d9f6e;
+    box-shadow: 0 0 15px rgba(16, 185, 129, 0.5);
+  }
+}
+
+.job-list {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+}
+
+.job-card {
+  background: rgba(15, 23, 42, 0.7);
+  border-radius: 0.8rem;
+  padding: 1.5rem;
+  border: 1px solid rgba(37, 99, 235, 0.2);
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-3px);
+    box-shadow: 0 5px 15px rgba(37, 99, 235, 0.2);
+    border-color: rgba(37, 99, 235, 0.5);
+  }
+}
+
+.job-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 0.8rem;
+
+  h4 {
+    margin: 0;
+    font-size: 1.1rem;
+    font-weight: 600;
+    color: white;
+  }
+}
+
+.remove-button {
+  background: rgba(239, 68, 68, 0.2);
+  color: #EF4444;
+  border: none;
+  padding: 0.3rem 0.8rem;
+  border-radius: 0.3rem;
+  font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+
+  &:hover {
+    background: #EF4444;
+    color: white;
+  }
+}
+
+.job-requirements {
+  font-size: 0.9rem;
+  color: rgba(255, 255, 255, 0.8);
+  margin-bottom: 0.8rem;
+
+  strong {
+    color: white;
+    font-weight: 500;
+  }
+}
+
+.job-description {
+  font-size: 0.95rem;
+  color: rgba(255, 255, 255, 0.7);
+  line-height: 1.5;
+}

+ 6 - 6
ai-interview/src/moudles/interviewee/interviewee-mine/interviewee-mine.spec.ts → ai-interview/src/app/hr/company-profile/company-profile.spec.ts

@@ -1,18 +1,18 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { IntervieweeMine } from './interviewee-mine';
+import { CompanyProfile } from './company-profile';
 
-describe('IntervieweeMine', () => {
-  let component: IntervieweeMine;
-  let fixture: ComponentFixture<IntervieweeMine>;
+describe('CompanyProfile', () => {
+  let component: CompanyProfile;
+  let fixture: ComponentFixture<CompanyProfile>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      imports: [IntervieweeMine]
+      imports: [CompanyProfile]
     })
     .compileComponents();
 
-    fixture = TestBed.createComponent(IntervieweeMine);
+    fixture = TestBed.createComponent(CompanyProfile);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });

+ 43 - 0
ai-interview/src/app/hr/company-profile/company-profile.ts

@@ -0,0 +1,43 @@
+// company-profile.component.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Company } from '../models/company.model';
+import { CompanyService } from '../services/company.service';
+
+@Component({
+  selector: 'app-company-profile',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './company-profile.html',
+  styleUrls: ['./company-profile.scss']
+})
+export class CompanyProfile {
+  company: Company;
+  isEditing = false;
+  newJob = { title: '', description: '', requirements: '' };
+
+  constructor(private companyService: CompanyService) {
+    this.company = this.companyService.getCompany();
+  }
+
+  toggleEdit() {
+    this.isEditing = !this.isEditing;
+    if (!this.isEditing) {
+      this.companyService.updateCompany(this.company);
+    }
+  }
+
+  addJob() {
+    if (this.newJob.title && this.newJob.description) {
+      this.company.jobOpenings.push({...this.newJob});
+      this.newJob = { title: '', description: '', requirements: '' };
+      this.companyService.updateCompany(this.company);
+    }
+  }
+
+  removeJob(index: number) {
+    this.company.jobOpenings.splice(index, 1);
+    this.companyService.updateCompany(this.company);
+  }
+}

+ 29 - 0
ai-interview/src/app/hr/dashboard/dashboard.html

@@ -0,0 +1,29 @@
+<!-- dashboard.component.html -->
+<div class="dashboard-container">
+  <header class="dashboard-header">
+    <h1 class="title">企业后端管理系统</h1>
+    <div class="tab-nav">
+      <button 
+        class="tab-button" 
+        [class.active]="activeTab === 'resumes'"
+        (click)="activeTab = 'resumes'">
+        简历筛选
+      </button>
+      <button 
+        class="tab-button" 
+        [class.active]="activeTab === 'company'"
+        (click)="activeTab = 'company'">
+        企业资料
+      </button>
+    </div>
+  </header>
+
+  <div class="dashboard-content">
+    <app-status-card *ngIf="activeTab === 'resumes'"></app-status-card>
+    
+    <div class="main-content">
+      <app-resume-filter *ngIf="activeTab === 'resumes'"></app-resume-filter>
+      <app-company-profile *ngIf="activeTab === 'company'"></app-company-profile>
+    </div>
+  </div>
+</div>

+ 75 - 0
ai-interview/src/app/hr/dashboard/dashboard.scss

@@ -0,0 +1,75 @@
+/* dashboard.component.scss */
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+
+.dashboard-container {
+  font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+  background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
+  min-height: 100vh;
+  color: white;
+  padding: 2rem;
+}
+
+.dashboard-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 2rem;
+  border-bottom: 1px solid rgba(37, 99, 235, 0.3);
+  padding-bottom: 1rem;
+
+  .title {
+    font-size: 1.8rem;
+    font-weight: 600;
+    background: linear-gradient(90deg, #2563EB 0%, #7C3AED 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    margin: 0;
+  }
+}
+
+.tab-nav {
+  display: flex;
+  gap: 1rem;
+  background: rgba(30, 41, 59, 0.8);
+  padding: 0.5rem;
+  border-radius: 0.5rem;
+  backdrop-filter: blur(10px);
+  border: 1px solid rgba(37, 99, 235, 0.2);
+}
+
+.tab-button {
+  background: transparent;
+  border: none;
+  color: rgba(255, 255, 255, 0.7);
+  padding: 0.5rem 1.5rem;
+  border-radius: 0.3rem;
+  cursor: pointer;
+  font-weight: 500;
+  transition: all 0.3s ease;
+
+  &:hover {
+    color: white;
+    background: rgba(37, 99, 235, 0.2);
+  }
+
+  &.active {
+    background: #2563EB;
+    color: white;
+    box-shadow: 0 0 15px rgba(37, 99, 235, 0.5);
+  }
+}
+
+.dashboard-content {
+  display: grid;
+  grid-template-columns: 300px 1fr;
+  gap: 1.5rem;
+
+  .main-content {
+    background: rgba(30, 41, 59, 0.8);
+    border-radius: 0.8rem;
+    padding: 1.5rem;
+    backdrop-filter: blur(10px);
+    border: 1px solid rgba(37, 99, 235, 0.2);
+    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+  }
+}

+ 6 - 6
ai-interview/src/moudles/interviewer/interviewer-home/interviewer-home.spec.ts → ai-interview/src/app/hr/dashboard/dashboard.spec.ts

@@ -1,18 +1,18 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { InterviewerHome } from './interviewer-home';
+import { Dashboard } from './dashboard';
 
-describe('InterviewerHome', () => {
-  let component: InterviewerHome;
-  let fixture: ComponentFixture<InterviewerHome>;
+describe('Dashboard', () => {
+  let component: Dashboard;
+  let fixture: ComponentFixture<Dashboard>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      imports: [InterviewerHome]
+      imports: [Dashboard]
     })
     .compileComponents();
 
-    fixture = TestBed.createComponent(InterviewerHome);
+    fixture = TestBed.createComponent(Dashboard);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });

+ 24 - 0
ai-interview/src/app/hr/dashboard/dashboard.ts

@@ -0,0 +1,24 @@
+// dashboard.component.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { StatusCard } from '../status-card/status-card';
+import { ResumeFilter } from '../resume-filter/resume-filter';
+import { CompanyProfile } from '../company-profile/company-profile';
+
+@Component({
+  selector: 'app-dashboard',
+  standalone: true,
+  imports: [
+    CommonModule, 
+    RouterModule,
+    StatusCard,
+    ResumeFilter,
+    CompanyProfile
+  ],
+  templateUrl: './dashboard.html',
+  styleUrls: ['./dashboard.scss']
+})
+export class Dashboard {
+  activeTab: 'resumes' | 'company' = 'resumes';
+}

+ 10 - 0
ai-interview/src/app/hr/dashboard/hr.routes.ts

@@ -0,0 +1,10 @@
+import { Routes } from '@angular/router';
+
+
+export const HrRoutes: Routes = [
+  { 
+    path: '', 
+    loadComponent: () => import('./dashboard').then(m=>m.Dashboard)
+  },
+  
+];

+ 14 - 0
ai-interview/src/app/hr/models/company.model.ts

@@ -0,0 +1,14 @@
+// company.model.ts
+export interface JobOpening {
+  title: string;
+  description: string;
+  requirements: string;
+}
+
+export interface Company {
+  name: string;
+  address: string;
+  industry: string;
+  description: string;
+  jobOpenings: JobOpening[];
+}

+ 10 - 0
ai-interview/src/app/hr/models/resume.model.ts

@@ -0,0 +1,10 @@
+// resume.model.ts
+export interface Resume {
+  id: string;
+  name: string;
+  position: string;
+  education: '博士' | '硕士' | '本科' | '大专';
+  experience: number;
+  skills: string[];
+  status: 'pending' | 'approved' | 'rejected';
+}

+ 79 - 0
ai-interview/src/app/hr/resume-filter/resume-filter.html

@@ -0,0 +1,79 @@
+<!-- resume-filter.component.html -->
+<div class="resume-filter-container">
+  <div class="filter-controls">
+    <div class="search-box">
+      <input 
+        type="text" 
+        [(ngModel)]="filters.search" 
+        (input)="applyFilters()"
+        placeholder="搜索姓名或职位...">
+      <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
+        <path d="M11 19C15.4183 19 19 15.4183 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11C3 15.4183 6.58172 19 11 19Z" stroke="#7C3AED" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <path d="M21 21L16.65 16.65" stroke="#7C3AED" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+      </svg>
+    </div>
+
+    <div class="filter-group">
+      <select [(ngModel)]="filters.education" (change)="applyFilters()">
+        <option value="">所有学历</option>
+        <option value="博士">博士</option>
+        <option value="硕士">硕士</option>
+        <option value="本科">本科</option>
+        <option value="大专">大专</option>
+      </select>
+
+      <select [(ngModel)]="filters.experience" (change)="applyFilters()">
+        <option value="">所有经验</option>
+        <option value="1">1年以上</option>
+        <option value="3">3年以上</option>
+        <option value="5">5年以上</option>
+      </select>
+
+      <select [(ngModel)]="filters.status" (change)="applyFilters()">
+        <option value="">所有状态</option>
+        <option value="pending">待处理</option>
+        <option value="approved">已通过</option>
+        <option value="rejected">已拒绝</option>
+      </select>
+    </div>
+  </div>
+
+  <div class="resume-list">
+    <div *ngFor="let resume of filteredResumes" class="resume-card">
+      <div class="resume-header">
+        <div class="avatar">{{ resume.name.charAt(0) }}</div>
+        <div class="resume-info">
+          <h3>{{ resume.name }}</h3>
+          <p>{{ resume.position }} · {{ resume.experience }}年经验</p>
+        </div>
+        <div class="education-badge">{{ resume.education }}</div>
+      </div>
+
+      <div class="resume-skills">
+        <span *ngFor="let skill of resume.skills" class="skill-tag">{{ skill }}</span>
+      </div>
+
+      <div class="resume-actions">
+        <button 
+          class="approve" 
+          (click)="updateStatus(resume, 'approved')"
+          [class.active]="resume.status === 'approved'">
+          通过
+        </button>
+        <button 
+          class="reject" 
+          (click)="updateStatus(resume, 'rejected')"
+          [class.active]="resume.status === 'rejected'">
+          拒绝
+        </button>
+      </div>
+
+      <div class="resume-status" [class.pending]="resume.status === 'pending'" 
+                               [class.approved]="resume.status === 'approved'" 
+                               [class.rejected]="resume.status === 'rejected'">
+        {{ resume.status === 'pending' ? '待处理' : 
+           resume.status === 'approved' ? '已通过' : '已拒绝' }}
+      </div>
+    </div>
+  </div>
+</div>

+ 212 - 0
ai-interview/src/app/hr/resume-filter/resume-filter.scss

@@ -0,0 +1,212 @@
+/* resume-filter.component.scss */
+.resume-filter-container {
+  display: flex;
+  flex-direction: column;
+  gap: 1.5rem;
+}
+
+.filter-controls {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+}
+
+.search-box {
+  position: relative;
+  
+  input {
+    width: 100%;
+    padding: 0.8rem 1rem 0.8rem 2.5rem;
+    background: rgba(15, 23, 42, 0.7);
+    border: 1px solid rgba(37, 99, 235, 0.3);
+    border-radius: 0.5rem;
+    color: white;
+    font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+    transition: all 0.3s ease;
+
+    &:focus {
+      outline: none;
+      border-color: #7C3AED;
+      box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.3);
+    }
+
+    &::placeholder {
+      color: rgba(255, 255, 255, 0.5);
+    }
+  }
+
+  svg {
+    position: absolute;
+    left: 1rem;
+    top: 50%;
+    transform: translateY(-50%);
+  }
+}
+
+.filter-group {
+  display: flex;
+  gap: 1rem;
+
+  select {
+    flex: 1;
+    padding: 0.7rem 1rem;
+    background: rgba(15, 23, 42, 0.7);
+    border: 1px solid rgba(37, 99, 235, 0.3);
+    border-radius: 0.5rem;
+    color: white;
+    font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+    cursor: pointer;
+    transition: all 0.3s ease;
+
+    &:focus {
+      outline: none;
+      border-color: #7C3AED;
+      box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.3);
+    }
+  }
+}
+
+.resume-list {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+  gap: 1.5rem;
+}
+
+.resume-card {
+  background: rgba(15, 23, 42, 0.7);
+  border-radius: 0.8rem;
+  padding: 1.5rem;
+  border: 1px solid rgba(37, 99, 235, 0.2);
+  transition: all 0.3s ease;
+  position: relative;
+  overflow: hidden;
+
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 10px 25px rgba(37, 99, 235, 0.2);
+    border-color: rgba(37, 99, 235, 0.5);
+  }
+}
+
+.resume-header {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+  margin-bottom: 1.2rem;
+}
+
+.avatar {
+  width: 50px;
+  height: 50px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #2563EB 0%, #7C3AED 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 600;
+  font-size: 1.2rem;
+  color: white;
+}
+
+.resume-info {
+  flex: 1;
+
+  h3 {
+    margin: 0;
+    font-size: 1.1rem;
+    font-weight: 600;
+    color: white;
+  }
+
+  p {
+    margin: 0.3rem 0 0 0;
+    font-size: 0.9rem;
+    color: rgba(255, 255, 255, 0.7);
+  }
+}
+
+.education-badge {
+  background: rgba(37, 99, 235, 0.2);
+  color: #2563EB;
+  padding: 0.3rem 0.8rem;
+  border-radius: 1rem;
+  font-size: 0.8rem;
+  font-weight: 500;
+}
+
+.resume-skills {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.5rem;
+  margin-bottom: 1.5rem;
+}
+
+.skill-tag {
+  background: rgba(124, 58, 237, 0.2);
+  color: #7C3AED;
+  padding: 0.3rem 0.8rem;
+  border-radius: 1rem;
+  font-size: 0.8rem;
+  font-weight: 500;
+}
+
+.resume-actions {
+  display: flex;
+  gap: 0.8rem;
+
+  button {
+    flex: 1;
+    padding: 0.6rem;
+    border-radius: 0.5rem;
+    border: none;
+    font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.3s ease;
+
+    &.approve {
+      background: rgba(16, 185, 129, 0.2);
+      color: #10B981;
+
+      &:hover, &.active {
+        background: #10B981;
+        color: white;
+      }
+    }
+
+    &.reject {
+      background: rgba(245, 158, 11, 0.2);
+      color: #F59E0B;
+
+      &:hover, &.active {
+        background: #F59E0B;
+        color: white;
+      }
+    }
+  }
+}
+
+.resume-status {
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding: 0.3rem 1rem;
+  border-bottom-left-radius: 0.8rem;
+  font-size: 0.8rem;
+  font-weight: 500;
+
+  &.pending {
+    background: rgba(37, 99, 235, 0.2);
+    color: #2563EB;
+  }
+
+  &.approved {
+    background: rgba(16, 185, 129, 0.2);
+    color: #10B981;
+  }
+
+  &.rejected {
+    background: rgba(245, 158, 11, 0.2);
+    color: #F59E0B;
+  }
+}

+ 6 - 6
ai-interview/src/moudles/interviewee/interviewee-home/interviewee-home.spec.ts → ai-interview/src/app/hr/resume-filter/resume-filter.spec.ts

@@ -1,18 +1,18 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { IntervieweeHome } from './interviewee-home';
+import { ResumeFilter } from './resume-filter';
 
-describe('IntervieweeHome', () => {
-  let component: IntervieweeHome;
-  let fixture: ComponentFixture<IntervieweeHome>;
+describe('ResumeFilter', () => {
+  let component: ResumeFilter;
+  let fixture: ComponentFixture<ResumeFilter>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      imports: [IntervieweeHome]
+      imports: [ResumeFilter]
     })
     .compileComponents();
 
-    fixture = TestBed.createComponent(IntervieweeHome);
+    fixture = TestBed.createComponent(ResumeFilter);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });

+ 47 - 0
ai-interview/src/app/hr/resume-filter/resume-filter.ts

@@ -0,0 +1,47 @@
+// resume-filter.component.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Resume } from '../models/resume.model';
+import { ResumeService } from '../services/resume.service';
+
+@Component({
+  selector: 'app-resume-filter',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './resume-filter.html',
+  styleUrls: ['./resume-filter.scss']
+})
+export class ResumeFilter {
+  resumes: Resume[] = [];
+  filteredResumes: Resume[] = [];
+  filters = {
+    education: '',
+    experience: '',
+    status: '',
+    search: ''
+  };
+
+  constructor(private resumeService: ResumeService) {
+    this.resumes = this.resumeService.getResumes();
+    this.filteredResumes = [...this.resumes];
+  }
+
+  applyFilters() {
+    this.filteredResumes = this.resumes.filter(resume => {
+      return (
+        (this.filters.education === '' || resume.education === this.filters.education) &&
+        (this.filters.experience === '' || resume.experience >= +this.filters.experience) &&
+        (this.filters.status === '' || resume.status === this.filters.status) &&
+        (this.filters.search === '' || 
+         resume.name.toLowerCase().includes(this.filters.search.toLowerCase()) ||
+         resume.position.toLowerCase().includes(this.filters.search.toLowerCase()))
+      );
+    });
+  }
+
+  updateStatus(resume: Resume, status: 'pending' | 'approved' | 'rejected') {
+    resume.status = status;
+    this.applyFilters();
+  }
+}

+ 40 - 0
ai-interview/src/app/hr/services/company.service.ts

@@ -0,0 +1,40 @@
+// company.service.ts
+import { Injectable } from '@angular/core';
+import { Company } from '../models/company.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class CompanyService {
+  private company: Company = {
+    name: '未来科技股份有限公司',
+    address: '北京市海淀区中关村软件园二期8号楼',
+    industry: '人工智能与大数据',
+    description: '未来科技是一家专注于人工智能和大数据技术研发的高科技企业,致力于为企业提供智能化解决方案。我们拥有一支由博士和硕士组成的高素质研发团队,在自然语言处理、计算机视觉和机器学习领域具有深厚的技术积累。',
+    jobOpenings: [
+      {
+        title: '高级前端工程师',
+        description: '负责公司核心产品的前端开发工作,参与技术方案设计和架构优化。',
+        requirements: '3年以上前端开发经验,精通Angular/React/Vue等框架'
+      },
+      {
+        title: 'Java后端开发',
+        description: '参与公司后端服务开发,负责高并发、分布式系统的设计与实现。',
+        requirements: '5年以上Java开发经验,熟悉Spring Cloud微服务架构'
+      },
+      {
+        title: 'AI算法工程师',
+        description: '负责机器学习算法的研发和优化,参与产品智能化功能的实现。',
+        requirements: '硕士及以上学历,熟悉TensorFlow/PyTorch等框架'
+      }
+    ]
+  };
+
+  getCompany(): Company {
+    return {...this.company};
+  }
+
+  updateCompany(updatedCompany: Company): void {
+    this.company = {...updatedCompany};
+  }
+}

+ 51 - 0
ai-interview/src/app/hr/services/resume.service.ts

@@ -0,0 +1,51 @@
+// resume.service.ts
+import { Injectable } from '@angular/core';
+import { Resume } from '../models/resume.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ResumeService {
+  private resumes: Resume[] = [
+    {
+      id: '1',
+      name: '张伟',
+      position: '高级前端工程师',
+      education: '硕士',
+      experience: 5,
+      skills: ['Angular', 'TypeScript', 'RxJS', 'SCSS'],
+      status: 'pending'
+    },
+    {
+      id: '2',
+      name: '李娜',
+      position: '后端开发工程师',
+      education: '本科',
+      experience: 3,
+      skills: ['Java', 'Spring Boot', 'MySQL', '微服务'],
+      status: 'approved'
+    },
+    {
+      id: '3',
+      name: '王强',
+      position: 'UI设计师',
+      education: '本科',
+      experience: 4,
+      skills: ['Figma', 'Sketch', 'Photoshop', '用户体验设计'],
+      status: 'rejected'
+    },
+    {
+      id: '4',
+      name: '赵敏',
+      position: '数据分析师',
+      education: '博士',
+      experience: 2,
+      skills: ['Python', 'SQL', '机器学习', '数据可视化'],
+      status: 'pending'
+    }
+  ];
+
+  getResumes(): Resume[] {
+    return this.resumes;
+  }
+}

+ 14 - 0
ai-interview/src/app/hr/status-card/status-card.html

@@ -0,0 +1,14 @@
+<!-- status-card.component.html -->
+<div class="status-card-container">
+  <div class="stat-card" *ngFor="let stat of stats">
+    <h3 class="stat-title">{{ stat.title }}</h3>
+    <div class="stat-value">{{ stat.value }}</div>
+    <div class="stat-change" [class.up]="stat.trend === 'up'" [class.down]="stat.trend === 'down'">
+      {{ stat.change }}
+      <svg width="12" height="12" viewBox="0 0 24 24" fill="none">
+        <path *ngIf="stat.trend === 'up'" d="M12 4L12 20M12 4L18 10M12 4L6 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <path *ngIf="stat.trend === 'down'" d="M12 20L12 4M12 20L18 14M12 20L6 14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+      </svg>
+    </div>
+  </div>
+</div>

+ 56 - 0
ai-interview/src/app/hr/status-card/status-card.scss

@@ -0,0 +1,56 @@
+/* status-card.component.scss */
+.status-card-container {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 1rem;
+  margin-bottom: 1.5rem;
+}
+
+.stat-card {
+  background: rgba(30, 41, 59, 0.8);
+  border-radius: 0.6rem;
+  padding: 1.2rem;
+  backdrop-filter: blur(10px);
+  border: 1px solid rgba(37, 99, 235, 0.2);
+  transition: transform 0.3s ease, box-shadow 0.3s ease;
+
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 10px 20px rgba(37, 99, 235, 0.2);
+    border-color: rgba(37, 99, 235, 0.5);
+  }
+}
+
+.stat-title {
+  font-size: 0.9rem;
+  color: rgba(255, 255, 255, 0.7);
+  margin: 0 0 0.5rem 0;
+  font-weight: 500;
+}
+
+.stat-value {
+  font-size: 1.8rem;
+  font-weight: 600;
+  color: white;
+  margin-bottom: 0.3rem;
+}
+
+.stat-change {
+  display: flex;
+  align-items: center;
+  gap: 0.3rem;
+  font-size: 0.9rem;
+  font-weight: 500;
+
+  &.up {
+    color: #10B981;
+  }
+
+  &.down {
+    color: #F59E0B;
+  }
+
+  svg {
+    margin-top: 2px;
+  }
+}

+ 23 - 0
ai-interview/src/app/hr/status-card/status-card.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { StatusCard } from './status-card';
+
+describe('StatusCard', () => {
+  let component: StatusCard;
+  let fixture: ComponentFixture<StatusCard>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [StatusCard]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(StatusCard);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 19 - 0
ai-interview/src/app/hr/status-card/status-card.ts

@@ -0,0 +1,19 @@
+// status-card.component.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-status-card',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './status-card.html',
+  styleUrls: ['./status-card.scss']
+})
+export class StatusCard {
+  stats = [
+    { title: '待处理简历', value: 24, change: '+5', trend: 'up' },
+    { title: '已通过', value: 56, change: '+12', trend: 'up' },
+    { title: '已拒绝', value: 32, change: '-3', trend: 'down' },
+    { title: '面试安排', value: 18, change: '+8', trend: 'up' }
+  ];
+}

+ 67 - 0
ai-interview/src/app/interviewee-auth/auth.html

@@ -0,0 +1,67 @@
+<!-- auth.component.html -->
+<div class="auth-container">
+  <!-- 装饰元素 -->
+  <div class="decorator decorator-1"></div>
+  <div class="decorator decorator-2"></div>
+  
+  <div class="auth-header">
+    <h1 class="auth-title">{{ isLoginMode() ? '用户登录' : '用户注册' }}</h1>
+    <p class="auth-subtitle">{{ isLoginMode() ? '欢迎回到未来世界' : '加入我们的科技社区' }}</p>
+  </div>
+
+  <!-- 错误消息 -->
+  @if (errorMessage()) {
+    <div class="message error-message">
+      {{ errorMessage() }}
+    </div>
+  }
+
+  <!-- 成功消息 -->
+  @if (successMessage()) {
+    <div class="message success-message">
+      {{ successMessage() }}
+    </div>
+  }
+
+  <form class="auth-form" (ngSubmit)="onSubmit()">
+    <div class="form-group">
+      <label class="form-label" for="username">用户名</label>
+      <input 
+        class="form-input" 
+        id="username" 
+        type="text" 
+        [(ngModel)]="username" 
+        name="username" 
+        required
+        placeholder="输入您的用户名"
+      >
+    </div>
+
+    <div class="form-group">
+      <label class="form-label" for="password">密码</label>
+      <input 
+        class="form-input" 
+        id="password" 
+        type="password" 
+        [(ngModel)]="password" 
+        name="password" 
+        required
+        placeholder="输入您的密码"
+      >
+      @if (!isLoginMode()) {
+        <small style="color: #94a3b8; font-size: 0.8rem;">密码长度必须大于8个字符</small>
+      }
+    </div>
+
+    <button class="submit-btn" type="submit">
+      {{ isLoginMode() ? '登录' : '注册' }}
+    </button>
+  </form>
+
+  <div class="mode-toggle">
+    {{ isLoginMode() ? '没有账号?' : '已有账号?' }}
+    <a class="toggle-link" (click)="toggleMode()">
+      {{ isLoginMode() ? '立即注册' : '立即登录' }}
+    </a>
+  </div>
+</div>

+ 13 - 0
ai-interview/src/app/interviewee-auth/auth.routes.ts

@@ -0,0 +1,13 @@
+import { Routes } from '@angular/router';
+
+
+export const AuthRoutes: Routes = [
+  { 
+    path: '', 
+    loadComponent: () => import('./auth').then(m=>m.Auth)
+  },
+  {
+    path:'home',
+    loadChildren: () => import('../interviewer/home/home.routes').then(m => m.HomeRoutes),
+  }
+];

+ 223 - 0
ai-interview/src/app/interviewee-auth/auth.scss

@@ -0,0 +1,223 @@
+/* auth.component.css */
+:host {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-height: 100vh;
+  background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
+  font-family: 'Inter', 'Alibaba PuHuiTi', sans-serif;
+  color: white;
+}
+
+.auth-container {
+  width: 100%;
+  max-width: 420px;
+  padding: 2.5rem;
+  background: rgba(30, 41, 59, 0.8);
+  border-radius: 1rem;
+  box-shadow: 0 0 30px rgba(37, 99, 235, 0.3);
+  border: 1px solid rgba(37, 99, 235, 0.2);
+  backdrop-filter: blur(10px);
+  position: relative;
+  overflow: hidden;
+}
+
+.auth-container::before {
+  content: '';
+  position: absolute;
+  top: -50%;
+  left: -50%;
+  width: 200%;
+  height: 200%;
+  background: radial-gradient(circle, rgba(37, 99, 235, 0.1) 0%, transparent 70%);
+  animation: rotate 20s linear infinite;
+  z-index: -1;
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.auth-header {
+  text-align: center;
+  margin-bottom: 2rem;
+}
+
+.auth-title {
+  font-size: 1.8rem;
+  font-weight: 600;
+  margin-bottom: 0.5rem;
+  background: linear-gradient(90deg, #2563EB, #7C3AED);
+  -webkit-background-clip: text;
+  background-clip: text;
+  color: transparent;
+}
+
+.auth-subtitle {
+  font-size: 0.9rem;
+  color: #94a3b8;
+}
+
+.auth-form {
+  display: flex;
+  flex-direction: column;
+  gap: 1.5rem;
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+}
+
+.form-label {
+  font-size: 0.9rem;
+  color: #e2e8f0;
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+}
+
+.form-label::before {
+  content: '>';
+  color: #7C3AED;
+  font-weight: bold;
+}
+
+.form-input {
+  padding: 0.8rem 1rem;
+  background: rgba(15, 23, 42, 0.5);
+  border: 1px solid rgba(37, 99, 235, 0.3);
+  border-radius: 0.5rem;
+  color: white;
+  font-family: inherit;
+  font-size: 1rem;
+  transition: all 0.3s ease;
+}
+
+.form-input:focus {
+  outline: none;
+  border-color: #2563EB;
+  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
+}
+
+.submit-btn {
+  padding: 0.8rem;
+  background: linear-gradient(90deg, #2563EB, #7C3AED);
+  color: white;
+  border: none;
+  border-radius: 0.5rem;
+  font-size: 1rem;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  margin-top: 0.5rem;
+  position: relative;
+  overflow: hidden;
+}
+
+.submit-btn::after {
+  content: '';
+  position: absolute;
+  top: -50%;
+  left: -50%;
+  width: 200%;
+  height: 200%;
+  background: linear-gradient(
+    to bottom right,
+    rgba(255, 255, 255, 0.3) 0%,
+    rgba(255, 255, 255, 0) 60%
+  );
+  transform: rotate(30deg);
+  transition: all 0.3s ease;
+}
+
+.submit-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 5px 15px rgba(37, 99, 235, 0.4);
+}
+
+.submit-btn:hover::after {
+  left: 100%;
+}
+
+.submit-btn:active {
+  transform: translateY(0);
+}
+
+.mode-toggle {
+  text-align: center;
+  margin-top: 1.5rem;
+  font-size: 0.9rem;
+  color: #94a3b8;
+}
+
+.toggle-link {
+  color: #7C3AED;
+  cursor: pointer;
+  font-weight: 600;
+  text-decoration: none;
+  transition: color 0.3s ease;
+}
+
+.toggle-link:hover {
+  color: #2563EB;
+  text-decoration: underline;
+}
+
+.message {
+  padding: 0.8rem;
+  border-radius: 0.5rem;
+  margin-bottom: 1rem;
+  font-size: 0.9rem;
+  text-align: center;
+}
+
+.error-message {
+  background-color: rgba(245, 158, 11, 0.1);
+  border: 1px solid rgba(245, 158, 11, 0.3);
+  color: #F59E0B;
+}
+
+.success-message {
+  background-color: rgba(16, 185, 129, 0.1);
+  border: 1px solid rgba(16, 185, 129, 0.3);
+  color: #10B981;
+}
+
+/* 科幻风格的装饰元素 */
+.decorator {
+  position: absolute;
+  background: rgba(37, 99, 235, 0.1);
+  border: 1px solid rgba(37, 99, 235, 0.3);
+}
+
+.decorator-1 {
+  width: 100px;
+  height: 100px;
+  border-radius: 50%;
+  top: -50px;
+  right: -50px;
+}
+
+.decorator-2 {
+  width: 60px;
+  height: 60px;
+  border-radius: 10px;
+  bottom: -30px;
+  left: -30px;
+  transform: rotate(45deg);
+}
+
+/* 响应式设计 */
+@media (max-width: 480px) {
+  .auth-container {
+    padding: 1.5rem;
+    margin: 0 1rem;
+  }
+}

+ 24 - 0
ai-interview/src/app/interviewee-auth/auth.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideHttpClient } from '@angular/common/http';
+import { Auth } from './auth';
+
+describe('Auth', () => {
+  let component: Auth;
+  let fixture: ComponentFixture<Auth>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [Auth],
+      providers: [provideHttpClient()]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(Auth);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 81 - 0
ai-interview/src/app/interviewee-auth/auth.ts

@@ -0,0 +1,81 @@
+import { Component, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Router, RouterModule } from '@angular/router';
+import { HttpClient, HttpClientModule } from '@angular/common/http';
+
+@Component({
+  selector: 'app-auth',
+  standalone: true,
+  imports: [CommonModule, FormsModule, RouterModule, HttpClientModule],
+  templateUrl: './auth.html',
+  styleUrls: ['./auth.scss']
+})
+export class Auth {
+  constructor(private router: Router, private http: HttpClient) {}
+
+  // 使用signal管理状态
+  isLoginMode = signal(true);
+  username = signal('');
+  password = signal('');
+  errorMessage = signal('');
+  successMessage = signal('');
+
+  // 切换登录/注册模式
+  toggleMode() {
+    this.isLoginMode.set(!this.isLoginMode());
+    this.errorMessage.set('');
+    this.successMessage.set('');
+  }
+
+  // 处理表单提交
+  onSubmit() {
+    if (this.isLoginMode()) {
+      this.handleLogin();
+    } else {
+      this.handleRegister();
+    }
+  }
+
+  // 登录逻辑
+  private handleLogin() {
+    this.http.post<any>('http://localhost:3000/api/login', {
+      username: this.username(),
+      password: this.password()
+    }).subscribe({
+      next: (response) => {
+        if (response.success) {
+          this.successMessage.set('登录成功! 欢迎回来,' + this.username());
+          this.errorMessage.set('');
+          this.router.navigate(['/intervieweeauth/home']);
+        } else {
+          this.errorMessage.set(response.message);
+        }
+      },
+      error: (error) => {
+        this.errorMessage.set('登录失败,请稍后再试');
+      }
+    });
+  }
+
+  // 注册逻辑
+  private handleRegister() {
+    this.http.post<any>('http://localhost:3000/api/register', {
+      username: this.username(),
+      password: this.password()
+    }).subscribe({
+      next: (response) => {
+        if (response.success) {
+          this.successMessage.set('注册成功! 您现在可以登录了');
+          this.errorMessage.set('');
+          this.isLoginMode.set(true); // 自动切换到登录模式
+        } else {
+          this.errorMessage.set(response.message);
+        }
+      },
+      error: (error) => {
+        this.errorMessage.set('注册失败,请稍后再试');
+      }
+    });
+  }
+}

+ 4 - 0
ai-interview/src/app/interviewee-auth/environment.ts

@@ -0,0 +1,4 @@
+export const environment = {
+  production: false,
+  apiUrl: 'http://localhost:3000/api'
+};

+ 104 - 0
ai-interview/src/app/interviewee-auth/server.js

@@ -0,0 +1,104 @@
+const express = require('express');
+const mysql = require('mysql2/promise');
+const bodyParser = require('body-parser');
+const cors = require('cors');
+
+const app = express();
+app.use(bodyParser.json());
+app.use(cors());
+
+// MySQL数据库配置
+const dbConfig = {
+  host: 'localhost',
+  user: 'auth',
+  password: '123456',
+  database: 'auth_interviewee',
+  waitForConnections: true,
+  connectionLimit: 10,
+  queueLimit: 0
+};
+
+// 创建连接池
+const pool = mysql.createPool(dbConfig);
+
+// 创建用户表(如果不存在)
+async function initializeDatabase() {
+  const connection = await pool.getConnection();
+  try {
+    await connection.query(`
+      CREATE TABLE IF NOT EXISTS users (
+        id INT AUTO_INCREMENT PRIMARY KEY,
+        username VARCHAR(255) NOT NULL UNIQUE,
+        password VARCHAR(255) NOT NULL,
+        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+      )
+    `);
+    console.log('Database initialized');
+  } catch (error) {
+    console.error('Database initialization error:', error);
+  } finally {
+    connection.release();
+  }
+}
+
+initializeDatabase();
+
+// 登录接口
+app.post('/api/login', async (req, res) => {
+  const { username, password } = req.body;
+  
+  try {
+    const [rows] = await pool.query(
+      'SELECT * FROM users WHERE username = ? AND password = ?', 
+      [username, password]
+    );
+    
+    if (rows.length > 0) {
+      res.json({ success: true, message: '登录成功', user: rows[0] });
+    } else {
+      res.status(401).json({ success: false, message: '用户名或密码错误' });
+    }
+  } catch (error) {
+    res.status(500).json({ success: false, message: '服务器错误' });
+  }
+});
+
+// 注册接口
+app.post('/api/register', async (req, res) => {
+  const { username, password } = req.body;
+  
+  // 验证密码长度
+  if (password.length <= 8) {
+    return res.status(400).json({ success: false, message: '密码长度必须大于8个字符' });
+  }
+  
+  try {
+    const [result] = await pool.query(
+      'INSERT INTO users (username, password) VALUES (?, ?)',
+      [username, password]
+    );
+    
+    res.json({ success: true, message: '注册成功' });
+  } catch (error) {
+    if (error.code === 'ER_DUP_ENTRY') {
+      res.status(400).json({ success: false, message: '用户名已存在' });
+    } else {
+      res.status(500).json({ success: false, message: '注册失败' });
+    }
+  }
+});
+
+const PORT = 3000;
+
+app.listen(PORT, () => {
+  console.log(`Server running on http://localhost:${PORT}`);
+});
+// 测试数据库连接
+pool.getConnection()
+  .then(conn => {
+    console.log('Successfully connected to MySQL');
+    conn.release();
+  })
+  .catch(err => {
+    console.error('MySQL connection error:', err);
+  });

二进制
ai-interview/src/app/interviewer/home/asstes/user-avatar.png


+ 55 - 0
ai-interview/src/app/interviewer/home/company-filter/company-filter.html

@@ -0,0 +1,55 @@
+<!-- company-filter.html -->
+<div class="company-filter-container">
+  <div class="filter-controls">
+    <div class="filter-group">
+      <label for="industry">行业</label>
+      <select id="industry" [(ngModel)]="selectedIndustry" class="glass-select">
+        <option *ngFor="let industry of industries" [value]="industry">{{ industry }}</option>
+      </select>
+    </div>
+    
+    <div class="filter-group">
+      <label for="region">地区</label>
+      <select id="region" [(ngModel)]="selectedRegion" class="glass-select">
+        <option *ngFor="let region of regions" [value]="region">{{ region }}</option>
+      </select>
+    </div>
+    
+    <div class="filter-group">
+      <label>距离: {{ radius }} km</label>
+      <input type="range" min="1" max="20" [(ngModel)]="radius" class="range-slider">
+    </div>
+  </div>
+  
+  <div class="content-area">
+    <div class="company-list">
+      <div 
+        *ngFor="let company of filteredCompanies" 
+        class="company-card"
+        [class.highlighted]="company.highlighted"
+        (click)="highlightCompany(company)">
+        <div class="company-logo">
+          <img [src]="company.logo" [alt]="company.name + ' logo'">
+        </div>
+        <div class="company-info">
+          <h3>{{ company.name }}</h3>
+          <div class="meta">
+            <span class="industry">{{ company.industry }}</span>
+            <span class="rating">
+              <i class="icon-star"></i>
+              {{ company.rating }}
+            </span>
+          </div>
+          <p class="location">
+            <i class="icon-pin"></i>
+            {{ company.location }} ({{ company.region }})
+          </p>
+          <div class="distance">
+            <i class="icon-location"></i>
+            {{ company.distance }} km
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 175 - 0
ai-interview/src/app/interviewer/home/company-filter/company-filter.scss

@@ -0,0 +1,175 @@
+// company-filter.scss
+.company-filter-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  
+  .filter-controls {
+    display: flex;
+    gap: 1.5rem;
+    margin-bottom: 1.5rem;
+    padding: 1rem;
+    background: rgba(30, 41, 59, 0.6);
+    border-radius: 12px;
+    backdrop-filter: blur(10px);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    
+    .filter-group {
+      flex: 1;
+      
+      label {
+        display: block;
+        margin-bottom: 0.5rem;
+        font-size: 0.85rem;
+        color: rgba(255, 255, 255, 0.7);
+      }
+      
+      .glass-select {
+        width: 100%;
+        padding: 0.75rem;
+        background: rgba(255, 255, 255, 0.1);
+        border: 1px solid rgba(255, 255, 255, 0.2);
+        border-radius: 8px;
+        color: rgb(11, 180, 242);
+        font-family: 'Inter', sans-serif;
+        backdrop-filter: blur(5px);
+        transition: all 0.3s ease;
+        
+        &:focus {
+          outline: none;
+          border-color: #7C3AED;
+          box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.3);
+        }
+      }
+      
+      .range-slider {
+        width: 100%;
+        height: 8px;
+        -webkit-appearance: none;
+        background: rgba(255, 255, 255, 0.1);
+        border-radius: 4px;
+        outline: none;
+        
+        &::-webkit-slider-thumb {
+          -webkit-appearance: none;
+          width: 18px;
+          height: 18px;
+          border-radius: 50%;
+          background: #7C3AED;
+          cursor: pointer;
+          transition: all 0.2s ease;
+          
+          &:hover {
+            transform: scale(1.1);
+            box-shadow: 0 0 10px rgba(124, 58, 237, 0.5);
+          }
+        }
+      }
+    }
+  }
+  
+  .content-area {
+    flex: 1;
+    overflow: hidden;
+    
+    .company-list {
+      height: 100%;
+      overflow-y: auto;
+      padding-right: 0.5rem;
+      
+      &::-webkit-scrollbar {
+        width: 6px;
+      }
+      
+      &::-webkit-scrollbar-track {
+        background: rgba(255, 255, 255, 0.05);
+        border-radius: 3px;
+      }
+      
+      &::-webkit-scrollbar-thumb {
+        background: rgba(255, 255, 255, 0.2);
+        border-radius: 3px;
+      }
+      
+      .company-card {
+        background: rgba(30, 41, 59, 0.6);
+        border-radius: 12px;
+        padding: 1rem;
+        margin-bottom: 1rem;
+        display: flex;
+        gap: 1rem;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        border: 1px solid transparent;
+        
+        &:hover {
+          background: rgba(30, 41, 59, 0.8);
+          transform: translateY(-2px);
+        }
+        
+        &.highlighted {
+          border: 1px solid #7C3AED;
+          box-shadow: 0 0 20px rgba(124, 58, 237, 0.3);
+          background: rgba(30, 41, 59, 0.8);
+        }
+        
+        .company-logo {
+          width: 60px;
+          height: 60px;
+          border-radius: 12px;
+          background: rgba(255, 255, 255, 0.1);
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          overflow: hidden;
+          border: 1px solid rgba(255, 255, 255, 0.1);
+          
+          img {
+            max-width: 80%;
+            max-height: 80%;
+            object-fit: contain;
+          }
+        }
+        
+        .company-info {
+          flex: 1;
+          
+          h3 {
+            margin: 0 0 0.5rem 0;
+            font-size: 1.1rem;
+          }
+          
+          .meta {
+            display: flex;
+            gap: 1rem;
+            margin-bottom: 0.5rem;
+            font-size: 0.85rem;
+            
+            .industry {
+              background: rgba(124, 58, 237, 0.2);
+              color: #7C3AED;
+              padding: 0.25rem 0.75rem;
+              border-radius: 20px;
+            }
+            
+            .rating {
+              color: #F59E0B;
+              display: flex;
+              align-items: center;
+              gap: 0.25rem;
+            }
+          }
+          
+          .location, .distance {
+            font-size: 0.85rem;
+            color: rgba(255, 255, 255, 0.7);
+            display: flex;
+            align-items: center;
+            gap: 0.5rem;
+            margin: 0.25rem 0;
+          }
+        }
+      }
+    }
+  }
+}

+ 23 - 0
ai-interview/src/app/interviewer/home/company-filter/company-filter.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CompanyFilter } from './company-filter';
+
+describe('CompanyFilter', () => {
+  let component: CompanyFilter;
+  let fixture: ComponentFixture<CompanyFilter>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [CompanyFilter]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(CompanyFilter);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 133 - 0
ai-interview/src/app/interviewer/home/company-filter/company-filter.ts

@@ -0,0 +1,133 @@
+// company-filter.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+interface Company {
+  id: number;
+  name: string;
+  industry: string;
+  location: string;
+  region: string;
+  distance: number;
+  rating: number;
+  logo: string;
+  highlighted: boolean;
+}
+
+@Component({
+  selector: 'app-company-filter',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './company-filter.html',
+  styleUrls: ['./company-filter.scss']
+})
+export class CompanyFilter {
+  industries = ['全部', '科技', '金融', '医疗', '教育', '制造', '零售', '设计', '区块链'];
+  regions = ['全部', '北京', '上海', '广东', '浙江', '江苏'];
+  selectedIndustry = '全部';
+  selectedRegion = '全部';
+  radius = 10;
+  
+  companies: Company[] = [
+    {
+      id: 1,
+      name: '量子科技',
+      industry: '科技',
+      location: '上海市徐汇区',
+      region: '上海',
+      distance: 2.5,
+      rating: 4.8,
+      logo: 'assets/company-logos/quantum.png',
+      highlighted: false
+    },
+    {
+      id: 2,
+      name: '神经矩阵',
+      industry: '科技',
+      location: '北京市海淀区',
+      region: '北京',
+      distance: 5.1,
+      rating: 4.6,
+      logo: 'assets/company-logos/neural.png',
+      highlighted: false
+    },
+    {
+      id: 3,
+      name: '数据视野',
+      industry: '科技',
+      location: '深圳市南山区',
+      region: '广东',
+      distance: 8.7,
+      rating: 4.5,
+      logo: 'assets/company-logos/datavision.png',
+      highlighted: false
+    },
+    {
+      id: 4,
+      name: '界面未来',
+      industry: '设计',
+      location: '杭州市余杭区',
+      region: '浙江',
+      distance: 12.3,
+      rating: 4.3,
+      logo: 'assets/company-logos/uifuture.png',
+      highlighted: false
+    },
+    {
+      id: 5,
+      name: '链芯科技',
+      industry: '区块链',
+      location: '上海市浦东新区',
+      region: '上海',
+      distance: 3.2,
+      rating: 4.7,
+      logo: 'assets/company-logos/chaincore.png',
+      highlighted: false
+    },
+    {
+      id: 6,
+      name: '云端智能',
+      industry: '科技',
+      location: '广州市天河区',
+      region: '广东',
+      distance: 9.5,
+      rating: 4.4,
+      logo: 'assets/company-logos/cloudai.png',
+      highlighted: false
+    },
+    {
+      id: 7,
+      name: '金陵制造',
+      industry: '制造',
+      location: '南京市鼓楼区',
+      region: '江苏',
+      distance: 15.2,
+      rating: 4.2,
+      logo: 'assets/company-logos/jinling.png',
+      highlighted: false
+    }
+  ];
+  
+  get filteredCompanies() {
+    let filtered = this.companies;
+    
+    // 按行业筛选
+    if (this.selectedIndustry !== '全部') {
+      filtered = filtered.filter(c => c.industry === this.selectedIndustry);
+    }
+    
+    // 按地区筛选
+    if (this.selectedRegion !== '全部') {
+      filtered = filtered.filter(c => c.region === this.selectedRegion);
+    }
+    
+    // 按距离筛选
+    return filtered.filter(c => c.distance <= this.radius);
+  }
+  
+  highlightCompany(company: Company) {
+    this.companies.forEach(c => c.highlighted = false);
+    company.highlighted = true;
+  }
+}

+ 47 - 0
ai-interview/src/app/interviewer/home/home.html

@@ -0,0 +1,47 @@
+<div class="home-container">
+  <header class="app-header">
+    <h1 class="logo">Career<span>Sync</span>AI</h1>
+    <div class="user-avatar">
+      <div class="avatar-glow"></div>
+      <img src="https://tse2-mm.cn.bing.net/th/id/OIP-C.nUQOGtTYj9pheJmRzY94bAHaHa?w=214&h=214&c=7&r=0&o=7&cb=thvnext&dpr=1.5&pid=1.7&rm=3" alt="User Avatar">
+    </div>
+  </header>
+
+  <nav class="tab-navigation">
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'recommendations'"
+      (click)="setActiveTab('recommendations')">
+      <i class="icon-recommend"></i>
+      <span>智能推荐</span>
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'companies'"
+      (click)="setActiveTab('companies')">
+      <i class="icon-filter"></i>
+      <span>公司筛选</span>
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'upload'"
+      (click)="setActiveTab('upload')">
+      <i class="icon-upload"></i>
+      <span>简历投稿</span>
+    </button>
+  </nav>
+
+  <main class="main-content">
+    <div class="content-section" *ngIf="activeTab === 'recommendations'">
+      <app-job-recommendations></app-job-recommendations>
+    </div>
+    
+    <div class="content-section" *ngIf="activeTab === 'companies'">
+      <app-company-filter></app-company-filter>
+    </div>
+    
+    <div class="content-section" *ngIf="activeTab === 'upload'">
+      <app-resume-upload></app-resume-upload>
+    </div>
+  </main>
+</div>

+ 14 - 0
ai-interview/src/app/interviewer/home/home.routes.ts

@@ -0,0 +1,14 @@
+import { Routes } from '@angular/router';
+import { Home } from './home';
+
+export const HomeRoutes: Routes = [
+  {
+    
+    path: '',
+    component: Home,
+  },
+  {
+      path: 'interviewer', 
+       loadChildren: () => import('../interviewer.routes').then(m => m.interviewerRoutes),
+    }
+];

+ 146 - 0
ai-interview/src/app/interviewer/home/home.scss

@@ -0,0 +1,146 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+
+:host {
+  display: block;
+  height: 100vh;
+  background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
+  color: white;
+  font-family: 'Inter', '阿里巴巴普惠体', sans-serif;
+}
+
+.home-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  padding: 1.5rem;
+  box-sizing: border-box;
+}
+
+.app-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 2rem;
+
+  .logo {
+    font-size: 1.8rem;
+    font-weight: 700;
+    letter-spacing: 1px;
+    background: linear-gradient(90deg, #2563EB 0%, #7C3AED 100%);
+    -webkit-background-clip: text;
+    background-clip: text;
+    color: transparent;
+    
+    span {
+      font-weight: 300;
+    }
+  }
+
+  .user-avatar {
+    position: relative;
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    overflow: hidden;
+    border: 2px solid rgba(#7C3AED, 0.5);
+    
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+
+    .avatar-glow {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border-radius: 50%;
+      background: radial-gradient(circle, rgba(#7C3AED, 0.4) 0%, transparent 70%);
+      animation: pulse 2s infinite alternate;
+    }
+  }
+}
+
+.tab-navigation {
+  display: flex;
+  justify-content: space-around;
+  margin-bottom: 2rem;
+  background: rgba(30, 41, 59, 0.7);
+  border-radius: 12px;
+  padding: 0.5rem;
+  backdrop-filter: blur(10px);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+
+  .tab-button {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 0.75rem;
+    background: none;
+    border: none;
+    color: rgba(255, 255, 255, 0.6);
+    font-size: 0.85rem;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    border-radius: 8px;
+    position: relative;
+    overflow: hidden;
+
+    i {
+      font-size: 1.2rem;
+      margin-bottom: 0.5rem;
+    }
+
+    &.active {
+      color: white;
+      background: rgba(#2563EB, 0.2);
+
+      &::after {
+        content: '';
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        transform: translateX(-50%);
+        width: 40%;
+        height: 3px;
+        background: #2563EB;
+        border-radius: 3px 3px 0 0;
+      }
+    }
+
+    &:hover:not(.active) {
+      background: rgba(255, 255, 255, 0.05);
+      color: rgba(255, 255, 255, 0.8);
+    }
+  }
+}
+
+.main-content {
+  flex: 1;
+  overflow: hidden;
+  border-radius: 16px;
+  background: rgba(15, 23, 42, 0.6);
+  backdrop-filter: blur(10px);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
+}
+
+.content-section {
+  height: 100%;
+  padding: 1.5rem;
+  box-sizing: border-box;
+}
+
+@keyframes pulse {
+  0% {
+    opacity: 0.4;
+    transform: scale(1);
+  }
+  100% {
+    opacity: 0.8;
+    transform: scale(1.05);
+  }
+}

+ 23 - 0
ai-interview/src/app/interviewer/home/home.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Home } from './home';
+
+describe('Home', () => {
+  let component: Home;
+  let fixture: ComponentFixture<Home>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [Home]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(Home);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 21 - 0
ai-interview/src/app/interviewer/home/home.ts

@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { JobRecommendations } from './job-recommendations/job-recommendations';
+import { CompanyFilter } from './company-filter/company-filter';
+import { ResumeUpload } from './resume-upload/resume-upload';
+import { Router } from '@angular/router';
+import { CommonModule } from '@angular/common';
+@Component({
+  selector: 'app-interviewer-home',
+  standalone: true,
+  imports: [JobRecommendations, CompanyFilter,ResumeUpload,CommonModule],
+  templateUrl: './home.html',
+  styleUrls: ['./home.scss']
+})
+export class Home {
+  constructor(private router: Router){}
+  activeTab: 'recommendations' | 'companies' | 'upload' = 'recommendations';
+  
+  setActiveTab(tab: 'recommendations' | 'companies' | 'upload') {
+    this.activeTab = tab;
+  }
+}

+ 57 - 0
ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.html

@@ -0,0 +1,57 @@
+<div class="recommendations-container">
+  <div class="section-header">
+    <h2>为您智能推荐</h2>
+    <div class="match-indicator">
+      <span>匹配度</span>
+      <div class="match-value">{{ cards[currentIndex].match }}%</div>
+    </div>
+  </div>
+
+  <div class="cards-container">
+    <button class="nav-button prev" (click)="prevCard()">
+      <i class="icon-arrow-left"></i>
+    </button>
+    
+    <div class="cards-track">
+      <div 
+        *ngFor="let card of cards; let i = index"
+        class="job-card"
+        [style.transform]="getTransformStyle(i)"
+        [class.active]="i === currentIndex">
+        <div class="card-header">
+          <div class="company-logo">
+            <img [src]="card.logo" [alt]="card.company + ' logo'">
+          </div>
+          <div class="job-info">
+            <h3>{{ card.title }}</h3>
+            <p class="company">{{ card.company }}</p>
+          </div>
+        </div>
+        
+        <div class="card-details">
+          <div class="detail-item">
+            <i class="icon-salary"></i>
+            <span>{{ card.salary }}</span>
+          </div>
+          <div class="detail-item">
+            <i class="icon-location"></i>
+            <span>{{ card.location }}</span>
+          </div>
+        </div>
+        
+        <div class="tags-container">
+          <span class="tag" *ngFor="let tag of card.tags">{{ tag }}</span>
+        </div>
+        
+        <div class="card-actions">
+          <button class="action-button apply" (click)="start()">开始面试</button>
+          <button class="action-button save">收藏</button>
+        </div>
+      </div>
+    </div>
+    
+    <button class="nav-button next" (click)="nextCard()">
+      <i class="icon-arrow-right"></i>
+    </button>
+  </div>
+</div>

+ 209 - 0
ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.scss

@@ -0,0 +1,209 @@
+.recommendations-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 1.5rem;
+    
+    h2 {
+      font-size: 1.5rem;
+      font-weight: 600;
+      margin: 0;
+      background: linear-gradient(90deg, white 0%, #7C3AED 100%);
+      -webkit-background-clip: text;
+      background-clip: text;
+      color: transparent;
+    }
+    
+    .match-indicator {
+      display: flex;
+      align-items: center;
+      gap: 0.5rem;
+      
+      span {
+        font-size: 0.85rem;
+        color: rgba(255, 255, 255, 0.7);
+      }
+      
+      .match-value {
+        font-weight: 700;
+        font-size: 1.1rem;
+        color: #10B981;
+      }
+    }
+  }
+  
+  .cards-container {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    position: relative;
+    
+    .nav-button {
+      width: 48px;
+      height: 48px;
+      border-radius: 50%;
+      background: rgba(37, 99, 235, 0.2);
+      border: 1px solid rgba(37, 99, 235, 0.5);
+      color: white;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      cursor: pointer;
+      z-index: 1000;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        background: rgba(37, 99, 235, 0.4);
+        transform: scale(1.1);
+      }
+      
+      &.prev {
+        margin-right: 1rem;
+      }
+      
+      &.next {
+        margin-left: 1rem;
+      }
+    }
+    
+    .cards-track {
+      flex: 1;
+      height: 100%;
+      position: relative;
+      perspective: 1000px;
+    }
+    
+    .job-card {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      background: rgba(30, 41, 59, 0.8);
+      border-radius: 16px;
+      padding: 1.5rem;
+      box-sizing: border-box;
+      transition: transform 0.5s ease, opacity 0.5s ease;
+      backface-visibility: hidden;
+      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+      border: 1px solid rgba(255, 255, 255, 0.1);
+      display: flex;
+      flex-direction: column;
+      
+      &.active {
+        border: 1px solid rgba(37, 99, 235, 0.5);
+        box-shadow: 0 0 30px rgba(37, 99, 235, 0.3);
+      }
+      
+      .card-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 1.5rem;
+        
+        .company-logo {
+          width: 60px;
+          height: 60px;
+          border-radius: 12px;
+          background: rgba(255, 255, 255, 0.1);
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-right: 1rem;
+          overflow: hidden;
+          border: 1px solid rgba(255, 255, 255, 0.1);
+          
+          img {
+            max-width: 80%;
+            max-height: 80%;
+            object-fit: contain;
+          }
+        }
+        
+        .job-info {
+          h3 {
+            font-size: 1.3rem;
+            margin: 0 0 0.25rem 0;
+            font-weight: 600;
+          }
+          
+          .company {
+            margin: 0;
+            font-size: 0.9rem;
+            color: rgba(255, 255, 255, 0.7);
+          }
+        }
+      }
+      
+      .card-details {
+        display: flex;
+        gap: 1.5rem;
+        margin-bottom: 1.5rem;
+        
+        .detail-item {
+          display: flex;
+          align-items: center;
+          gap: 0.5rem;
+          font-size: 0.9rem;
+          
+          i {
+            color: #7C3AED;
+          }
+        }
+      }
+      
+      .tags-container {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 0.5rem;
+        margin-bottom: 1.5rem;
+        
+        .tag {
+          background: rgba(124, 58, 237, 0.2);
+          color: #7C3AED;
+          padding: 0.25rem 0.75rem;
+          border-radius: 20px;
+          font-size: 0.8rem;
+          border: 1px solid rgba(124, 58, 237, 0.3);
+        }
+      }
+      
+      .card-actions {
+        display: flex;
+        gap: 1rem;
+        margin-top: auto;
+        
+        .action-button {
+          flex: 1;
+          padding: 0.75rem;
+          border-radius: 8px;
+          border: none;
+          font-weight: 500;
+          cursor: pointer;
+          transition: all 0.3s ease;
+          
+          &.apply {
+            background: linear-gradient(90deg, #2563EB 0%, #7C3AED 100%);
+            color: white;
+            
+            &:hover {
+              box-shadow: 0 0 15px rgba(37, 99, 235, 0.5);
+            }
+          }
+          
+          &.save {
+            background: rgba(255, 255, 255, 0.1);
+            color: white;
+            border: 1px solid rgba(255, 255, 255, 0.2);
+            
+            &:hover {
+              background: rgba(255, 255, 255, 0.2);
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 23 - 0
ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { JobRecommendations } from './job-recommendations';
+
+describe('JobRecommendations', () => {
+  let component: JobRecommendations;
+  let fixture: ComponentFixture<JobRecommendations>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [JobRecommendations]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(JobRecommendations);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 108 - 0
ai-interview/src/app/interviewer/home/job-recommendations/job-recommendations.ts

@@ -0,0 +1,108 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Router } from '@angular/router';
+interface JobCard {
+  id: number;
+  title: string;
+  company: string;
+  salary: string;
+  location: string;
+  tags: string[];
+  logo: string;
+  match: number;
+}
+
+@Component({
+  selector: 'app-job-recommendations',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './job-recommendations.html',
+  styleUrls: ['./job-recommendations.scss']
+})
+export class JobRecommendations {
+  currentIndex = 0;
+  cards: JobCard[] = [
+    {
+      id: 1,
+      title: '高级前端工程师',
+      company: '量子科技',
+      salary: '30-50K·15薪',
+      location: '上海·徐汇区',
+      tags: ['React', 'Angular', 'TypeScript'],
+      logo: 'assets/company-logos/quantum.png',
+      match: 92
+    },
+    {
+      id: 2,
+      title: 'AI算法工程师',
+      company: '神经矩阵',
+      salary: '40-60K·16薪',
+      location: '北京·海淀区',
+      tags: ['Python', 'TensorFlow', 'PyTorch'],
+      logo: 'assets/company-logos/neural.png',
+      match: 88
+    },
+    {
+      id: 3,
+      title: '全栈开发工程师',
+      company: '数据视野',
+      salary: '35-45K·14薪',
+      location: '深圳·南山区',
+      tags: ['Node.js', 'Vue', 'MongoDB'],
+      logo: 'assets/company-logos/datavision.png',
+      match: 85
+    },
+    {
+      id: 4,
+      title: 'UX设计师',
+      company: '界面未来',
+      salary: '25-40K·13薪',
+      location: '杭州·余杭区',
+      tags: ['Figma', 'Sketch', '用户研究'],
+      logo: 'assets/company-logos/uifuture.png',
+      match: 78
+    },
+    {
+      id: 5,
+      title: '区块链开发',
+      company: '链芯科技',
+      salary: '45-65K·16薪',
+      location: '上海·浦东新区',
+      tags: ['Solidity', 'Ethereum', '智能合约'],
+      logo: 'assets/company-logos/chaincore.png',
+      match: 83
+    }
+  ];
+  constructor(private router: Router){}
+
+  nextCard() {
+    this.currentIndex = (this.currentIndex + 1) % this.cards.length;
+  }
+
+  prevCard() {
+    this.currentIndex = (this.currentIndex - 1 + this.cards.length) % this.cards.length;
+  }
+
+  getTransformStyle(index: number): string {
+    const offset = index - this.currentIndex;
+    const baseZ = 100;
+    const baseScale = 0.9;
+    const scaleIncrement = 0.05;
+    
+    if (offset === 0) {
+      return `translateX(0) scale(1) z-index: ${baseZ + 2};`;
+    } else if (offset > 0) {
+      const scale = baseScale - (offset * scaleIncrement);
+      const translateX = `${offset * 30}px`;
+      return `translateX(${translateX}) scale(${scale}) z-index: ${baseZ - offset}; opacity: ${1 - (offset * 0.2)};`;
+    } else {
+      const scale = baseScale - (Math.abs(offset) * scaleIncrement);
+      const translateX = `${offset * 30}px`;
+      return `translateX(${translateX}) scale(${scale}) z-index: ${baseZ - Math.abs(offset)}; opacity: ${1 - (Math.abs(offset) * 0.2)};`;
+    }
+  }
+  start()
+  {
+     this.router.navigate(['/auth/home/interviewer']);
+  }
+}

+ 80 - 0
ai-interview/src/app/interviewer/home/resume-upload/resume-upload.html

@@ -0,0 +1,80 @@
+<div class="resume-upload-container">
+  <div class="upload-header">
+    <h2>简历投稿</h2>
+    <p>将您的简历投递给心仪的公司,开启职业新篇章</p>
+  </div>
+  
+  <div class="upload-main">
+    <button class="upload-button" (click)="toggleUploadPanel()">
+      <div class="button-glow"></div>
+      <i class="icon-upload"></i>
+      <span>上传简历</span>
+    </button>
+    
+    <div class="target-companies">
+      <h3>目标公司</h3>
+      <div class="company-tags">
+        <div 
+          *ngFor="let company of targetCompanies" 
+          class="company-tag"
+          [class.selected]="company.selected"
+          (click)="toggleCompanySelection(company)">
+          {{ company.name }}
+        </div>
+      </div>
+    </div>
+  </div>
+  
+  <div class="upload-panel" [class.open]="isUploadPanelOpen">
+    <div class="panel-header">
+      <h3>上传简历</h3>
+      <button class="close-button" (click)="toggleUploadPanel()">
+        <i class="icon-close"></i>
+      </button>
+    </div>
+    
+    <div class="panel-content">
+      <div 
+        class="drop-zone"
+        [class.dragging]="isDragging"
+        (dragover)="onDragOver($event)"
+        (dragleave)="onDragLeave($event)"
+        (drop)="onDrop($event)">
+        <div class="drop-content" *ngIf="uploadStatus === 'idle'">
+          <i class="icon-cloud-upload"></i>
+          <p>拖拽简历文件到此处或</p>
+          <label class="file-input-label">
+            <input type="file" class="file-input" (change)="onFileSelected($event)" accept=".pdf,.doc,.docx">
+            选择文件
+          </label>
+          <p class="hint">支持 PDF, DOC, DOCX 格式,大小不超过 5MB</p>
+        </div>
+        
+        <div class="upload-progress" *ngIf="uploadStatus === 'uploading'">
+          <div class="progress-circle">
+            <svg viewBox="0 0 100 100">
+              <circle cx="50" cy="50" r="45" class="progress-background"></circle>
+              <circle cx="50" cy="50" r="45" class="progress-bar" [style.stroke-dashoffset]="283 - (283 * uploadProgress / 100)"></circle>
+            </svg>
+            <div class="progress-text">{{ uploadProgress }}%</div>
+          </div>
+          <p>正在上传: {{ fileName }}</p>
+        </div>
+        
+        <div class="upload-success" *ngIf="uploadStatus === 'success'">
+          <i class="icon-check-circle"></i>
+          <h4>上传成功!</h4>
+          <p>{{ fileName }} ({{ fileSize }})</p>
+          <button class="action-button" (click)="submitResume()">提交给选定的公司</button>
+        </div>
+        
+        <div class="upload-error" *ngIf="uploadStatus === 'error'">
+          <i class="icon-error"></i>
+          <h4>上传失败</h4>
+          <p>请上传 PDF 或 Word 文档</p>
+          <button class="action-button" (click)="resetUpload()">重试</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 316 - 0
ai-interview/src/app/interviewer/home/resume-upload/resume-upload.scss

@@ -0,0 +1,316 @@
+.resume-upload-container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  
+  .upload-header {
+    margin-bottom: 2rem;
+    
+    h2 {
+      font-size: 1.8rem;
+      margin: 0 0 0.5rem 0;
+      background: linear-gradient(90deg, white 0%, #7C3AED 100%);
+      -webkit-background-clip: text;
+      background-clip: text;
+      color: transparent;
+    }
+    
+    p {
+      margin: 0;
+      color: rgba(255, 255, 255, 0.7);
+      font-size: 0.95rem;
+    }
+  }
+  
+  .upload-main {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 3rem;
+    
+    .upload-button {
+      position: relative;
+      padding: 1.25rem 2.5rem;
+      background: linear-gradient(90deg, #2563EB 0%, #7C3AED 100%);
+      border: none;
+      border-radius: 50px;
+      color: white;
+      font-size: 1.1rem;
+      font-weight: 500;
+      display: flex;
+      align-items: center;
+      gap: 0.75rem;
+      cursor: pointer;
+      overflow: hidden;
+      transition: all 0.3s ease;
+      box-shadow: 0 10px 20px rgba(37, 99, 235, 0.3);
+      z-index: 1;
+      
+      .button-glow {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: radial-gradient(circle, rgba(255, 255, 255, 0.8) 0%, transparent 70%);
+        opacity: 0;
+        animation: breath 3s infinite ease-in-out;
+        z-index: -1;
+      }
+      
+      &:hover {
+        transform: translateY(-3px);
+        box-shadow: 0 15px 30px rgba(37, 99, 235, 0.4);
+      }
+      
+      &:active {
+        transform: translateY(1px);
+      }
+    }
+    
+    .target-companies {
+      width: 100%;
+      max-width: 600px;
+      
+      h3 {
+        text-align: center;
+        margin-bottom: 1.5rem;
+        font-size: 1.2rem;
+        color: rgba(255, 255, 255, 0.9);
+      }
+      
+      .company-tags {
+        display: flex;
+        flex-wrap: wrap;
+        justify-content: center;
+        gap: 0.75rem;
+        
+        .company-tag {
+          padding: 0.75rem 1.5rem;
+          background: rgba(30, 41, 59, 0.6);
+          border-radius: 50px;
+          border: 1px solid rgba(255, 255, 255, 0.1);
+          cursor: pointer;
+          transition: all 0.3s ease;
+          
+          &:hover {
+            background: rgba(37, 99, 235, 0.2);
+            border-color: rgba(37, 99, 235, 0.5);
+          }
+          
+          &.selected {
+            background: rgba(37, 99, 235, 0.4);
+            border-color: #2563EB;
+            color: white;
+            box-shadow: 0 0 15px rgba(37, 99, 235, 0.3);
+          }
+        }
+      }
+    }
+  }
+  
+  .upload-panel {
+    position: fixed;
+    bottom: -100%;
+    left: 0;
+    width: 100%;
+    background: rgba(15, 23, 42, 0.95);
+    border-radius: 24px 24px 0 0;
+    padding: 1.5rem;
+    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.3);
+    backdrop-filter: blur(20px);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    z-index: 1000;
+    transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+    
+    &.open {
+      bottom: 0;
+    }
+    
+    .panel-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 1.5rem;
+      
+      h3 {
+        margin: 0;
+        font-size: 1.3rem;
+      }
+      
+      .close-button {
+        width: 36px;
+        height: 36px;
+        border-radius: 50%;
+        background: rgba(255, 255, 255, 0.1);
+        border: none;
+        color: white;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        
+        &:hover {
+          background: rgba(255, 255, 255, 0.2);
+        }
+      }
+    }
+    
+    .panel-content {
+      .drop-zone {
+        border: 2px dashed rgba(255, 255, 255, 0.2);
+        border-radius: 16px;
+        padding: 2rem;
+        text-align: center;
+        transition: all 0.3s ease;
+        
+        &.dragging {
+          border-color: #7C3AED;
+          background: rgba(124, 58, 237, 0.1);
+        }
+        
+        .drop-content {
+          i {
+            font-size: 3rem;
+            color: #7C3AED;
+            margin-bottom: 1rem;
+          }
+          
+          p {
+            margin: 0.5rem 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          
+          .file-input-label {
+            display: inline-block;
+            margin-top: 1rem;
+            padding: 0.75rem 1.5rem;
+            background: rgba(37, 99, 235, 0.2);
+            color: white;
+            border-radius: 8px;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            border: 1px solid rgba(37, 99, 235, 0.5);
+            
+            &:hover {
+              background: rgba(37, 99, 235, 0.4);
+            }
+            
+            .file-input {
+              display: none;
+            }
+          }
+          
+          .hint {
+            font-size: 0.85rem;
+            color: rgba(255, 255, 255, 0.5);
+          }
+        }
+        
+        .upload-progress {
+          .progress-circle {
+            position: relative;
+            width: 120px;
+            height: 120px;
+            margin: 0 auto 1.5rem;
+            
+            svg {
+              width: 100%;
+              height: 100%;
+              
+              circle {
+                fill: none;
+                stroke-width: 8;
+                transform: rotate(-90deg);
+                transform-origin: 50% 50%;
+                
+                &.progress-background {
+                  stroke: rgba(255, 255, 255, 0.1);
+                }
+                
+                &.progress-bar {
+                  stroke: #7C3AED;
+                  stroke-dasharray: 283;
+                  stroke-linecap: round;
+                  transition: stroke-dashoffset 0.5s ease;
+                }
+              }
+            }
+            
+            .progress-text {
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              transform: translate(-50%, -50%);
+              font-size: 1.5rem;
+              font-weight: 600;
+            }
+          }
+          
+          p {
+            color: rgba(255, 255, 255, 0.8);
+          }
+        }
+        
+        .upload-success, .upload-error {
+          i {
+            font-size: 3rem;
+            margin-bottom: 1rem;
+          }
+          
+          h4 {
+            margin: 0 0 0.5rem 0;
+            font-size: 1.3rem;
+          }
+          
+          p {
+            margin: 0 0 1.5rem 0;
+            color: rgba(255, 255, 255, 0.7);
+          }
+          
+          .action-button {
+            padding: 0.75rem 1.5rem;
+            background: linear-gradient(90deg, #2563EB 0%, #7C3AED 100%);
+            border: none;
+            border-radius: 8px;
+            color: white;
+            font-weight: 500;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            
+            &:hover {
+              transform: translateY(-2px);
+              box-shadow: 0 5px 15px rgba(37, 99, 235, 0.3);
+            }
+          }
+        }
+        
+        .upload-success {
+          i {
+            color: #10B981;
+          }
+        }
+        
+        .upload-error {
+          i {
+            color: #EF4444;
+          }
+        }
+      }
+    }
+  }
+}
+
+@keyframes breath {
+  0%, 100% {
+    opacity: 0.2;
+    transform: scale(1);
+  }
+  50% {
+    opacity: 0.6;
+    transform: scale(1.05);
+  }
+}

+ 23 - 0
ai-interview/src/app/interviewer/home/resume-upload/resume-upload.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ResumeUpload } from './resume-upload';
+
+describe('ResumeUpload', () => {
+  let component: ResumeUpload;
+  let fixture: ComponentFixture<ResumeUpload>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [ResumeUpload]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(ResumeUpload);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 121 - 0
ai-interview/src/app/interviewer/home/resume-upload/resume-upload.ts

@@ -0,0 +1,121 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+  selector: 'app-resume-upload',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './resume-upload.html',
+  styleUrls: ['./resume-upload.scss']
+})
+export class ResumeUpload {
+  isUploadPanelOpen = false;
+  isDragging = false;
+  uploadProgress = 0;
+  uploadStatus: 'idle' | 'uploading' | 'success' | 'error' = 'idle';
+  fileName = '';
+  fileSize = '';
+  
+  targetCompanies = [
+    { id: 1, name: '量子科技', selected: false },
+    { id: 2, name: '神经矩阵', selected: false },
+    { id: 3, name: '数据视野', selected: false },
+    { id: 4, name: '界面未来', selected: false },
+    { id: 5, name: '链芯科技', selected: false }
+  ];
+  
+  toggleUploadPanel() {
+    this.isUploadPanelOpen = !this.isUploadPanelOpen;
+    if (!this.isUploadPanelOpen) {
+      this.resetUpload();
+    }
+  }
+  
+  onDragOver(event: DragEvent) {
+    event.preventDefault();
+    event.stopPropagation();
+    this.isDragging = true;
+  }
+  
+  onDragLeave(event: DragEvent) {
+    event.preventDefault();
+    event.stopPropagation();
+    this.isDragging = false;
+  }
+  
+  onDrop(event: DragEvent) {
+    event.preventDefault();
+    event.stopPropagation();
+    this.isDragging = false;
+    
+    if (event.dataTransfer?.files) {
+      this.handleFiles(event.dataTransfer.files);
+    }
+  }
+  
+  onFileSelected(event: Event) {
+    const input = event.target as HTMLInputElement;
+    if (input.files && input.files.length > 0) {
+      this.handleFiles(input.files);
+    }
+  }
+  
+  handleFiles(files: FileList) {
+    const file = files[0];
+    if (file.type === 'application/pdf' || file.type === 'application/msword' || file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
+      this.fileName = file.name;
+      this.fileSize = this.formatFileSize(file.size);
+      this.startUpload();
+    } else {
+      this.uploadStatus = 'error';
+    }
+  }
+  
+  formatFileSize(bytes: number): string {
+    if (bytes === 0) return '0 Bytes';
+    const k = 1024;
+    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+  }
+  
+  startUpload() {
+    this.uploadStatus = 'uploading';
+    this.uploadProgress = 0;
+    
+    const interval = setInterval(() => {
+      this.uploadProgress += 5;
+      if (this.uploadProgress >= 100) {
+        clearInterval(interval);
+        setTimeout(() => {
+          this.uploadStatus = 'success';
+        }, 500);
+      }
+    }, 200);
+  }
+  
+  resetUpload() {
+    this.uploadStatus = 'idle';
+    this.uploadProgress = 0;
+    this.fileName = '';
+    this.fileSize = '';
+    this.isDragging = false;
+  }
+  
+  toggleCompanySelection(company: any) {
+    company.selected = !company.selected;
+  }
+  
+  submitResume() {
+    const selectedCompanies = this.targetCompanies.filter(c => c.selected);
+    if (selectedCompanies.length === 0) {
+      alert('请至少选择一家目标公司');
+      return;
+    }
+    
+    // 这里应该是实际的提交逻辑
+    console.log('提交简历到:', selectedCompanies.map(c => c.name).join(', '));
+    this.toggleUploadPanel();
+  }
+}

+ 0 - 0
ai-interview/src/moudles/interviewee/interviewee-home/interviewee-home.scss → ai-interview/src/app/interviewer/interviewer.html


+ 25 - 0
ai-interview/src/app/interviewer/interviewer.routes.ts

@@ -0,0 +1,25 @@
+// interviewer.routes.ts
+import { Routes } from '@angular/router';
+import { Interviewer } from './interviewer';
+
+export const interviewerRoutes: Routes = [
+  {
+    path: '',
+    component: Interviewer,
+    children: [
+      { 
+        path: '', 
+         loadComponent: () => import('./steps/prepare-step/prepare-step').then(m => m.PrepareStep),
+      },
+      { 
+        path: 'interview',
+        loadComponent: () => import('./steps/interview-step/interview-step').then(m => m.InterviewStep),                  
+       
+      },
+      { path: 'result',
+         loadComponent: () => import('./steps/result-step/result-step').then(m => m.ResultStep),
+      }
+    ]
+  },
+  
+];

+ 184 - 0
ai-interview/src/app/interviewer/interviewer.scss

@@ -0,0 +1,184 @@
+// interviewer.component.scss
+@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&display=swap');
+
+$primary-color: #2563EB;
+$secondary-color: #7C3AED;
+$accent-color: #00F0FF;
+$dark-bg: #0F172A;
+$light-bg: #1E293B;
+$text-color: #E2E8F0;
+
+.sci-fi-container {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  background-color: $dark-bg;
+  color: $text-color;
+  font-family: 'Orbitron', '阿里巴巴普惠体', sans-serif;
+  position: relative;
+  overflow-x: hidden;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 2px;
+    background: linear-gradient(90deg, transparent, $accent-color, transparent);
+    z-index: 10;
+    animation: scanline 3s linear infinite;
+  }
+}
+
+@keyframes scanline {
+  0% { transform: translateY(0); }
+  100% { transform: translateY(100vh); }
+}
+
+.sci-fi-header {
+  padding: 1.5rem;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 2rem;
+  background: linear-gradient(to bottom, rgba(15, 23, 42, 0.9), rgba(15, 23, 42, 0.7));
+  position: relative;
+  z-index: 2;
+  
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: 5%;
+    right: 5%;
+    height: 1px;
+    background: linear-gradient(90deg, transparent, $secondary-color, transparent);
+  }
+}
+
+.logo {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+  font-size: 1.5rem;
+  font-weight: 700;
+  letter-spacing: 0.1em;
+  color: $accent-color;
+  text-shadow: 0 0 10px rgba(0, 240, 255, 0.5);
+  
+  .logo-icon {
+    width: 2rem;
+    height: 2rem;
+    filter: drop-shadow(0 0 5px $accent-color);
+  }
+}
+
+.progress-tracker {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  max-width: 800px;
+  position: relative;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 15px;
+    left: 0;
+    right: 0;
+    height: 2px;
+    background: $light-bg;
+    z-index: 1;
+  }
+}
+
+.step {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  z-index: 2;
+  
+  &-circle {
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: $light-bg;
+    color: $text-color;
+    font-weight: 700;
+    margin-bottom: 0.5rem;
+    border: 2px solid $light-bg;
+    transition: all 0.3s ease;
+  }
+  
+  &-label {
+    font-size: 0.75rem;
+    text-transform: uppercase;
+    letter-spacing: 0.1em;
+    opacity: 0.7;
+    transition: all 0.3s ease;
+  }
+  
+  &.active {
+    .step-circle {
+      background: $primary-color;
+      border-color: $accent-color;
+      box-shadow: 0 0 10px $primary-color;
+      color: white;
+    }
+    
+    .step-label {
+      opacity: 1;
+      color: $accent-color;
+    }
+  }
+  
+  &.completed {
+    .step-circle {
+      background: $secondary-color;
+      color: white;
+    }
+    
+    .step-label {
+      opacity: 0.9;
+    }
+  }
+}
+
+.sci-fi-content {
+  flex: 1;
+  padding: 2rem;
+  position: relative;
+  z-index: 1;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: 
+      radial-gradient(circle at 20% 30%, rgba($primary-color, 0.1) 0%, transparent 50%),
+      radial-gradient(circle at 80% 70%, rgba($secondary-color, 0.1) 0%, transparent 50%);
+    pointer-events: none;
+    z-index: -1;
+  }
+}
+
+.sci-fi-footer {
+  padding: 1.5rem;
+  text-align: center;
+  font-size: 0.75rem;
+  opacity: 0.7;
+  position: relative;
+  
+  .neon-line {
+    height: 1px;
+    background: linear-gradient(90deg, transparent, $accent-color, transparent);
+    margin-bottom: 1rem;
+  }
+}

+ 23 - 0
ai-interview/src/app/interviewer/interviewer.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Interviewer } from './interviewer';
+
+describe('Interviewer', () => {
+  let component: Interviewer;
+  let fixture: ComponentFixture<Interviewer>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [Interviewer]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(Interviewer);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 54 - 0
ai-interview/src/app/interviewer/interviewer.ts

@@ -0,0 +1,54 @@
+// interviewer.component.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { Injectable } from '@angular/core';
+
+@Injectable({
+  providedIn: 'root'
+})
+@Component({
+  selector: 'app-interviewer',
+  standalone: true,
+  imports: [CommonModule, RouterModule],
+  template: `
+    <div class="sci-fi-container">
+      <header class="sci-fi-header">
+        <div class="logo">
+          <svg class="logo-icon" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M12,2A2,2 0 0,1 14,4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0 0,1 21,14H22A1,1 0 0,1 23,15V18A1,1 0 0,1 22,19H21V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V19H2A1,1 0 0,1 1,18V15A1,1 0 0,1 2,14H3A7,7 0 0,1 10,7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0 0,1 12,2M7.5,13A2.5,2.5 0 0,0 5,15.5A2.5,2.5 0 0,0 7.5,18A2.5,2.5 0 0,0 10,15.5A2.5,2.5 0 0,0 7.5,13M16.5,13A2.5,2.5 0 0,0 14,15.5A2.5,2.5 0 0,0 16.5,18A2.5,2.5 0 0,0 19,15.5A2.5,2.5 0 0,0 16.5,13Z" />
+          </svg>
+          <span>AI RECRUIT</span>
+        </div>
+        <div class="progress-tracker">
+          <div *ngFor="let step of steps" 
+               [class.active]="currentStep === step.id"
+               [class.completed]="step.id < currentStep"
+               class="step">
+            <div class="step-circle">{{ step.id }}</div>
+            <div class="step-label">{{ step.label }}</div>
+          </div>
+        </div>
+      </header>
+      
+      <main class="sci-fi-content">
+        <router-outlet></router-outlet>
+      </main>
+      
+      <footer class="sci-fi-footer">
+        <div class="neon-line"></div>
+        <p>© 2023 AI Recruit System | Neural Network Powered</p>
+      </footer>
+    </div>
+  `,
+  styleUrls: ['./interviewer.scss']
+})
+export class Interviewer {
+  currentStep = 1;
+  
+  steps = [
+    { id: 1, label: '面试准备' },
+    { id: 2, label: '面试进行' },
+    { id: 3, label: '结果反馈' }
+  ];
+}

+ 88 - 0
ai-interview/src/app/interviewer/steps/interview-step/interview-step.html

@@ -0,0 +1,88 @@
+<div class="interview-container" [class.recording]="isRecording" [class.paused]="isPaused">
+  <div class="header">
+    <button class="back-btn" (click)="back.emit()" *ngIf="!isRecording">
+      <svg viewBox="0 0 24 24">
+        <path fill="currentColor" d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
+      </svg>
+    </button>
+    <h2 class="title">AI面试</h2>
+    <div class="spacer"></div>
+    <div class="timer" *ngIf="isRecording">
+      {{ formatTime(timeLeft) }}
+    </div>
+  </div>
+
+  <div class="main-content">
+    <div class="ai-avatar" [class.speaking]="aiAvatarState === 'speaking'" 
+                          [class.listening]="aiAvatarState === 'listening'"
+                          [class.paused]="aiAvatarState === 'paused'">
+      <div class="avatar-face">
+        <div class="eyes">
+          <div class="eye left"></div>
+          <div class="eye right"></div>
+        </div>
+        <div class="mouth"></div>
+      </div>
+      <div class="avatar-body">
+        <div class="glow"></div>
+      </div>
+    </div>
+
+    <div class="question-container">
+      <div class="question-text" [innerHTML]="currentQuestion"></div>
+      <div class="typing-indicator" *ngIf="aiAvatarState === 'speaking'">
+        <span></span>
+        <span></span>
+        <span></span>
+      </div>
+    </div>
+
+    <div class="recording-visualizer" *ngIf="isRecording && !isPaused">
+      <div *ngFor="let bar of [1,2,3,4,5,6,7,8,9,10]" 
+           class="bar" 
+           [style.height.%]="getRandomHeight()"
+           [style.background]="getBarColor()">
+      </div>
+    </div>
+
+    <div class="emotion-feedback" *ngIf="isRecording && emotion !== 'neutral'">
+      <div class="emotion-bubble" [class.positive]="emotion === 'positive'"
+                                 [class.negative]="emotion === 'negative'">
+        <span *ngIf="emotion === 'positive'">👍 回答得很好!</span>
+        <span *ngIf="emotion === 'negative'">🤔 可以更详细一些</span>
+      </div>
+    </div>
+  </div>
+
+  <div class="controls">
+    <button *ngIf="!isRecording" 
+            (click)="startInterview()"
+            class="start-btn">
+      开始面试
+    </button>
+
+    <div class="recording-controls" *ngIf="isRecording">
+      <button (click)="pauseInterview()" class="control-btn pause">
+        <svg viewBox="0 0 24 24">
+          <path *ngIf="!isPaused" fill="currentColor" d="M14,19H18V5H14M6,19H10V5H6V19Z" />
+          <path *ngIf="isPaused" fill="currentColor" d="M10,16.5L16,12L10,7.5V16.5M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
+        </svg>
+      </button>
+      <button (click)="nextQuestion()" class="control-btn next">
+        <svg viewBox="0 0 24 24">
+          <path fill="currentColor" d="M18,18V6H16V18H18M11,18L17,12L11,6V18Z" />
+        </svg>
+      </button>
+      <button (click)="completeInterview()" class="control-btn stop">
+        <svg viewBox="0 0 24 24">
+          <path fill="currentColor" d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M9,9V15H15V9H9Z" />
+        </svg>
+      </button>
+    </div>
+  </div>
+
+  <div class="tech-overlay">
+    <div class="circuit-lines"></div>
+    <div class="binary-code"></div>
+  </div>
+</div>

+ 354 - 0
ai-interview/src/app/interviewer/steps/interview-step/interview-step.scss

@@ -0,0 +1,354 @@
+// interview-step.component.scss
+.interview-container {
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 120px);
+  position: relative;
+}
+
+.interview-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 1rem;
+  background: rgba(30, 41, 59, 0.7);
+  border-bottom: 1px solid rgba(124, 58, 237, 0.3);
+  
+  .timer {
+    font-size: 1.25rem;
+    font-weight: 700;
+    color: #00F0FF;
+    background: rgba(15, 23, 42, 0.8);
+    padding: 0.5rem 1rem;
+    border-radius: 4px;
+    min-width: 60px;
+    text-align: center;
+    
+    &.warning {
+      color: #F59E0B;
+      animation: pulse 1s infinite;
+    }
+  }
+  
+  .question-counter {
+    color: #94A3B8;
+    font-size: 0.875rem;
+  }
+}
+
+@keyframes pulse {
+  0% { opacity: 1; }
+  50% { opacity: 0.5; }
+  100% { opacity: 1; }
+}
+
+.interview-main {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  padding: 1rem;
+  gap: 1rem;
+}
+
+.ai-interviewer {
+  display: flex;
+  align-items: flex-start;
+  gap: 1rem;
+  margin-bottom: 1rem;
+}
+
+.avatar-container {
+  position: relative;
+  
+  .avatar {
+    width: 60px;
+    height: 60px;
+    border-radius: 50%;
+    background-size: cover;
+    background-position: center;
+    border: 2px solid #7C3AED;
+    box-shadow: 0 0 15px rgba(124, 58, 237, 0.5);
+  }
+  
+  .status-indicator {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    background: #10B981;
+    border: 2px solid #1E293B;
+  }
+}
+
+.speech-bubble {
+  flex: 1;
+  background: rgba(30, 41, 59, 0.8);
+  border-radius: 0 12px 12px 12px;
+  padding: 1rem;
+  position: relative;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: -10px;
+    width: 0;
+    height: 0;
+    border: 10px solid transparent;
+    border-right-color: rgba(30, 41, 59, 0.8);
+    border-left: 0;
+  }
+  
+  p {
+    margin: 0;
+    color: #E2E8F0;
+    line-height: 1.5;
+  }
+}
+
+.typing-indicator {
+  display: flex;
+  gap: 4px;
+  margin-top: 0.5rem;
+  
+  span {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    background: #94A3B8;
+    border-radius: 50%;
+    animation: typing 1.4s infinite ease-in-out;
+    
+    &:nth-child(2) {
+      animation-delay: 0.2s;
+    }
+    
+    &:nth-child(3) {
+      animation-delay: 0.4s;
+    }
+  }
+}
+
+@keyframes typing {
+  0%, 60%, 100% { transform: translateY(0); }
+  30% { transform: translateY(-5px); }
+}
+
+.user-section {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+}
+
+.video-preview {
+  position: relative;
+  height: 180px;
+  background: rgba(15, 23, 42, 0.8);
+  border: 1px solid rgba(124, 58, 237, 0.5);
+  border-radius: 8px;
+  overflow: hidden;
+  
+  .video-placeholder {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    
+    &::before {
+      content: '';
+      position: absolute;
+      inset: 0;
+      background: 
+        linear-gradient(45deg, transparent 45%, rgba(124, 58, 237, 0.1) 45.5%, transparent 46%),
+        linear-gradient(-45deg, transparent 45%, rgba(124, 58, 237, 0.1) 45.5%, transparent 46%);
+      background-size: 20px 20px;
+    }
+    
+    svg {
+      width: 48px;
+      height: 48px;
+      color: #7C3AED;
+      opacity: 0.5;
+      z-index: 1;
+    }
+  }
+}
+
+.audio-visualizer {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 30px;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+  gap: 2px;
+  padding: 0 10px;
+  
+  .bar {
+    width: 4px;
+    min-height: 2px;
+    background: linear-gradient(to top, #00F0FF, #7C3AED);
+    border-radius: 2px;
+    transition: height 0.2s ease;
+  }
+}
+
+.controls {
+  display: flex;
+  gap: 1rem;
+  justify-content: center;
+}
+
+.control-button {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  padding: 0.75rem 1.5rem;
+  background: rgba(30, 41, 59, 0.8);
+  border: 1px solid rgba(124, 58, 237, 0.5);
+  border-radius: 4px;
+  color: #E2E8F0;
+  font-family: inherit;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  
+  svg {
+    width: 18px;
+    height: 18px;
+  }
+  
+  &:hover {
+    background: rgba(124, 58, 237, 0.2);
+  }
+  
+  &.emergency {
+    background: rgba(239, 68, 68, 0.1);
+    border-color: rgba(239, 68, 68, 0.5);
+    color: #EF4444;
+    
+    &:hover {
+      background: rgba(239, 68, 68, 0.2);
+    }
+  }
+}
+
+.transcript-container {
+  flex: 1;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  background: rgba(15, 23, 42, 0.7);
+  border-top: 1px solid rgba(124, 58, 237, 0.3);
+  
+  h3 {
+    padding: 1rem;
+    margin: 0;
+    color: #E2E8F0;
+    font-size: 1rem;
+    border-bottom: 1px solid rgba(124, 58, 237, 0.2);
+  }
+}
+
+.transcript {
+  flex: 1;
+  overflow-y: auto;
+  padding: 0.5rem;
+}
+
+.transcript-item {
+  margin-bottom: 1rem;
+  padding: 0.75rem;
+  background: rgba(15, 23, 42, 0.5);
+  border-radius: 6px;
+  border-left: 3px solid #7C3AED;
+  
+  &.ai {
+    border-left-color: #2563EB;
+  }
+  
+  .speaker {
+    font-size: 0.75rem;
+    color: #94A3B8;
+    margin-bottom: 0.25rem;
+  }
+  
+  .content {
+    color: #E2E8F0;
+    margin-bottom: 0.25rem;
+  }
+  
+  .time {
+    font-size: 0.75rem;
+    color: #64748B;
+    text-align: right;
+  }
+}
+
+.emergency-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(15, 23, 42, 0.9);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 100;
+  
+  .modal-content {
+    background: #1E293B;
+    border-radius: 8px;
+    padding: 2rem;
+    max-width: 400px;
+    width: 90%;
+    border: 1px solid rgba(239, 68, 68, 0.5);
+    box-shadow: 0 0 20px rgba(239, 68, 68, 0.2);
+    
+    h3 {
+      color: #EF4444;
+      margin-top: 0;
+    }
+    
+    p {
+      color: #E2E8F0;
+    }
+    
+    .contact-info {
+      background: rgba(15, 23, 42, 0.5);
+      padding: 1rem;
+      border-radius: 4px;
+      margin: 1rem 0;
+      
+      p {
+        margin: 0.5rem 0;
+      }
+    }
+  }
+  
+  .modal-actions {
+    display: flex;
+    justify-content: flex-end;
+    gap: 1rem;
+    margin-top: 1.5rem;
+  }
+  
+  .sci-fi-button {
+    &.danger {
+      background: linear-gradient(135deg, #EF4444, #DC2626);
+      color: white;
+      
+      &:hover {
+        box-shadow: 0 0 15px rgba(239, 68, 68, 0.5);
+      }
+    }
+  }
+}

+ 23 - 0
ai-interview/src/app/interviewer/steps/interview-step/interview-step.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { InterviewStep } from './interview-step';
+
+describe('InterviewStep', () => {
+  let component: InterviewStep;
+  let fixture: ComponentFixture<InterviewStep>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [InterviewStep]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(InterviewStep);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 225 - 0
ai-interview/src/app/interviewer/steps/interview-step/interview-step.ts

@@ -0,0 +1,225 @@
+// steps/interview-step.component.ts
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Router } from '@angular/router';
+import { Interviewer } from '../../interviewer';
+
+interface InterviewQuestion {
+  id: number;
+  text: string;
+  type: 'technical' | 'behavioral' | 'situational';
+  timeLimit: number;
+}
+
+@Component({
+  selector: 'app-interview-step',
+  standalone: true,
+  imports: [CommonModule,Interviewer],
+  template: `
+    <div class="interview-container">
+      <div class="interview-header">
+        <div class="timer" [class.warning]="remainingTime <= 10">
+          {{ remainingTime }}s
+        </div>
+        <div class="question-counter">
+          问题 {{ currentQuestion + 1 }}/{{ questions.length }}
+        </div>
+      </div>
+      
+      <div class="interview-main">
+        <div class="ai-interviewer">
+          <div class="avatar-container">
+            <div class="avatar" [style.background-image]="'url(assets/ai-avatar.png)'"></div>
+            <div class="status-indicator"></div>
+          </div>
+          <div class="speech-bubble">
+            <p>{{ displayedQuestion }}</p>
+            <div class="typing-indicator" *ngIf="isTyping">
+              <span></span>
+              <span></span>
+              <span></span>
+            </div>
+          </div>
+        </div>
+        
+        <div class="user-section">
+          <div class="video-preview">
+            <div class="video-placeholder"></div>
+            <div class="audio-visualizer">
+              <div *ngFor="let bar of audioBars" class="bar" [style.height.%]="bar"></div>
+            </div>
+          </div>
+          
+          <div class="controls">
+            <button class="control-button" (click)="togglePause()">
+              <svg viewBox="0 0 24 24">
+                <path fill="currentColor" [attr.d]="isPaused ? 'M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z' : 'M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z'" />
+              </svg>
+              {{ isPaused ? '继续' : '暂停' }}
+            </button>
+            <button class="control-button emergency" (click)="showEmergencyModal = true">
+              <svg viewBox="0 0 24 24">
+                <path fill="currentColor" d="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16" />
+              </svg>
+              紧急求助
+            </button>
+          </div>
+        </div>
+      </div>
+      
+      <div class="transcript-container">
+        <h3>对话记录</h3>
+        <div class="transcript">
+          <div *ngFor="let item of transcript" class="transcript-item" [class.ai]="item.speaker === 'ai'">
+            <div class="speaker">{{ item.speaker === 'ai' ? 'AI面试官' : '您' }}</div>
+            <div class="content">{{ item.text }}</div>
+            <div class="time">{{ item.time }}</div>
+          </div>
+        </div>
+      </div>
+      
+      <div class="emergency-modal" *ngIf="showEmergencyModal">
+        <div class="modal-content">
+          <h3>紧急求助</h3>
+          <p>如果需要暂停或终止面试,请联系HR:</p>
+          <div class="contact-info">
+  <p><strong>电话:</strong> {{ contactInfo.phone }}</p>
+  <p><strong>邮箱:</strong> {{ contactInfo.email }}</p>
+</div>
+          <div class="modal-actions">
+            <button class="sci-fi-button secondary" (click)="showEmergencyModal = false">
+              取消
+            </button>
+            <button class="sci-fi-button danger" (click)="endInterview()">
+              终止面试
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  `,
+  styleUrls: ['./interview-step.scss']
+})
+export class InterviewStep implements OnInit, OnDestroy {
+  questions: InterviewQuestion[] = [
+    { id: 1, text: '请简单介绍一下您自己', type: 'behavioral', timeLimit: 60 },
+    { id: 2, text: '您在前端开发中遇到的最大挑战是什么?如何解决的?', type: 'technical', timeLimit: 90 },
+    { id: 3, text: '如果产品经理要求在一个不可能的时间期限内完成功能,您会如何处理?', type: 'situational', timeLimit: 75 },
+    { id: 4, text: '请解释一下Angular的变更检测机制', type: 'technical', timeLimit: 90 },
+    { id: 5, text: '您如何看待团队合作中的冲突?请举例说明', type: 'behavioral', timeLimit: 60 }
+  ];
+  
+  currentQuestion = 0;
+  displayedQuestion = '';
+  isTyping = false;
+  remainingTime = 0;
+  timer: any;
+  isPaused = false;
+  showEmergencyModal = false;
+  contactInfo = {
+  phone: '400-123-4567',
+  email: 'hr@airecruit.com'
+};
+  transcript: { speaker: 'ai' | 'user'; text: string; time: string }[] = [];
+  audioBars: number[] = Array(10).fill(0);
+  audioInterval: any;
+  
+  constructor(private router: Router,public interviewer: Interviewer) {}
+  
+  ngOnInit() {
+    this.startInterview();
+    this.simulateAudio();
+  }
+  
+  ngOnDestroy() {
+    clearInterval(this.timer);
+    clearInterval(this.audioInterval);
+  }
+  
+  startInterview() {
+    this.typeQuestion(this.questions[this.currentQuestion].text);
+    this.startTimer(this.questions[this.currentQuestion].timeLimit);
+  }
+  
+  typeQuestion(question: string) {
+    this.isTyping = true;
+    this.displayedQuestion = '';
+    let i = 0;
+    
+    const typing = setInterval(() => {
+      if (i < question.length) {
+        this.displayedQuestion += question.charAt(i);
+        i++;
+      } else {
+        clearInterval(typing);
+        this.isTyping = false;
+        this.addToTranscript('ai', question);
+      }
+    }, 30);
+  }
+  
+  startTimer(seconds: number) {
+    this.remainingTime = seconds;
+    
+    this.timer = setInterval(() => {
+      if (!this.isPaused) {
+        this.remainingTime--;
+        
+        if (this.remainingTime <= 0) {
+          clearInterval(this.timer);
+          this.nextQuestion();
+        }
+      }
+    }, 1000);
+  }
+  
+  nextQuestion() {
+    if (this.currentQuestion < this.questions.length - 1) {
+      this.currentQuestion++;
+      this.typeQuestion(this.questions[this.currentQuestion].text);
+      this.startTimer(this.questions[this.currentQuestion].timeLimit);
+    } else {
+      this.endInterview();
+    }
+  }
+  
+  togglePause() {
+    this.isPaused = !this.isPaused;
+  }
+  
+  addToTranscript(speaker: 'ai' | 'user', text: string) {
+    const now = new Date();
+    const timeString = now.getHours().toString().padStart(2, '0') + ':' + 
+                      now.getMinutes().toString().padStart(2, '0');
+    
+    this.transcript.push({
+      speaker,
+      text,
+      time: timeString
+    });
+    
+    // 自动滚动到底部
+    setTimeout(() => {
+      const transcriptEl = document.querySelector('.transcript');
+      if (transcriptEl) {
+        transcriptEl.scrollTop = transcriptEl.scrollHeight;
+      }
+    }, 100);
+  }
+  
+  simulateAudio() {
+    this.audioInterval = setInterval(() => {
+      this.audioBars = this.audioBars.map(() => Math.min(100, Math.random() * 100));
+    }, 200);
+  }
+  
+  endInterview() {
+    clearInterval(this.timer);
+    clearInterval(this.audioInterval);
+    
+    this.router.navigate(['/auth/home/interviewer/result']);
+    this.interviewer.currentStep=3;
+    
+  }
+  
+}

+ 64 - 0
ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.html

@@ -0,0 +1,64 @@
+<div class="prepare-container">
+  <div class="holographic-panel">
+    <div class="panel-header">
+      <button class="back-btn" (click)="back.emit()">
+        <svg viewBox="0 0 24 24">
+          <path fill="currentColor" d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
+        </svg>
+      </button>
+      <h2 class="neon-title">面试准备</h2>
+      <div class="spacer"></div>
+    </div>
+
+    <div class="steps-container">
+      <div *ngFor="let step of steps; let i = index" 
+           class="step-card"
+           [class.completed]="step.completed"
+           [class.active]="step.inProgress">
+        <div class="step-icon">
+          <svg *ngIf="step.icon === 'mic'" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z" />
+          </svg>
+          <svg *ngIf="step.icon === 'wifi'" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M12,21L15.6,16.2C14.6,15.45 13.35,15 12,15C10.65,15 9.4,15.45 8.4,16.2L12,21M12,3C7.95,3 4.21,4.34 1.2,6.6L3,9C5.5,7.12 8.62,6 12,6C15.38,6 18.5,7.12 21,9L22.8,6.6C19.79,4.34 16.05,3 12,3M12,9C9.3,9 6.81,9.89 4.8,11.4L6.6,13.8C8.1,12.67 9.97,12 12,12C14.03,12 15.9,12.67 17.4,13.8L19.2,11.4C17.19,9.89 14.7,9 12,9Z" />
+          </svg>
+          <svg *ngIf="step.icon === 'light'" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M12,6A6,6 0 0,1 18,12C18,14.22 16.79,16.16 15,17.2V19A1,1 0 0,1 14,20H10A1,1 0 0,1 9,19V17.2C7.21,16.16 6,14.22 6,12A6,6 0 0,1 12,6M14,21V22A1,1 0 0,1 13,23H11A1,1 0 0,1 10,22V21H14M20,11H23V13H20V11M1,11H4V13H1V11M13,1V4H11V1H13M4.92,3.5L7.05,5.64L5.63,7.05L3.5,4.93L4.92,3.5M16.95,5.63L19.07,3.5L20.5,4.93L18.37,7.05L16.95,5.63Z" />
+          </svg>
+          <svg *ngIf="step.icon === 'document'" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M15,18V16H6V18H15M18,14V12H6V14H18Z" />
+          </svg>
+        </div>
+        <div class="step-content">
+          <h3>{{ step.title }}</h3>
+          <p>{{ step.description }}</p>
+        </div>
+        <div class="step-status">
+          <div class="status-indicator" *ngIf="!step.completed && !step.inProgress"></div>
+          <div class="status-indicator loading" *ngIf="step.inProgress"></div>
+          <svg *ngIf="step.completed" viewBox="0 0 24 24" class="checkmark">
+            <path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
+          </svg>
+        </div>
+      </div>
+    </div>
+
+    <div class="action-buttons">
+      <button *ngIf="currentStepIndex < steps.length - 1" 
+              (click)="nextStep()"
+              class="next-btn">
+        下一步
+      </button>
+      <button *ngIf="currentStepIndex === steps.length - 1" 
+              (click)="completeAll()"
+              class="complete-btn">
+        完成准备
+      </button>
+    </div>
+  </div>
+
+  <div class="tech-overlay">
+    <div class="grid-lines"></div>
+    <div class="circuit-pattern"></div>
+  </div>
+</div>

+ 160 - 0
ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.scss

@@ -0,0 +1,160 @@
+// prepare-step.component.scss
+.checklist {
+  margin: 2rem 0;
+}
+
+.check-item {
+  display: flex;
+  gap: 1rem;
+  padding: 1rem;
+  margin-bottom: 1rem;
+  background: rgba(15, 23, 42, 0.5);
+  border-radius: 6px;
+  border: 1px solid rgba(124, 58, 237, 0.2);
+  transition: all 0.3s ease;
+  
+  &:hover {
+    border-color: rgba(124, 58, 237, 0.5);
+  }
+  
+  &.checked {
+    background: rgba(37, 99, 235, 0.1);
+    border-color: rgba(37, 99, 235, 0.5);
+    
+    .check-icon {
+      background: rgba(37, 99, 235, 0.2);
+      color: #00F0FF;
+    }
+  }
+}
+
+.check-icon {
+  width: 48px;
+  height: 48px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(15, 23, 42, 0.7);
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  
+  svg {
+    width: 24px;
+    height: 24px;
+    color: #7C3AED;
+  }
+  
+  &:hover {
+    background: rgba(124, 58, 237, 0.2);
+  }
+}
+
+.check-content {
+  flex: 1;
+  
+  h3 {
+    color: #E2E8F0;
+    margin-bottom: 0.25rem;
+    font-size: 1rem;
+  }
+  
+  p {
+    color: #94A3B8;
+    font-size: 0.875rem;
+    margin-bottom: 0.5rem;
+  }
+}
+
+.audio-meter {
+  height: 6px;
+  background: rgba(15, 23, 42, 0.8);
+  border-radius: 3px;
+  overflow: hidden;
+  margin-top: 0.5rem;
+  
+  .level {
+    height: 100%;
+    background: linear-gradient(90deg, #10B981, #00F0FF);
+    transition: width 0.2s ease;
+  }
+}
+
+.camera-preview {
+  width: 100%;
+  height: 120px;
+  background: rgba(15, 23, 42, 0.8);
+  border-radius: 4px;
+  margin-top: 0.5rem;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  
+  .placeholder {
+    width: 48px;
+    height: 48px;
+    color: #7C3AED;
+    opacity: 0.5;
+  }
+}
+
+.network-stats {
+  display: flex;
+  gap: 1rem;
+  margin-top: 0.5rem;
+  
+  .stat {
+    flex: 1;
+    background: rgba(15, 23, 42, 0.8);
+    padding: 0.5rem;
+    border-radius: 4px;
+    text-align: center;
+    
+    .label {
+      display: block;
+      font-size: 0.75rem;
+      color: #94A3B8;
+      margin-bottom: 0.25rem;
+    }
+    
+    .value {
+      display: block;
+      font-size: 1rem;
+      color: #00F0FF;
+      font-weight: 500;
+    }
+  }
+}
+
+.interview-tips {
+  margin: 2rem 0;
+  padding: 1rem;
+  background: rgba(15, 23, 42, 0.5);
+  border-radius: 6px;
+  border-left: 3px solid #7C3AED;
+  
+  h3 {
+    color: #E2E8F0;
+    margin-bottom: 1rem;
+    font-size: 1.25rem;
+  }
+  
+  ul {
+    list-style-type: none;
+    padding-left: 0;
+    
+    li {
+      position: relative;
+      padding-left: 1.5rem;
+      margin-bottom: 0.5rem;
+      color: #94A3B8;
+      
+      &::before {
+        content: '•';
+        position: absolute;
+        left: 0;
+        color: #7C3AED;
+      }
+    }
+  }
+}

+ 23 - 0
ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PrepareStep } from './prepare-step';
+
+describe('PrepareStep', () => {
+  let component: PrepareStep;
+  let fixture: ComponentFixture<PrepareStep>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [PrepareStep]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(PrepareStep);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 163 - 0
ai-interview/src/app/interviewer/steps/prepare-step/prepare-step.ts

@@ -0,0 +1,163 @@
+// steps/prepare-step.component.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Router } from '@angular/router';
+import { Interviewer } from '../../interviewer';
+
+
+@Component({
+  selector: 'app-prepare-step',
+  standalone: true,
+  imports: [CommonModule,Interviewer],
+  template: `
+    <div class="sci-fi-card">
+      <h2 class="sci-fi-title">面试准备</h2>
+      <p class="sci-fi-subtitle">请确保您的设备满足以下要求</p>
+      
+      <div class="checklist">
+        <div class="check-item" [class.checked]="micChecked">
+          <div class="check-icon" (click)="toggleMicCheck()">
+            <svg viewBox="0 0 24 24">
+              <path [attr.d]="micChecked ? 'M9,3A4,4 0 0,1 13,7H5A4,4 0 0,1 9,3M11.84,9.82L11,18H10V19A2,2 0 0,0 12,21A2,2 0 0,0 14,19V14A4,4 0 0,1 18,10H20L19,11L20,10H18A2,2 0 0,0 16,12V19A4,4 0 0,1 12,23A4,4 0 0,1 8,19V18H7L6.16,9.82C5.67,9.32 5.31,8.7 5.13,8H12.87C12.69,8.7 12.33,9.32 11.84,9.82M9,11A1,1 0 0,0 8,12A1,1 0 0,0 9,13A1,1 0 0,0 10,12A1,1 0 0,0 9,11Z' : 'M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z'" 
+                    fill="currentColor" />
+            </svg>
+          </div>
+          <div class="check-content">
+            <h3>麦克风检测</h3>
+            <p>请允许使用麦克风并测试录音</p>
+            <div class="audio-meter" *ngIf="micChecked">
+              <div class="level" [style.width.%]="audioLevel"></div>
+            </div>
+          </div>
+        </div>
+        
+        <div class="check-item" [class.checked]="cameraChecked">
+          <div class="check-icon" (click)="toggleCameraCheck()">
+            <svg viewBox="0 0 24 24">
+              <path [attr.d]="cameraChecked ? 'M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z' : 'M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,10.5A1.5,1.5 0 0,1 13.5,12A1.5,1.5 0 0,1 12,13.5A1.5,1.5 0 0,1 10.5,12A1.5,1.5 0 0,1 12,10.5M7,6A1,1 0 0,1 8,7A1,1 0 0,1 7,8A1,1 0 0,1 6,7A1,1 0 0,1 7,6M17,6A1,1 0 0,1 18,7A1,1 0 0,1 17,8A1,1 0 0,1 16,7A1,1 0 0,1 17,6Z'" 
+                    fill="currentColor" />
+            </svg>
+          </div>
+          <div class="check-content">
+            <h3>摄像头检测</h3>
+            <p>请允许使用摄像头并调整角度</p>
+            <div class="camera-preview" *ngIf="cameraChecked">
+              <div class="placeholder">
+                <svg viewBox="0 0 24 24">
+                  <path fill="currentColor" d="M12,12A3,3 0 0,0 9,15A3,3 0 0,0 12,18A3,3 0 0,0 15,15A3,3 0 0,0 12,12M12,20A5,5 0 0,1 7,15A5,5 0 0,1 12,10A5,5 0 0,1 17,15A5,5 0 0,1 12,20M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8C10.89,8 10,7.1 10,6C10,4.89 10.89,4 12,4M17,2H7C5.89,2 5,2.89 5,4V20A2,2 0 0,0 7,22H17A2,2 0 0,0 19,20V4C19,2.89 18.1,2 17,2Z" />
+                </svg>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <div class="check-item" [class.checked]="networkChecked">
+          <div class="check-icon" (click)="toggleNetworkCheck()">
+            <svg viewBox="0 0 24 24">
+              <path [attr.d]="networkChecked ? 'M12,3C16.97,3 21,7.03 21,12C21,16.97 16.97,21 12,21C7.03,21 3,16.97 3,12C3,7.03 7.03,3 12,3M12,7C9.24,7 7,9.24 7,12C7,14.76 9.24,17 12,17C14.76,17 17,14.76 17,12C17,9.24 14.76,7 12,7Z' : 'M12,3C16.97,3 21,7.03 21,12C21,16.97 16.97,21 12,21C7.03,21 3,16.97 3,12C3,7.03 7.03,3 12,3M12,5C8.14,5 5,8.14 5,12C5,15.86 8.14,19 12,19C15.86,19 19,15.86 19,12C19,8.14 15.86,5 12,5Z'" 
+                    fill="currentColor" />
+            </svg>
+          </div>
+          <div class="check-content">
+            <h3>网络检测</h3>
+            <p>确保网络连接稳定</p>
+            <div class="network-stats" *ngIf="networkChecked">
+              <div class="stat">
+                <span class="label">延迟</span>
+                <span class="value">{{ ping }} ms</span>
+              </div>
+              <div class="stat">
+                <span class="label">下载</span>
+                <span class="value">{{ downloadSpeed }} Mbps</span>
+              </div>
+              <div class="stat">
+                <span class="label">上传</span>
+                <span class="value">{{ uploadSpeed }} Mbps</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      
+      <div class="interview-tips">
+        <h3>面试须知</h3>
+        <ul>
+          <li>请选择一个安静、光线充足的环境</li>
+          <li>确保面试期间不会被打扰</li>
+          <li>准备好您的简历和相关项目经验</li>
+          <li>AI面试官会评估您的技术能力和沟通技巧</li>
+          <li>每个问题有固定回答时间,请注意倒计时</li>
+        </ul>
+      </div>
+      
+      <button class="sci-fi-button primary" [disabled]="!allChecked" 
+              (click)="startInterview()">
+        开始AI面试
+      </button>
+    </div>
+  `,
+  styleUrls: ['./prepare-step.scss']
+})
+export class PrepareStep {
+  micChecked = false;
+  cameraChecked = false;
+  networkChecked = false;
+  
+  audioLevel = 0;
+  ping = 0;
+  downloadSpeed = 0;
+  uploadSpeed = 0;
+  
+  constructor(private router: Router,public interviewer: Interviewer) {}
+  
+  get allChecked(): boolean {
+    return this.micChecked && this.cameraChecked && this.networkChecked;
+  }
+  
+  toggleMicCheck() {
+    this.micChecked = !this.micChecked;
+    if (this.micChecked) {
+      this.simulateAudioTest();
+    }
+  }
+  
+  toggleCameraCheck() {
+    this.cameraChecked = !this.cameraChecked;
+  }
+  
+  toggleNetworkCheck() {
+    this.networkChecked = !this.networkChecked;
+    if (this.networkChecked) {
+      this.simulateNetworkTest();
+    }
+  }
+  
+  simulateAudioTest() {
+    let count = 0;
+    const interval = setInterval(() => {
+      this.audioLevel = Math.min(100, Math.max(0, Math.random() * 120 - 10));
+      count++;
+      
+      if (count >= 20) {
+        clearInterval(interval);
+        this.audioLevel = 80 + Math.random() * 20;
+      }
+    }, 100);
+  }
+  
+  simulateNetworkTest() {
+    setTimeout(() => {
+      this.ping = Math.floor(20 + Math.random() * 50);
+      this.downloadSpeed = parseFloat((50 + Math.random() * 50).toFixed(1));
+      this.uploadSpeed = parseFloat((10 + Math.random() * 20).toFixed(1));
+    }, 1000);
+  }
+  
+  startInterview() {
+    if (this.allChecked) {
+      this.router.navigate(['/auth/home/interviewer/interview']);
+      this.interviewer.currentStep=2;
+    
+    }
+  }
+}

+ 67 - 0
ai-interview/src/app/interviewer/steps/result-step/result-step.html

@@ -0,0 +1,67 @@
+<div class="result-container">
+  <div class="result-header">
+    <h2 class="neon-title">面试结果</h2>
+    <p class="subtitle">AI分析报告</p>
+  </div>
+
+  <div class="score-card">
+    <div class="radar-chart">
+      <div class="radar-grid">
+        <div class="grid-circle" *ngFor="let circle of [1,2,3,4]"></div>
+      </div>
+      <div class="radar-axes">
+        <div class="axis" *ngFor="let skill of skills; let i = index" 
+             [style.transform]="'rotate(' + (i * 72) + 'deg)'"></div>
+      </div>
+      <div class="radar-polygon" [style.clip-path]="getRadarPath()"></div>
+      <div class="skill-labels">
+        <div class="label" *ngFor="let skill of skills; let i = index" 
+             [style.transform]="'rotate(' + (i * 72) + 'deg) translate(0, -110px)'">
+          {{ skill.name }}
+        </div>
+      </div>
+    </div>
+    <div class="score-display">
+      <div class="overall-score">{{ overallScore }}</div>
+      <div class="score-label">综合得分</div>
+      <div class="match-badge" [class.high-match]="overallScore >= 80"
+                             [class.medium-match]="overallScore >= 60 && overallScore < 80"
+                             [class.low-match]="overallScore < 60">
+        {{ jobMatch }}
+      </div>
+    </div>
+  </div>
+
+  <div class="feedback-section">
+    <h3 class="section-title">AI反馈</h3>
+    <div class="feedback-text">{{ feedback }}</div>
+  </div>
+
+  <div class="suggestions-section">
+    <h3 class="section-title">改进建议</h3>
+    <div class="suggestions-list">
+      <div class="suggestion-item" *ngFor="let suggestion of suggestions">
+        <div class="suggestion-icon">
+          <svg viewBox="0 0 24 24">
+            <path fill="currentColor" d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z" />
+          </svg>
+        </div>
+        <div class="suggestion-text">{{ suggestion }}</div>
+      </div>
+    </div>
+  </div>
+
+  <div class="action-buttons">
+    <button (click)="restartInterview()" class="restart-btn">
+      重新开始面试
+    </button>
+    <button class="download-btn">
+      下载完整报告
+    </button>
+  </div>
+
+  <div class="tech-overlay">
+    <div class="data-grid"></div>
+    <div class="particles"></div>
+  </div>
+</div>

+ 295 - 0
ai-interview/src/app/interviewer/steps/result-step/result-step.scss

@@ -0,0 +1,295 @@
+// result-step.component.scss
+.result-container {
+  padding: 1rem;
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+.result-header {
+  text-align: center;
+  margin-bottom: 2rem;
+  
+  h2 {
+    color: #00F0FF;
+    margin-bottom: 0.5rem;
+    font-size: 1.75rem;
+  }
+  
+  p {
+    color: #94A3B8;
+    margin: 0;
+  }
+}
+
+.result-summary {
+  display: flex;
+  gap: 2rem;
+  margin-bottom: 2rem;
+  background: rgba(30, 41, 59, 0.7);
+  border-radius: 8px;
+  padding: 1.5rem;
+  align-items: center;
+  
+  @media (max-width: 600px) {
+    flex-direction: column;
+  }
+}
+
+.overall-score {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  min-width: 180px;
+}
+
+.score-circle {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #2563EB, #7C3AED);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 1rem;
+  position: relative;
+  box-shadow: 0 0 20px rgba(37, 99, 235, 0.3);
+  
+  &::before {
+    content: '';
+    position: absolute;
+    inset: -5px;
+    border-radius: 50%;
+    border: 2px dashed #00F0FF;
+    opacity: 0.5;
+    animation: rotate 20s linear infinite;
+  }
+  
+  .score-value {
+    font-size: 2.5rem;
+    font-weight: 700;
+    color: white;
+    line-height: 1;
+  }
+  
+  .score-label {
+    font-size: 0.875rem;
+    color: rgba(255, 255, 255, 0.8);
+    text-transform: uppercase;
+    letter-spacing: 0.1em;
+  }
+}
+
+.match-score {
+  text-align: center;
+  
+  .match-value {
+    font-size: 1.5rem;
+    font-weight: 700;
+    color: #00F0FF;
+    line-height: 1;
+  }
+  
+  .match-label {
+    font-size: 0.875rem;
+    color: #94A3B8;
+  }
+}
+
+.summary-text {
+  flex: 1;
+  
+  p {
+    color: #E2E8F0;
+    line-height: 1.6;
+    margin: 0;
+  }
+}
+
+.skill-radar {
+  background: rgba(30, 41, 59, 0.7);
+  border-radius: 8px;
+  padding: 1.5rem;
+  margin-bottom: 2rem;
+  
+  h3 {
+    color: #E2E8F0;
+    margin-top: 0;
+    margin-bottom: 1.5rem;
+    text-align: center;
+  }
+}
+
+.radar-container {
+  display: flex;
+  justify-content: center;
+}
+
+.radar-chart {
+  width: 100%;
+  max-width: 400px;
+  height: auto;
+}
+
+.grid-circle {
+  fill: none;
+  stroke: rgba(124, 58, 237, 0.2);
+  stroke-width: 1;
+}
+
+.axis-line {
+  stroke: rgba(124, 58, 237, 0.3);
+  stroke-width: 1;
+}
+
+.skill-area {
+  fill: rgba(37, 99, 235, 0.3);
+  stroke: #2563EB;
+  stroke-width: 2;
+}
+
+.skill-label {
+  font-size: 0.75rem;
+  fill: #E2E8F0;
+  text-anchor: middle;
+}
+
+.detailed-feedback {
+  background: rgba(30, 41, 59, 0.7);
+  border-radius: 8px;
+  padding: 1.5rem;
+  margin-bottom: 2rem;
+  
+  h3 {
+    color: #E2E8F0;
+    margin-top: 0;
+    margin-bottom: 1.5rem;
+    text-align: center;
+  }
+}
+
+.skill-ratings {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 1.5rem;
+}
+
+.skill-rating {
+  background: rgba(15, 23, 42, 0.5);
+  border-radius: 6px;
+  padding: 1rem;
+  border-left: 3px solid #7C3AED;
+}
+
+.skill-info {
+  margin-bottom: 0.5rem;
+  
+  h4 {
+    color: #E2E8F0;
+    margin-top: 0;
+    margin-bottom: 0.5rem;
+    font-size: 1rem;
+  }
+}
+
+.rating-bar {
+  height: 10px;
+  background: rgba(15, 23, 42, 0.8);
+  border-radius: 5px;
+  position: relative;
+  margin-bottom: 0.25rem;
+}
+
+.rating-fill {
+  height: 100%;
+  border-radius: 5px;
+  background: linear-gradient(90deg, #7C3AED, #2563EB);
+  transition: width 0.5s ease;
+}
+
+.rating-value {
+  position: absolute;
+  right: 0;
+  top: 12px;
+  font-size: 0.75rem;
+  color: #94A3B8;
+}
+
+.skill-description {
+  color: #94A3B8;
+  font-size: 0.875rem;
+  margin: 0;
+  line-height: 1.5;
+}
+
+.improvement-suggestions {
+  background: rgba(30, 41, 59, 0.7);
+  border-radius: 8px;
+  padding: 1.5rem;
+  margin-bottom: 2rem;
+  
+  h3 {
+    color: #E2E8F0;
+    margin-top: 0;
+    margin-bottom: 1.5rem;
+    text-align: center;
+  }
+}
+
+.improvement-item {
+  margin-bottom: 1.5rem;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+  
+  h4 {
+    color: #E2E8F0;
+    margin-top: 0;
+    margin-bottom: 0.75rem;
+    font-size: 1rem;
+    position: relative;
+    padding-left: 1.5rem;
+    
+    &::before {
+      content: '⬥';
+      position: absolute;
+      left: 0;
+      color: #7C3AED;
+    }
+  }
+  
+  ul {
+    list-style-type: none;
+    padding-left: 1.5rem;
+    margin: 0;
+    
+    li {
+      position: relative;
+      padding-left: 1rem;
+      margin-bottom: 0.5rem;
+      color: #94A3B8;
+      font-size: 0.875rem;
+      line-height: 1.5;
+      
+      &::before {
+        content: '•';
+        position: absolute;
+        left: 0;
+        color: #7C3AED;
+      }
+    }
+  }
+}
+
+.result-actions {
+  display: flex;
+  justify-content: center;
+  gap: 1rem;
+  margin-top: 2rem;
+}
+
+@keyframes rotate {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}

+ 23 - 0
ai-interview/src/app/interviewer/steps/result-step/result-step.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ResultStep } from './result-step';
+
+describe('ResultStep', () => {
+  let component: ResultStep;
+  let fixture: ComponentFixture<ResultStep>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [ResultStep]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(ResultStep);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 193 - 0
ai-interview/src/app/interviewer/steps/result-step/result-step.ts

@@ -0,0 +1,193 @@
+// steps/result-step.component.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Router } from '@angular/router';
+import { Interviewer } from '../../interviewer';
+
+interface SkillRating {
+  name: string;
+  score: number;
+  description: string;
+}
+
+interface ImprovementSuggestion {
+  area: string;
+  suggestions: string[];
+}
+
+@Component({
+  selector: 'app-result-step',
+  standalone: true,
+  imports: [CommonModule,Interviewer],
+  template: `
+    <div class="result-container">
+      <div class="result-header">
+        <h2>AI面试结果分析</h2>
+        <p>基于您的表现生成的详细评估报告</p>
+      </div>
+      
+      <div class="result-summary">
+        <div class="overall-score">
+          <div class="score-circle">
+            <div class="score-value">{{ overallScore }}</div>
+            <div class="score-label">综合得分</div>
+          </div>
+          <div class="match-score">
+            <div class="match-value">{{ jobMatch }}%</div>
+            <div class="match-label">岗位匹配度</div>
+          </div>
+        </div>
+        
+        <div class="summary-text">
+          <p>{{ summaryText }}</p>
+        </div>
+      </div>
+      
+      <div class="skill-radar">
+        <h3>能力雷达图</h3>
+        <div class="radar-container">
+          <svg viewBox="0 0 200 200" class="radar-chart">
+            <polygon [attr.points]="getRadarPoints()" class="skill-area"/>
+            
+            <!-- 雷达图网格 -->
+            <g *ngFor="let level of [1, 2, 3, 4, 5]">
+              <circle cx="100" cy="100" [attr.r]="level * 20" class="grid-circle"/>
+            </g>
+            
+            <!-- 雷达图轴线 -->
+            <line x1="100" y1="0" x2="100" y2="200" class="axis-line"/>
+            <line x1="0" y1="100" x2="200" y2="100" class="axis-line"/>
+            <line x1="25" y1="25" x2="175" y2="175" class="axis-line"/>
+            <line x1="175" y1="25" x2="25" y2="175" class="axis-line"/>
+            
+            <!-- 技能标签 -->
+            <g *ngFor="let skill of skills; let i = index">
+              <text [attr.x]="getLabelX(i)" [attr.y]="getLabelY(i)" class="skill-label">
+                {{ skill.name }}
+              </text>
+            </g>
+          </svg>
+        </div>
+      </div>
+      
+      <div class="detailed-feedback">
+        <h3>详细评估</h3>
+        
+        <div class="skill-ratings">
+          <div *ngFor="let skill of skills" class="skill-rating">
+            <div class="skill-info">
+              <h4>{{ skill.name }}</h4>
+              <div class="rating-bar">
+                <div class="rating-fill" [style.width.%]="skill.score * 10"></div>
+                <div class="rating-value">{{ skill.score }}/10</div>
+              </div>
+            </div>
+            <p class="skill-description">{{ skill.description }}</p>
+          </div>
+        </div>
+      </div>
+      
+      <div class="improvement-suggestions">
+        <h3>改进建议</h3>
+        
+        <div *ngFor="let item of improvements" class="improvement-item">
+          <h4>{{ item.area }}</h4>
+          <ul>
+            <li *ngFor="let suggestion of item.suggestions">{{ suggestion }}</li>
+          </ul>
+        </div>
+      </div>
+      
+      <div class="result-actions">
+        <button class="sci-fi-button primary" (click)="downloadReport()">
+          下载完整报告
+        </button>
+        <button class="sci-fi-button secondary" (click)="returnToHome()">
+          返回首页
+        </button>
+      </div>
+    </div>
+  `,
+  styleUrls: ['./result-step.scss']
+})
+export class ResultStep {
+  overallScore = 7.8;
+  jobMatch = 82;
+  
+  summaryText = '您在技术能力方面表现优异,特别是在Angular框架的理解和应用上。沟通表达清晰,但在解决复杂问题的系统性思维方面还有提升空间。整体与目标岗位有较高匹配度。';
+  
+  skills: SkillRating[] = [
+    { name: '技术能力', score: 8.5, description: '对Angular框架有深入理解,能够熟练运用RxJS进行状态管理,熟悉组件化开发模式。' },
+    { name: '问题解决', score: 7.0, description: '能够分析并解决一般性问题,但在复杂系统问题上的解决策略有待加强。' },
+    { name: '沟通表达', score: 8.0, description: '表达清晰有条理,能够有效传达技术概念,偶尔会有技术术语使用不当的情况。' },
+    { name: '团队协作', score: 7.5, description: '表现出良好的团队合作意识,能够理解并接受他人意见,主动沟通方面可以更积极。' },
+    { name: '学习能力', score: 8.5, description: '展示出快速学习新技术的能力,能够通过文档和示例快速掌握新工具。' }
+  ];
+  
+  improvements: ImprovementSuggestion[] = [
+    {
+      area: '技术深度',
+      suggestions: [
+        '深入研究Angular变更检测机制,理解Zone.js的工作原理',
+        '学习高级RxJS操作符和模式,如自定义操作符、多播等',
+        '了解Angular性能优化策略,如懒加载、变更检测策略等'
+      ]
+    },
+    {
+      area: '问题解决',
+      suggestions: [
+        '练习系统性思考,尝试从多个角度分析问题',
+        '学习常见的设计模式和架构模式',
+        '在解决问题前先明确问题和约束条件'
+      ]
+    },
+    {
+      area: '沟通表达',
+      suggestions: [
+        '注意面向不同听众调整技术讲解的深度',
+        '练习使用更简洁的语言表达复杂概念',
+        '在回答前先组织语言结构'
+      ]
+    }
+  ];
+  
+  constructor(private router: Router) {}
+  
+  getRadarPoints(): string {
+    return this.skills.map((skill, i) => {
+      const angle = (Math.PI * 2 * i) / this.skills.length - Math.PI / 2;
+      const radius = skill.score * 20;
+      const x = 100 + radius * Math.cos(angle);
+      const y = 100 + radius * Math.sin(angle);
+      return `${x},${y}`;
+    }).join(' ');
+  }
+  
+  getLabelX(index: number): number {
+    const angle = (Math.PI * 2 * index) / this.skills.length - Math.PI / 2;
+    const radius = 110;
+    return 100 + radius * Math.cos(angle);
+  }
+  
+  getLabelY(index: number): number {
+    const angle = (Math.PI * 2 * index) / this.skills.length - Math.PI / 2;
+    const radius = 110;
+    return 100 + radius * Math.sin(angle);
+  }
+  
+  downloadReport() {
+  // 实际实现下载功能
+  const content = "这里是报告内容"; // 替换为实际报告内容
+  const blob = new Blob([content], { type: 'application/pdf' });
+  const url = window.URL.createObjectURL(blob);
+  const a = document.createElement('a');
+  a.href = url;
+  a.download = '面试报告.pdf';
+  a.click();
+  window.URL.revokeObjectURL(url);
+}
+  
+  returnToHome() {
+    this.router.navigate(['/']);
+  }
+}

+ 42 - 0
ai-interview/src/app/login-choice/login-choice.html

@@ -0,0 +1,42 @@
+<div class="cyber-container">
+  <!-- 全息投影背景效果 -->
+  <div class="holographic-bg"></div>
+  
+  <!-- 科幻风格面板 -->
+  <div class="cyber-panel">
+    <!-- 发光标题 -->
+    <h1 class="cyber-title">
+      <span class="glow-text">AI智能面试系统</span>
+      <div class="title-underline"></div>
+    </h1>
+    
+    <!-- 选项卡片容器 -->
+    <div class="cyber-cards">
+      <!-- 面板输入口 -->
+      <div class="cyber-card" [routerLink]="['/intervieweeauth']">
+        <div class="card-icon pulse-blue">
+          <i class="fas fa-desktop"></i>
+        </div>
+        <h2 class="cyber-heading">面试者入口</h2>
+        <p class="cyber-desc">体验AI模拟面试,获取智能评估与改进建议</p>
+        <div class="card-glow"></div>
+      </div>
+      
+      <!-- HR入口 -->
+      <div class="cyber-card" [routerLink]="['/hrauth']">
+        <div class="card-icon pulse-purple">
+          <i class="fas fa-id-card"></i>
+        </div>
+        <h2 class="cyber-heading">HR入口</h2>
+        <p class="cyber-desc">智能分析候选人数据,高效筛选人才</p>
+        <div class="card-glow"></div>
+      </div>
+    </div>
+    
+    <!-- 底部科技感装饰 -->
+    <div class="cyber-footer">
+      <div class="scanline"></div>
+      <p class="cyber-copyright">© {{currentYear}} 量子科技系统</p>
+    </div>
+  </div>
+</div>

+ 192 - 0
ai-interview/src/app/login-choice/login-choice.scss

@@ -0,0 +1,192 @@
+/* 科幻主题变量 */
+$neon-blue: #0ff;
+$neon-purple: #f0f;
+$dark-bg: #0a0a12;
+$panel-bg: rgba(10, 10, 25, 0.8);
+$text-glow: 0 0 8px;
+
+/* 基础容器 */
+.cyber-container {
+  position: relative;
+  height: 100vh;
+  background: $dark-bg;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  overflow: hidden;
+  font-family: 'Rajdhani', 'Arial Narrow', sans-serif;
+}
+
+/* 全息投影背景 */
+.holographic-bg {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  background: 
+    radial-gradient(circle at 30% 50%, rgba($neon-blue, 0.1) 0%, transparent 40%),
+    radial-gradient(circle at 70% 50%, rgba($neon-purple, 0.1) 0%, transparent 40%);
+  animation: hologram-pulse 8s infinite alternate;
+}
+
+/* 主面板样式 */
+.cyber-panel {
+  position: relative;
+  width: 90%;
+  max-width: 500px;
+  padding: 30px;
+  background: $panel-bg;
+  border: 1px solid rgba($neon-blue, 0.3);
+  box-shadow: 0 0 30px rgba($neon-blue, 0.2),
+             0 0 15px rgba($neon-purple, 0.1) inset;
+  backdrop-filter: blur(5px);
+  z-index: 2;
+}
+
+/* 科幻标题 */
+.cyber-title {
+  color: white;
+  text-align: center;
+  margin-bottom: 30px;
+  position: relative;
+  
+  .glow-text {
+    text-shadow: $text-glow $neon-blue;
+    font-size: 24px;
+    letter-spacing: 2px;
+  }
+  
+  .title-underline {
+    height: 2px;
+    width: 100px;
+    background: linear-gradient(90deg, transparent, $neon-blue, $neon-purple, transparent);
+    margin: 10px auto;
+    animation: underline-glow 3s infinite;
+  }
+}
+
+/* 卡片容器 */
+.cyber-cards {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+/* 科幻卡片 */
+.cyber-card {
+  position: relative;
+  padding: 25px;
+  background: rgba(15, 15, 30, 0.7);
+  border: 1px solid rgba($neon-blue, 0.2);
+  border-radius: 2px;
+  cursor: pointer;
+  transition: all 0.3s;
+  overflow: hidden;
+  
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 5px 15px rgba($neon-blue, 0.3);
+    
+    .card-glow {
+      opacity: 1;
+    }
+  }
+}
+
+/* 图标样式 */
+.card-icon {
+  font-size: 40px;
+  margin-bottom: 15px;
+  
+  &.pulse-blue {
+    color: $neon-blue;
+    animation: pulse 2s infinite;
+  }
+  
+  &.pulse-purple {
+    color: $neon-purple;
+    animation: pulse 2s infinite 0.5s;
+  }
+}
+
+/* 文字样式 */
+.cyber-heading {
+  color: white;
+  margin-bottom: 8px;
+  font-size: 18px;
+  letter-spacing: 1px;
+}
+
+.cyber-desc {
+  color: rgba(white, 0.7);
+  font-size: 14px;
+  margin: 0;
+}
+
+/* 卡片发光效果 */
+.card-glow {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: radial-gradient(circle at center, rgba($neon-blue, 0.1) 0%, transparent 70%);
+  opacity: 0;
+  transition: opacity 0.3s;
+}
+
+/* 底部样式 */
+.cyber-footer {
+  margin-top: 30px;
+  position: relative;
+  
+  .scanline {
+    height: 1px;
+    background: linear-gradient(90deg, transparent, $neon-blue, transparent);
+    margin-bottom: 15px;
+    animation: scanline 3s linear infinite;
+  }
+  
+  .cyber-copyright {
+    color: rgba(white, 0.5);
+    font-size: 12px;
+    letter-spacing: 1px;
+  }
+}
+
+/* 动画定义 */
+@keyframes hologram-pulse {
+  0% { opacity: 0.8; }
+  100% { opacity: 1; }
+}
+
+@keyframes pulse {
+  0% { text-shadow: 0 0 5px currentColor; }
+  50% { text-shadow: 0 0 15px currentColor; }
+  100% { text-shadow: 0 0 5px currentColor; }
+}
+
+@keyframes underline-glow {
+  0% { opacity: 0.5; width: 100px; }
+  50% { opacity: 1; width: 150px; }
+  100% { opacity: 0.5; width: 100px; }
+}
+
+@keyframes scanline {
+  0% { transform: translateX(-100%); }
+  100% { transform: translateX(100%); }
+}
+
+/* 响应式调整 */
+@media (max-width: 480px) {
+  .cyber-panel {
+    padding: 20px;
+  }
+  
+  .cyber-title .glow-text {
+    font-size: 20px;
+  }
+  
+  .card-icon {
+    font-size: 32px;
+  }
+}

+ 23 - 0
ai-interview/src/app/login-choice/login-choice.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginChoice } from './login-choice';
+
+describe('LoginChoice', () => {
+  let component: LoginChoice;
+  let fixture: ComponentFixture<LoginChoice>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [LoginChoice]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(LoginChoice);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 67 - 0
ai-interview/src/app/login-choice/login-choice.ts

@@ -0,0 +1,67 @@
+import { Component, AfterViewInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import Swiper from 'swiper';
+
+@Component({
+  selector: 'app-login-choice',
+  standalone: true,
+  imports: [CommonModule, RouterModule],
+  templateUrl: './login-choice.html',
+  styleUrls: ['./login-choice.scss']
+})
+export class LoginChoice implements AfterViewInit {
+  currentYear = new Date().getFullYear();
+  private swiper: Swiper | undefined;
+
+  ngAfterViewInit(): void {
+    new Swiper('.swiper-container', {
+      slidesPerView: 1.1,
+      spaceBetween: 15,
+      centeredSlides: true
+    });
+  }
+
+  private initSwiper(): void {
+    this.swiper = new Swiper('.swiper-container', {
+      effect: 'slide',
+      resistanceRatio: 0.3,
+      touchAngle: 30,
+      grabCursor: true,
+      centeredSlides: true,
+      slidesPerView: 1.1, // 让两侧露出一点点
+      spaceBetween: 20,
+    });
+  }
+
+  private createDynamicEffects(): void {
+    // 创建波纹动画
+    const createRipple = (parent: HTMLElement, color: string) => {
+      const ripple = document.createElement('div');
+      ripple.className = 'ripple';
+      ripple.style.backgroundColor = color;
+      ripple.style.left = `${Math.random() * 80 + 10}%`;
+      ripple.style.top = `${Math.random() * 80 + 10}%`;
+      parent.appendChild(ripple);
+      setTimeout(() => ripple.remove(), 2000);
+    };
+
+    // 为面试者卡片添加蓝色波纹
+    const interviewerSlide = document.querySelector('.slide-interviewer');
+    if (interviewerSlide) {
+      setInterval(() => createRipple(interviewerSlide as HTMLElement, 'rgba(100, 200, 255, 0.3)'), 1200);
+    }
+
+    // 为HR卡片添加紫色数据流动画
+    const hrSlide = document.querySelector('.slide-hr');
+    if (hrSlide) {
+      setInterval(() => {
+        const dataFlow = document.createElement('div');
+        dataFlow.className = 'data-flow';
+        dataFlow.style.left = `${Math.random() * 80 + 10}%`;
+        hrSlide.appendChild(dataFlow);
+        setTimeout(() => dataFlow.remove(), 1000);
+      }, 800);
+    }
+  }
+}

+ 0 - 17
ai-interview/src/app/material.provider.ts

@@ -1,17 +0,0 @@
-import { ApplicationConfig } from '@angular/core';
-import { provideNativeDateAdapter } from '@angular/material/core';
-import { MAT_TABS_CONFIG, MatTabsConfig } from '@angular/material/tabs';
-
-export function provideMaterial() {
-  return [
-    provideNativeDateAdapter(),
-    // 配置 Material 组件
-    {
-      provide: MAT_TABS_CONFIG,
-      useValue: {
-        animationDuration: '0ms',
-        preserveContent: true
-      } as MatTabsConfig
-    }
-  ];
-}

+ 18 - 0
ai-interview/src/app/shard/directives/swiper.directive.ts

@@ -0,0 +1,18 @@
+import { Directive, ElementRef, Input, OnInit } from '@angular/core';
+import { SwiperContainer } from 'swiper/element';
+import { SwiperOptions } from 'swiper/types';
+
+@Directive({
+  selector: '[swiper]',
+  standalone: true
+})
+export class SwiperDirective implements OnInit {
+  @Input('config') config?: SwiperOptions;
+
+  constructor(private el: ElementRef<SwiperContainer>) {}
+
+  ngOnInit() {
+    Object.assign(this.el.nativeElement, this.config);
+    this.el.nativeElement.initialize();
+  }
+}

+ 2 - 0
ai-interview/src/index.html

@@ -8,6 +8,8 @@
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
   <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+ <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
+  <link href="https://cdn.bootcdn.net/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
 </head>
 <body class="mat-typography">
   <app-root></app-root>

+ 5 - 1
ai-interview/src/main.ts

@@ -1,6 +1,10 @@
 import { bootstrapApplication } from '@angular/platform-browser';
-import { appConfig } from './app/app.config';
+import { provideRouter } from '@angular/router';
 import { App } from './app/app';
+import { routes } from './app/app.routes';
+import { importProvidersFrom } from '@angular/core';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { appConfig } from './app/app.config';
 
 bootstrapApplication(App, appConfig)
   .catch((err) => console.error(err));

+ 0 - 2
ai-interview/src/moudles/interviewee/interviewee-home/interviewee-home.html

@@ -1,2 +0,0 @@
-<p>interviewee-home works!</p>
-<router-outlet />

+ 0 - 13
ai-interview/src/moudles/interviewee/interviewee-home/interviewee-home.ts

@@ -1,13 +0,0 @@
-import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
-
-@Component({
-  selector: 'app-interviewee-home',
-  imports: [RouterOutlet],
-  standalone: true,
-  templateUrl: './interviewee-home.html',
-  styleUrl: './interviewee-home.scss'
-})
-export class IntervieweeHome {
-
-}

+ 0 - 2
ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.html

@@ -1,2 +0,0 @@
-<p>interviewee-interview works!</p>
-<router-outlet />

+ 0 - 0
ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.scss


+ 0 - 23
ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.spec.ts

@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { IntervieweeInterview } from './interviewee-interview';
-
-describe('IntervieweeInterview', () => {
-  let component: IntervieweeInterview;
-  let fixture: ComponentFixture<IntervieweeInterview>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [IntervieweeInterview]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(IntervieweeInterview);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});

+ 0 - 13
ai-interview/src/moudles/interviewee/interviewee-interview/interviewee-interview.ts

@@ -1,13 +0,0 @@
-import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
-
-@Component({
-  selector: 'app-interviewee-interview',
-  imports: [RouterOutlet],
-  standalone: true,
-  templateUrl: './interviewee-interview.html',
-  styleUrl: './interviewee-interview.scss'
-})
-export class IntervieweeInterview {
-
-}

+ 0 - 2
ai-interview/src/moudles/interviewee/interviewee-mine/interviewee-mine.html

@@ -1,2 +0,0 @@
-<p>interviewee-mine works!</p>
-<router-outlet />

+ 0 - 0
ai-interview/src/moudles/interviewee/interviewee-mine/interviewee-mine.scss


+ 0 - 13
ai-interview/src/moudles/interviewee/interviewee-mine/interviewee-mine.ts

@@ -1,13 +0,0 @@
-import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
-
-@Component({
-  selector: 'app-interviewee-mine',
-  imports: [RouterOutlet],
-  standalone: true,
-  templateUrl: './interviewee-mine.html',
-  styleUrl: './interviewee-mine.scss'
-})
-export class IntervieweeMine {
-
-}

+ 0 - 28
ai-interview/src/moudles/interviewee/interviewee.routes.ts

@@ -1,28 +0,0 @@
-import { Routes } from '@angular/router';
-import { NavIntervieweeTabs } from './nav-interviewee-tabs/nav-interviewee-tabs';
-
-export const intervieweeRoutes: Routes = [
-  {
-    path: '',
-    component: NavIntervieweeTabs,
-    children: [
-      {
-        path: 'home',
-        loadComponent: () => import('./interviewee-home/interviewee-home').then(m => m.IntervieweeHome),
-      },
-      {
-        path: 'interview',
-        loadComponent: () => import('./interviewee-interview/interviewee-interview').then(m=>m.IntervieweeInterview)
-      },
-      {
-        path: 'mine',
-        loadComponent: () => import('./interviewee-mine/interviewee-mine').then(m => m.IntervieweeMine),
-      },
-      {
-        path: '',
-        redirectTo: 'home',
-        pathMatch: 'full',
-      },
-    ],
-  },
-];

+ 0 - 17
ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.html

@@ -1,17 +0,0 @@
-<!-- nav-interviewee-tabs.html -->
-<div class="tabs-container">
-  <mat-tab-nav-panel #tabPanel>
-    <router-outlet></router-outlet>
-  </mat-tab-nav-panel>
-</div>
-
-<nav mat-tab-nav-bar [tabPanel]="tabPanel" class="tabs-nav">
-  <a mat-tab-link
-     *ngFor="let tab of tabs"
-     [routerLink]="tab.path"
-     routerLinkActive #rla="routerLinkActive"
-     [active]="rla.isActive">
-    <mat-icon>{{ tab.icon }}</mat-icon>
-    <span>{{ tab.label }}</span>
-  </a>
-</nav>

+ 0 - 30
ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.scss

@@ -1,30 +0,0 @@
- :host {
-      display: flex;
-      flex-direction: column;
-      height: 100%;
-    }
-    .tabs-container {
-      flex: 1;
-      overflow-y: auto;
-    }
-    .tabs-nav {
-      display: flex;
-      justify-content: space-around;
-      background: white;
-      border-top: 1px solid #e0e0e0;
-    }
-    a[mat-tab-link] {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      min-width: 80px;
-      padding: 8px 12px;
-      opacity: 0.6;
-    }
-    a[mat-tab-link][active] {
-      opacity: 1;
-    }
-    mat-icon {
-      margin-bottom: 4px;
-    }

+ 0 - 23
ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.spec.ts

@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { NavIntervieweeTabs } from './nav-interviewee-tabs';
-
-describe('NavIntervieweeTabs', () => {
-  let component: NavIntervieweeTabs;
-  let fixture: ComponentFixture<NavIntervieweeTabs>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [NavIntervieweeTabs]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(NavIntervieweeTabs);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});

+ 0 - 28
ai-interview/src/moudles/interviewee/nav-interviewee-tabs/nav-interviewee-tabs.ts

@@ -1,28 +0,0 @@
-// nav-interviewee-tabs.ts
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
-import { MatTabsModule } from '@angular/material/tabs';
-import { MatIconModule } from '@angular/material/icon';
-
-@Component({
-  selector: 'app-nav-interviewee-tabs',
-  standalone: true,
-  imports: [
-    CommonModule,
-    RouterOutlet,
-    RouterLink,
-    RouterLinkActive,
-    MatTabsModule,
-    MatIconModule
-  ],
-  templateUrl: './nav-interviewee-tabs.html',
-  styleUrl: './nav-interviewee-tabs.scss'
-})
-export class NavIntervieweeTabs {
-  tabs = [
-    { path: 'home', label: '首页', icon: 'home' },
-    { path: 'interview', label: '面试', icon: 'assignment' },
-    { path: 'mine', label: '我的', icon: 'person' },
-  ];
-}

+ 0 - 1
ai-interview/src/moudles/interviewer/interviewer-home/interviewer-home.html

@@ -1 +0,0 @@
-<p>interviewer-home works!</p>

+ 0 - 0
ai-interview/src/moudles/interviewer/interviewer-home/interviewer-home.scss


+ 0 - 11
ai-interview/src/moudles/interviewer/interviewer-home/interviewer-home.ts

@@ -1,11 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'app-interviewer-home',
-  imports: [],
-  templateUrl: './interviewer-home.html',
-  styleUrl: './interviewer-home.scss'
-})
-export class InterviewerHome {
-
-}

部分文件因为文件数量过多而无法显示