Browse Source

docs xiangxi

0235713 12 hours ago
parent
commit
223f9dd780

+ 7 - 5
interview-web/src/app/app.config.ts

@@ -1,12 +1,14 @@
-import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
+import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
 import { provideRouter } from '@angular/router';
-
+import { provideClientHydration } from '@angular/platform-browser';
 import { routes } from './app.routes';
+import { provideCharts } from 'ng2-charts';
 
 export const appConfig: ApplicationConfig = {
   providers: [
-    provideBrowserGlobalErrorListeners(),
     provideZoneChangeDetection({ eventCoalescing: true }),
-    provideRouter(routes)
+    provideRouter(routes),
+    provideClientHydration(),
+    
   ]
-};
+};

+ 10 - 1
interview-web/src/modules/interview/mobile/mobile.routes.ts

@@ -38,5 +38,14 @@ export const MOBILE_ROUTES: Routes = [
   {
   path: 'interview/mobile/page-question-bank',
   loadComponent: () => import('./page-question-bank/page-question-bank').then(m => m.PageQuestionBank)
-}
+  },
+  {
+  path: 'interview/mobile/page-job-detail/:id',
+  loadComponent: () => import('./page-job-detail/page-job-detail').then(m => m.PageJobDetail)
+  },
+  {
+  path: 'interview/mobile/page-ability-analysis',
+  loadComponent: () => import('./page-ability-analysis/page-ability-analysis').then(m => m.PageAbilityAnalysis)
+  }
+
 ];

+ 82 - 0
interview-web/src/modules/interview/mobile/page-ability-analysis/page-ability-analysis.html

@@ -0,0 +1,82 @@
+<div class="ability-container">
+  <!-- 头部 -->
+  <div class="header">
+    <button class="back-btn" (click)="goBack()">
+      <fa-icon [icon]="icons.arrowLeft"></fa-icon>
+    </button>
+    <h1 class="title">个人能力分析</h1>
+    <div class="placeholder"></div>
+  </div>
+
+  <!-- 能力雷达图 -->
+  <div class="chart-section">
+    <h2 class="section-title">能力雷达图</h2>
+    <div class="chart-container">
+      <canvas baseChart
+        [data]="radarChartData"
+        [options]="radarChartOptions"
+        [labels]="radarChartLabels"
+        chartType="radar">
+      </canvas>
+    </div>
+    <div class="chart-legend">
+      <div class="legend-item">
+        <span class="legend-color" style="background-color: #2A5CAA;"></span>
+        <span>当前能力</span>
+      </div>
+      <div class="legend-item">
+        <span class="legend-color" style="background-color: #FF6B35;"></span>
+        <span>岗位要求</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- 能力详情 -->
+  <div class="details-section">
+    <h2 class="section-title">能力详情</h2>
+    <div class="ability-cards">
+      <div class="ability-card" *ngFor="let ability of abilityDetails">
+        <div class="ability-header">
+          <div class="ability-icon" [style.background]="'rgba(42, 92, 170, 0.1)'">
+            <fa-icon [icon]="ability.icon"></fa-icon>
+          </div>
+          <h3 class="ability-title">{{ability.title}}</h3>
+        </div>
+        <div class="ability-score">
+          <span class="score-value">{{ability.score}}</span>
+          <span class="score-label">分</span>
+        </div>
+        <p class="ability-desc">{{ability.description}}</p>
+        <div class="progress-bar">
+          <div class="progress-fill" [style.width.%]="ability.score"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- 历史趋势 -->
+  <div class="history-section">
+    <h2 class="section-title">能力成长趋势</h2>
+    <div class="history-chart">
+      <canvas baseChart
+        [data]="historyChartData"
+        [options]="historyChartOptions"
+        [labels]="historyChartLabels"
+        chartType="line">
+      </canvas>
+    </div>
+  </div>
+
+  <!-- 改进建议 -->
+  <div class="suggestions-section">
+    <h2 class="section-title">改进建议</h2>
+    <div class="suggestion-card">
+      <h3 class="suggestion-title">提升编码能力</h3>
+      <ul class="suggestion-list">
+        <li>每周完成3道LeetCode中等难度算法题</li>
+        <li>阅读《重构:改善既有代码的设计》</li>
+        <li>参与开源项目,学习优秀代码风格</li>
+      </ul>
+    </div>
+  </div>
+</div>

+ 212 - 0
interview-web/src/modules/interview/mobile/page-ability-analysis/page-ability-analysis.scss

@@ -0,0 +1,212 @@
+.ability-container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 25px;
+  
+  .back-btn {
+    background: none;
+    border: none;
+    font-size: 20px;
+    color: #2A5CAA;
+    cursor: pointer;
+  }
+  
+  .title {
+    font-size: 20px;
+    font-weight: 600;
+    color: #2D3748;
+  }
+  
+  .placeholder {
+    width: 24px; // 保持对称
+  }
+}
+
+.section-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #2D3748;
+  margin-bottom: 15px;
+  padding-left: 10px;
+  position: relative;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 5px;
+    height: 16px;
+    width: 4px;
+    background: #2A5CAA;
+    border-radius: 2px;
+  }
+}
+
+.chart-section {
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 15px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+  
+  .chart-container {
+    height: 300px;
+    margin-bottom: 15px;
+  }
+  
+  .chart-legend {
+    display: flex;
+    justify-content: center;
+    gap: 20px;
+    
+    .legend-item {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #4A5568;
+      
+      .legend-color {
+        display: inline-block;
+        width: 12px;
+        height: 12px;
+        border-radius: 50%;
+        margin-right: 6px;
+      }
+    }
+  }
+}
+
+.details-section {
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 15px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+  
+  .ability-cards {
+    display: grid;
+    grid-template-columns: 1fr;
+    gap: 15px;
+    
+    @media (min-width: 600px) {
+      grid-template-columns: repeat(2, 1fr);
+    }
+  }
+  
+  .ability-card {
+    border: 1px solid #EDF2F7;
+    border-radius: 10px;
+    padding: 15px;
+    
+    .ability-header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 10px;
+      
+      .ability-icon {
+        width: 36px;
+        height: 36px;
+        border-radius: 50%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-right: 12px;
+        color: #2A5CAA;
+        font-size: 16px;
+      }
+      
+      .ability-title {
+        font-size: 16px;
+        font-weight: 500;
+        color: #2D3748;
+      }
+    }
+    
+    .ability-score {
+      margin-bottom: 10px;
+      
+      .score-value {
+        font-size: 24px;
+        font-weight: 600;
+        color: #2A5CAA;
+      }
+      
+      .score-label {
+        font-size: 14px;
+        color: #718096;
+        margin-left: 2px;
+      }
+    }
+    
+    .ability-desc {
+      font-size: 13px;
+      color: #718096;
+      margin-bottom: 15px;
+      line-height: 1.5;
+    }
+    
+    .progress-bar {
+      height: 6px;
+      background: #EDF2F7;
+      border-radius: 3px;
+      overflow: hidden;
+      
+      .progress-fill {
+        height: 100%;
+        background: linear-gradient(to right, #2A5CAA, #3A7BD5);
+        border-radius: 3px;
+      }
+    }
+  }
+}
+
+.history-section {
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 15px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+  
+  .history-chart {
+    height: 250px;
+  }
+}
+
+.suggestions-section {
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 30px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+  
+  .suggestion-card {
+    background: #F0F9FF;
+    border-radius: 10px;
+    padding: 15px;
+    
+    .suggestion-title {
+      font-size: 16px;
+      font-weight: 500;
+      color: #2A5CAA;
+      margin-bottom: 10px;
+    }
+    
+    .suggestion-list {
+      padding-left: 20px;
+      color: #4A5568;
+      font-size: 14px;
+      line-height: 1.6;
+      
+      li {
+        margin-bottom: 8px;
+      }
+    }
+  }
+}

+ 23 - 0
interview-web/src/modules/interview/mobile/page-ability-analysis/page-ability-analysis.spec.ts

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

+ 158 - 0
interview-web/src/modules/interview/mobile/page-ability-analysis/page-ability-analysis.ts

@@ -0,0 +1,158 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import { 
+  faArrowLeft,
+  faChartLine,
+  faLightbulb,
+  faCode,
+  faComments,
+  faUsers,
+  faClock
+} from '@fortawesome/free-solid-svg-icons';
+import { ChartConfiguration, ChartType } from 'chart.js';
+import { BaseChartDirective } from 'ng2-charts';
+import { Location } from '@angular/common';
+
+@Component({
+  selector: 'app-page-ability-analysis',
+  standalone: true,
+  imports: [CommonModule, FaIconComponent, RouterModule , BaseChartDirective],
+  templateUrl: './page-ability-analysis.html',
+  styleUrls: ['./page-ability-analysis.scss'],
+  
+})
+export class PageAbilityAnalysis {
+  icons = {
+    arrowLeft: faArrowLeft,
+    chart: faChartLine,
+    knowledge: faLightbulb,
+    coding: faCode,
+    communication: faComments,
+    teamwork: faUsers,
+    efficiency: faClock
+  };
+
+  constructor(private location: Location) {}
+
+  goBack() {
+    this.location.back();
+  }
+
+  public radarChartOptions: ChartConfiguration<'radar'>['options'] = {
+  responsive: true,
+  scales: {
+    r: {
+      angleLines: { display: true },
+      suggestedMin: 0,
+      suggestedMax: 100,
+      ticks: { stepSize: 20 }
+    }
+  }
+};
+
+public radarChartLabels = ['专业知识', '编码能力', '沟通表达', '团队协作', '工作效率', '学习能力'];
+
+public radarChartData: ChartConfiguration<'radar'>['data'] = {
+  labels: this.radarChartLabels,
+  datasets: [
+    {
+      label: '当前能力',
+      data: [85, 78, 90, 82, 88, 92],
+      backgroundColor: 'rgba(42, 92, 170, 0.2)',
+      borderColor: 'rgba(42, 92, 170, 1)',
+      pointBackgroundColor: 'rgba(42, 92, 170, 1)',
+      pointBorderColor: '#fff',
+      pointHoverBackgroundColor: '#fff',
+      pointHoverBorderColor: 'rgba(42, 92, 170, 1)'
+    },
+    {
+      label: '岗位要求',
+      data: [90, 85, 85, 80, 85, 88],
+      backgroundColor: 'rgba(255, 107, 53, 0.2)',
+      borderColor: 'rgba(255, 107, 53, 1)',
+      pointBackgroundColor: 'rgba(255, 107, 53, 1)',
+      pointBorderColor: '#fff',
+      pointHoverBackgroundColor: '#fff',
+      pointHoverBorderColor: 'rgba(255, 107, 53, 1)'
+    }
+  ]
+};
+
+  // 能力详情
+  abilityDetails = [
+    {
+      title: '专业知识',
+      icon: this.icons.knowledge,
+      score: 85,
+      description: '掌握前端开发核心知识体系'
+    },
+    {
+      title: '编码能力',
+      icon: this.icons.coding,
+      score: 78,
+      description: '熟练使用多种编程语言和框架'
+    },
+    {
+      title: '沟通表达',
+      icon: this.icons.communication,
+      score: 90,
+      description: '能够清晰表达技术观点'
+    },
+    {
+      title: '团队协作',
+      icon: this.icons.teamwork,
+      score: 82,
+      description: '良好的团队合作意识'
+    },
+    {
+      title: '工作效率',
+      icon: this.icons.efficiency,
+      score: 88,
+      description: '高效完成任务的能力'
+    },
+    {
+      title: '学习能力',
+      icon: this.icons.chart,
+      score: 92,
+      description: '快速学习新技术的能力'
+    }
+  ];
+
+  // 历史趋势图数据
+  historyChartLabels = ['2023-01', '2023-04', '2023-07'];
+  historyChartOptions = {
+    responsive: true,
+    scales: {
+      y: {
+        min: 60,
+        max: 100
+      }
+    }
+  };
+  
+  historyChartData = {
+    labels: this.historyChartLabels,
+    datasets: [
+      {
+        label: '专业知识',
+        data: [70, 75, 80],
+        borderColor: '#4BC0C0',
+        backgroundColor: 'rgba(75, 192, 192, 0.2)'
+      },
+      {
+        label: '编码能力',
+        data: [65, 70, 75],
+        borderColor: '#FF9F40',
+        backgroundColor: 'rgba(255, 159, 64, 0.2)'
+      },
+      {
+        label: '沟通表达',
+        data: [75, 80, 85],
+        borderColor: '#9966FF',
+        backgroundColor: 'rgba(153, 102, 255, 0.2)'
+      }
+    ]
+  };
+}

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

@@ -26,7 +26,7 @@
     </h2>
      <swiper-container [init]="false" [config]="swiperConfig" class="mySwiper">
       <swiper-slide *ngFor="let job of jobs">
-        <div class="job-card" (click)="handleCardClick()">
+        <div class="job-card" (click)="handleCardClick(job)">
           <div class="job-header">
             <h3 class="job-title">{{job.title}}</h3>
             <div class="job-salary">{{job.salary}}</div>

+ 20 - 10
interview-web/src/modules/interview/mobile/page-home/page-home.ts

@@ -112,6 +112,13 @@ export class PageHome {
       gradient: 'linear-gradient(135deg, #48BB78, #38A169)',
       RouterLink:'/interview/mobile/page-mine'
     },
+    {
+    icon: this.icons.chartLine,
+    title: '成长报告',
+    description: '查看历史表现',
+    gradient: 'linear-gradient(135deg, #48BB78, #38A169)',
+    RouterLink: '/interview/mobile/page-ability-analysis'
+    }
     
   ];
 
@@ -128,16 +135,19 @@ export class PageHome {
     }
   };
 
-  // 处理卡片点击
-  handleCardClick() {
-    alert('即将跳转到对应功能页面');
+    // 处理卡片点击
+  handleCardClick(job: any) {
+    // 在实际应用中,这里应该使用job.id
+    // 现在我们使用数组索引+1作为ID模拟
+    const jobId = (this.jobs.indexOf(job) + 1).toString();
+    this.router.navigate(['/interview/mobile/page-job-detail', jobId]);
   }
 
-  // 获取问候语
-  get greeting() {
-    const hour = new Date().getHours();
-    if (hour < 12) return '上午好';
-    if (hour < 18) return '下午好';
-    return '晚上好';
-  }
+    // 获取问候语
+    get greeting() {
+      const hour = new Date().getHours();
+      if (hour < 12) return '上午好';
+      if (hour < 18) return '下午好';
+      return '晚上好';
+    }
 }

+ 61 - 0
interview-web/src/modules/interview/mobile/page-job-detail/page-job-detail.html

@@ -0,0 +1,61 @@
+<div class="job-detail-container">
+  <!-- 头部 -->
+  <div class="job-header">
+    <button class="back-btn" (click)="goBack()">
+      <fa-icon [icon]="icons.arrowLeft"></fa-icon>
+    </button>
+    <div class="action-buttons">
+      <button class="icon-btn">
+        <fa-icon [icon]="icons.bookmark"></fa-icon>
+      </button>
+      <button class="icon-btn">
+        <fa-icon [icon]="icons.share"></fa-icon>
+      </button>
+    </div>
+  </div>
+
+  <!-- 职位基本信息 -->
+  <div class="job-basic-info">
+    <h1 class="job-title">{{job.title}}</h1>
+    <div class="job-salary">{{job.salary}}</div>
+    
+    <div class="job-meta">
+      <span>{{job.companyName}}</span>
+      <span>{{job.location}}</span>
+      <span>{{job.experience}}</span>
+      <span>{{job.education}}</span>
+    </div>
+    
+    <div class="job-tags">
+      <span class="tag" *ngFor="let tag of job.tags">{{tag}}</span>
+    </div>
+    
+    <div class="match-info">
+      <span>匹配度 {{job.match}}%</span>
+      <div class="progress-bar">
+        <div class="progress-fill" [style.width.%]="job.match"></div>
+      </div>
+    </div>
+  </div>
+
+  <!-- 职位详情 -->
+  <div class="job-content">
+    <h2>职位描述</h2>
+    <pre class="job-description">{{job.description}}</pre>
+    
+    <h2>公司福利</h2>
+    <div class="benefits">
+      <div class="benefit-item" *ngFor="let benefit of job.benefits">
+        {{benefit}}
+      </div>
+    </div>
+  </div>
+
+  <!-- 底部操作栏 -->
+  <div class="job-actions">
+    <button class="apply-btn" (click)="applyJob()">
+      <fa-icon [icon]="icons.apply"></fa-icon>
+      <span>立即申请</span>
+    </button>
+  </div>
+</div>

+ 164 - 0
interview-web/src/modules/interview/mobile/page-job-detail/page-job-detail.scss

@@ -0,0 +1,164 @@
+.job-detail-container {
+  padding: 20px;
+  padding-bottom: 80px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+}
+
+.job-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  
+  .back-btn {
+    background: none;
+    border: none;
+    font-size: 20px;
+    color: #2A5CAA;
+    cursor: pointer;
+  }
+  
+  .action-buttons {
+    display: flex;
+    gap: 15px;
+    
+    .icon-btn {
+      background: none;
+      border: none;
+      font-size: 18px;
+      color: #4A5568;
+      cursor: pointer;
+    }
+  }
+}
+
+.job-basic-info {
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 15px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+  
+  .job-title {
+    font-size: 22px;
+    margin-bottom: 5px;
+    color: #2D3748;
+  }
+  
+  .job-salary {
+    color: #FF6B35;
+    font-size: 18px;
+    font-weight: bold;
+    margin-bottom: 15px;
+  }
+  
+  .job-meta {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+    margin-bottom: 15px;
+    color: #718096;
+    font-size: 14px;
+  }
+  
+  .job-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    margin-bottom: 15px;
+    
+    .tag {
+      background: #EDF2F7;
+      padding: 4px 10px;
+      border-radius: 20px;
+      font-size: 12px;
+      color: #4A5568;
+    }
+  }
+  
+  .match-info {
+    font-size: 14px;
+    color: #4A5568;
+    
+    .progress-bar {
+      height: 6px;
+      background: #EDF2F7;
+      border-radius: 3px;
+      margin-top: 5px;
+      overflow: hidden;
+      
+      .progress-fill {
+        height: 100%;
+        background: linear-gradient(to right, #2A5CAA, #3A7BD5);
+        border-radius: 3px;
+      }
+    }
+  }
+}
+
+.job-content {
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 15px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+  
+  h2 {
+    font-size: 18px;
+    margin-bottom: 15px;
+    color: #2D3748;
+  }
+  
+  .job-description {
+    white-space: pre-wrap;
+    font-size: 14px;
+    line-height: 1.6;
+    color: #4A5568;
+    margin-bottom: 20px;
+  }
+  
+  .benefits {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+    
+    .benefit-item {
+      background: #F0FFF4;
+      color: #38A169;
+      padding: 6px 12px;
+      border-radius: 20px;
+      font-size: 13px;
+    }
+  }
+}
+
+.job-actions {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: white;
+  padding: 15px 20px;
+  box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
+  
+  .apply-btn {
+    background: linear-gradient(to right, #2A5CAA, #3A7BD5);
+    color: white;
+    border: none;
+    width: 100%;
+    padding: 12px;
+    border-radius: 30px;
+    font-size: 16px;
+    font-weight: 500;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 8px;
+    cursor: pointer;
+    
+    &:hover {
+      opacity: 0.9;
+    }
+  }
+}

+ 23 - 0
interview-web/src/modules/interview/mobile/page-job-detail/page-job-detail.spec.ts

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

+ 109 - 0
interview-web/src/modules/interview/mobile/page-job-detail/page-job-detail.ts

@@ -0,0 +1,109 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ActivatedRoute } from '@angular/router';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import { 
+  faArrowLeft,
+  faBookmark,
+  faShareNodes,
+  faPaperPlane
+} from '@fortawesome/free-solid-svg-icons';
+
+// 定义职位详情接口
+interface JobDetail {
+  id: string;
+  title: string;
+  salary: string;
+  companyLogo: string;
+  companyName: string;
+  location: string;
+  experience: string;
+  education: string;
+  tags: string[];
+  match: number;
+  description: string;
+  benefits: string[];
+}
+
+@Component({
+  selector: 'app-page-job-detail',
+  standalone: true,
+  imports: [CommonModule, FaIconComponent],
+  templateUrl: './page-job-detail.html',
+  styleUrls: ['./page-job-detail.scss']
+})
+export class PageJobDetail {
+  icons = {
+    arrowLeft: faArrowLeft,
+    bookmark: faBookmark,
+    share: faShareNodes,
+    apply: faPaperPlane
+  };
+
+  job!: JobDetail;
+  
+  constructor(private route: ActivatedRoute) {
+    // 使用非空断言操作符(!)告诉TypeScript我们知道这个值不会是null
+    const jobId = this.route.snapshot.paramMap.get('id')!;
+    this.loadJobDetail(jobId);
+  }
+
+  loadJobDetail(jobId: string) {
+    // 定义更完整的模拟数据类型
+    const mockJobs: Record<string, JobDetail> = {
+      '1': {
+        id: '1',
+        title: '前端开发工程师',
+        salary: '25-40K·15薪',
+        companyLogo: 'TX',
+        companyName: '腾讯科技',
+        location: '深圳',
+        experience: '3-5年',
+        education: '本科及以上',
+        tags: ['Vue.js', 'React', 'TypeScript'],
+        match: 87,
+        description: '岗位职责:\n1. 负责公司前端项目开发与维护\n2. 参与产品需求讨论和技术方案设计\n3. 优化前端性能,提升用户体验\n\n任职要求:\n1. 精通HTML/CSS/JavaScript\n2. 熟练掌握Vue.js/React框架\n3. 有大型项目开发经验者优先',
+        benefits: ['五险一金', '年度体检', '带薪年假', '弹性工作制']
+      },
+      '2': {
+        id: '2',
+        title: 'Java高级工程师',
+        salary: '30-50K·16薪',
+        companyLogo: 'AL',
+        companyName: '阿里巴巴',
+        location: '杭州',
+        experience: '5-8年',
+        education: '本科及以上',
+        tags: ['Spring Cloud', '分布式', 'MySQL'],
+        match: 76,
+        description: '岗位职责:\n1. 负责核心系统架构设计与开发\n2. 解决高并发场景下的技术难题\n3. 参与技术方案评审\n\n任职要求:\n1. 精通Java及常用框架\n2. 熟悉分布式系统设计\n3. 有性能优化经验',
+        benefits: ['六险一金', '股票期权', '免费三餐', '年度旅游']
+      },
+      '3': {
+        id: '3',
+        title: '产品经理',
+        salary: '20-35K·14薪',
+        companyLogo: 'BD',
+        companyName: '百度',
+        location: '北京',
+        experience: '3-5年',
+        education: '本科及以上',
+        tags: ['Axure', '用户研究', 'PRD'],
+        match: 92,
+        description: '岗位职责:\n1. 负责产品规划与设计\n2. 进行市场调研和竞品分析\n3. 协调研发团队推进产品落地\n\n任职要求:\n1. 熟悉产品设计流程\n2. 优秀的沟通协调能力\n3. 有成功产品案例',
+        benefits: ['五险一金', '弹性工作', '学习基金', '健身房']
+      }
+    };
+    
+    // 使用类型断言确保jobId是mockJobs的合法键
+    this.job = mockJobs[jobId as keyof typeof mockJobs] || mockJobs['1'];
+  }
+
+  goBack() {
+    window.history.back();
+  }
+
+  applyJob() {
+    alert(`已申请 ${this.job.title} 职位`);
+  }
+}

+ 179 - 0
package-lock.json

@@ -0,0 +1,179 @@
+{
+  "name": "Works",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "chart.js": "^4.5.0",
+        "ng2-charts": "^8.0.0"
+      }
+    },
+    "node_modules/@angular/cdk": {
+      "version": "20.0.5",
+      "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.0.5.tgz",
+      "integrity": "sha512-WhJ1I/ib/Za0qjWkSzMYV0gM8NOWrtOcZ2TYZ4aYFsjd8E13rGhxOez0DWt2sN3vfjAc1iWMmGGbNZrkp98adg==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "parse5": "^7.1.2",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/common": "^20.0.0 || ^21.0.0",
+        "@angular/core": "^20.0.0 || ^21.0.0",
+        "rxjs": "^6.5.3 || ^7.4.0"
+      }
+    },
+    "node_modules/@angular/common": {
+      "version": "20.0.6",
+      "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.0.6.tgz",
+      "integrity": "sha512-NRsq2gI4CH8nEy8yEZFySEmZ4U+1Y1yGzdIFubrKmtE2NXxR4KFGvQCkBLCLh6hNQXQx+Soe126bqByA6oIaFw==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+      },
+      "peerDependencies": {
+        "@angular/core": "20.0.6",
+        "rxjs": "^6.5.3 || ^7.4.0"
+      }
+    },
+    "node_modules/@angular/core": {
+      "version": "20.0.6",
+      "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.0.6.tgz",
+      "integrity": "sha512-PLSRl8vM8I+HOlAJFiTcRMNbRj2Clb7lpQqUfkeBSk8bBWOy9fLlscoY3JOk0tXoUTnW6lbRB1LmAFuYAQZzAA==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+      },
+      "peerDependencies": {
+        "@angular/compiler": "20.0.6",
+        "rxjs": "^6.5.3 || ^7.4.0",
+        "zone.js": "~0.15.0"
+      },
+      "peerDependenciesMeta": {
+        "@angular/compiler": {
+          "optional": true
+        },
+        "zone.js": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@angular/platform-browser": {
+      "version": "20.0.6",
+      "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.0.6.tgz",
+      "integrity": "sha512-EZC6ILD0nXOddNuwqQqwTzvRgXs/1kZoRGzdG8zpHhRREBf6VFMZ+g7IN3EKnYN4hDL5EMxIKIsIcQjmCDsu2A==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "engines": {
+        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+      },
+      "peerDependencies": {
+        "@angular/animations": "20.0.6",
+        "@angular/common": "20.0.6",
+        "@angular/core": "20.0.6"
+      },
+      "peerDependenciesMeta": {
+        "@angular/animations": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@kurkle/color": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+      "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+      "license": "MIT"
+    },
+    "node_modules/chart.js": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
+      "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@kurkle/color": "^0.3.0"
+      },
+      "engines": {
+        "pnpm": ">=8"
+      }
+    },
+    "node_modules/entities": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+      "license": "BSD-2-Clause",
+      "peer": true,
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
+    "node_modules/ng2-charts": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-8.0.0.tgz",
+      "integrity": "sha512-nofsNHI2Zt+EAwT+BJBVg0kgOhNo9ukO4CxULlaIi7VwZSr7I1km38kWSoU41Oq6os6qqIh5srnL+CcV+RFPFA==",
+      "license": "MIT",
+      "dependencies": {
+        "lodash-es": "^4.17.15",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/cdk": ">=19.0.0",
+        "@angular/common": ">=19.0.0",
+        "@angular/core": ">=19.0.0",
+        "@angular/platform-browser": ">=19.0.0",
+        "chart.js": "^3.4.0 || ^4.0.0",
+        "rxjs": "^6.5.3 || ^7.4.0"
+      }
+    },
+    "node_modules/parse5": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "entities": "^6.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "7.8.2",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+      "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+      "license": "Apache-2.0",
+      "peer": true,
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "license": "0BSD"
+    }
+  }
+}

+ 6 - 0
package.json

@@ -0,0 +1,6 @@
+{
+  "dependencies": {
+    "chart.js": "^4.5.0",
+    "ng2-charts": "^8.0.0"
+  }
+}