一、项目初始化与环境准备

Article detail

前端

2026/4/20 · 44 分钟阅读

一、项目初始化与环境准备

本文档描述基于 Angular 构建的流媒体服务落地页(Landing Page)的前端代码结构与功能,包括共享模块、路由配置、组件实现等。所有代码均保持原样,仅添加解释性说明。


//

一、项目初始化与环境准备

在开发此项目前,通常需要全局安装 Angular CLI 并创建项目,同时可选添加服务端渲染(SSR)支持。以下是你已执行的命令及其作用:

npm install -g @angular/cli
  • 作用:全局安装 Angular 命令行工具,便于创建、开发和构建 Angular 项目。
ng add @angular/ssr
  • 作用:为现有 Angular 项目添加服务端渲染(Server-Side Rendering)支持,提升首屏加载速度和 SEO 表现。在选择配置时,你选择了 nocss,即不生成服务端路由文件,并采用纯 CSS 样式(未使用 Sass/Less)。

二、共享模块(SharedModule)

2.1 文件:shared-module.ts

路径src/app/shared/shared-module.ts
功能:集中管理、导入并导出项目中常用的 Angular Material 模块、表单模块以及路由模块,避免在多个特性模块中重复导入,提高代码复用性和维护性。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

// Angular Material 组件模块导入
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatMenuModule } from '@angular/material/menu';
import { MatDividerModule } from '@angular/material/divider';
import { MatCardModule }   from '@angular/material/card';
import { MatSelectModule } from '@angular/material/select';
import { MatChipsModule }  from '@angular/material/chips';
import { MatTableModule }  from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule }   from '@angular/material/sort';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatExpansionModule } from '@angular/material/expansion';
import { RouterModule } from '@angular/router';

// 将需要共享的模块集中在一个数组中
const MATERIAL_MODULES = [
  MatIconModule,
  MatButtonModule,
  MatFormFieldModule,
  MatInputModule,
  MatProgressBarModule,
  MatProgressSpinnerModule,
  MatTooltipModule,
  MatMenuModule,
  MatDividerModule,
  MatCardModule,
  MatSelectModule,
  MatChipsModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatDialogModule,
  MatSnackBarModule,
  MatSlideToggleModule,
  MatSliderModule,
  MatToolbarModule,
  MatExpansionModule
];

@NgModule({
  declarations: [],
  // 导入:CommonModule、RouterModule、表单模块(模板驱动 + 响应式)以及所有 Material 模块
  imports: [RouterModule, FormsModule, ReactiveFormsModule, CommonModule, ...MATERIAL_MODULES],
  // 导出:使任何导入本模块的模块都能直接使用这些模块
  exports: [RouterModule, FormsModule, ReactiveFormsModule, CommonModule, ...MATERIAL_MODULES]
})
export class SharedModule {}

⚠️ 编译错误提示:当前代码缺少对 FormsModuleReactiveFormsModule 的导入语句。你需要在文件顶部添加:

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

否则 TypeScript 编译器会报错 Cannot find name 'FormsModule'


三、根模块与路由配置

3.1 文件:app-module.ts

路径src/app/app-module.ts
功能:Angular 应用的根模块,负责引导启动、声明根组件和可路由组件、导入核心模块以及配置全局提供者。

import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing-module';
import { App } from './app';
import { Landing } from './landing/landing';
import { SharedModule } from './shared/shared-module';
import { Signup } from './signup/signup';
import { provideHttpClient } from '@angular/common/http';

@NgModule({
  // 声明本模块拥有的组件/指令/管道
  declarations: [App, Landing, Signup],
  // 导入其他模块
  imports: [BrowserModule, AppRoutingModule, SharedModule],
  // 全局提供者:错误监听器、HTTP 客户端
  providers: [provideBrowserGlobalErrorListeners(), provideHttpClient()],
  // 启动时引导的根组件
  bootstrap: [App],
})
export class AppModule {}
  • provideHttpClient():提供 Angular 的 HTTP 客户端服务,用于调用后端 API。
  • provideBrowserGlobalErrorListeners():提供全局错误监听,便于捕获未处理的异常。

3.2 文件:app-routing-module.ts

路径src/app/app-routing-module.ts
功能:定义应用级路由规则,将 URL 路径映射到对应的组件。

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Landing } from './landing/landing';
import { Signup } from './signup/signup';

const routes: Routes = [
  { path: '', component: Landing },           // 根路径显示落地页
  { path: 'signup', component: Signup },      // 注册页
  // { path: 'login', component: Login },      // 登录页(尚未实现)
  { path: '**', redirectTo: '', pathMatch: 'full' }, // 通配符重定向至首页
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
  • 通配符路由 ** 用于处理用户访问不存在的路径,自动跳转回首页。
  • login 路由被注释,表明登录页面尚未开发。

四、落地页组件(Landing)

落地页是用户进入应用的第一屏,包含品牌展示、邮箱收集、特色功能介绍、FAQ 等模块。

4.1 组件类文件:landing.ts

路径src/app/landing/landing.ts
功能:处理落地页的交互逻辑,包括表单验证、导航跳转、动态数据的定义等。

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';

@Component({
  selector: 'app-landing',
  standalone: false,
  templateUrl: './landing.html',
  styleUrl: './landing.css',
})
export class Landing {
  landingForm!: FormGroup;          // 邮箱输入表单组
  year = new Date().getFullYear();  // 当前年份,用于页脚版权信息

  constructor(
    private fb: FormBuilder,
    private router: Router
  ) {
    // 初始化响应式表单,包含 email 字段及验证规则
    this.landingForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]]
    });
  }

  // 跳转到登录页(暂未实现路由,实际点击会尝试导航到 '/login')
  login() {
    this.router.navigate(['/login']);
  }

  // 获取邮箱并跳转到注册页,将邮箱作为查询参数传递
  getStarted() {
    const email = this.landingForm.get('email')?.value?.toString().trim() || '';
    console.log('getStarted called, email=', email);
    if (!email) {
      console.warn('getStarted aborted: empty email');
      return;
    }
    this.router.navigate(['/signup'], { queryParams: { email } })
      .then(ok => console.log('router.navigate result:', ok))
      .catch(err => console.error('router.navigate error:', err));
  }

  // “为什么选择 Pudplanet” 的理由列表,用于在模板中循环渲染
  reasons = [
    { title: 'Enjoy on your TV.', text: 'Watch on Smart TVs, PlayStation, Xbox, Chromecast, Apple TV and more.', icon: 'tv' },
    { title: 'Watch anywhere', text: 'Stream on your phone, tablet or laptop — downloads available for offline viewing.', icon: 'devices' },
    { title: 'Personalized recommendations', text: 'Get suggestions based on what you watch to discover more you’ll love.', icon: 'recommend' },
    { title: 'Multiple profiles', text: 'Create profiles for family members with separate recommendations and settings.', icon: 'people' },
    { title: 'Ultra HD & HDR', text: 'Select titles available in 4K UHD and HDR for brilliant detail.', icon: '4k' },
    { title: 'Download & Go', text: 'Save shows to watch offline while commuting or traveling.', icon: 'download' },
    { title: 'Create watchlists', text: 'Save favorites to your list and pick up where you left off.', icon: 'playlist' },
    { title: 'Cancel anytime', text: 'No long-term commitment — pause or cancel your membership whenever you want.', icon: 'cancel' },
  ];

  // FAQ 数据列表
  faqs = [
    { question: 'What is Pudplanet?', answer: 'Pudplanet is a streaming service offering movies, TV shows, anime, documentaries and more across internet-connected devices. Enjoy unlimited ad‑free streaming for one monthly price.' },
    { question: 'How much does it cost?', answer: 'We offer several plans to fit different needs. Prices vary by region and plan (SD, HD, UHD). See the pricing page for current rates and available features.' },
    { question: 'Which devices are supported?', answer: 'Pudplanet works on Smart TVs, streaming players (Roku, Apple TV, Chromecast), game consoles, phones, tablets and web browsers.' },
    { question: 'Can I download shows to watch offline?', answer: 'Yes. Many titles are available for download on mobile apps so you can watch without an internet connection.' },
    { question: 'How do I cancel?', answer: 'You can cancel anytime from your account settings. Your subscription will remain active until the end of the current billing period.' },
    { question: 'Are there parental controls?', answer: 'Yes. You can create profiles and enable parental controls to restrict content by maturity rating.' },
    { question: 'What payment methods are accepted?', answer: 'We accept major credit/debit cards and selected local payment methods depending on your country.' },
  ];
}

4.2 模板文件:landing.html

路径src/app/landing/landing.html
功能:定义落地页的 DOM 结构,绑定组件数据与事件。

主要区域包括:

  • 背景层:全屏半透明黑色遮罩叠加背景图。
  • 顶部导航栏:品牌 Logo 和“Sign In”按钮。
  • 英雄区:主标题、副标题、邮箱输入表单及“Get Started”按钮。
  • 特色理由网格:通过 *ngFor 循环渲染 reasons 数组,展示功能亮点。
  • FAQ 手风琴:使用 Angular Material 的 mat-expansion-panel 展示常见问题,自定义展开/折叠图标。
  • 页脚:版权信息、使用条款、隐私政策、帮助中心链接。

详细代码见你提供的 landing.html 文件。

4.3 样式文件:landing.css

路径src/app/landing/landing.css
功能:为落地页提供深色主题的视觉样式,包括背景图叠加、玻璃态卡片、响应式布局、滚动条美化等。

  • 背景图片路径为 /landing.jpg,实际文件应放置于 public/ 目录下。
  • 使用了 CSS 自定义属性(--bg--card 等)定义主题色。
  • 通过 clamp() 函数实现字体和间距的流畅缩放。
  • 包含对 Material 组件内部样式的深度覆盖(如 ::ng-deep)。

💡 注意:由于 Angular 视图封装机制,需要使用 ::ng-deep 穿透组件样式以修改 Material 表单字段的边框颜色等。


五、项目运行与访问

  1. 启动开发服务器:在项目根目录执行 ng serve,默认访问 http://localhost:4200
  2. 页面路由
    • /:落地页(Landing)
    • /signup:注册页(需自行实现 Signup 组件)
    • /login:尚未配置路由,点击“Sign In”会尝试跳转但无法匹配。

六、注意事项与已知问题

问题说明
FormsModuleReactiveFormsModule 未导入shared-module.ts 中使用了这两个模块但未在文件顶部导入,导致 TypeScript 编译错误。需手动添加导入语句。
登录路由未实现Landing.login() 导航到 /login,但 AppRoutingModule 中该路由被注释,实际访问会重定向到首页。
背景图片路径CSS 中使用了 url('/landing.jpg'),请确保图片文件存在于 public/ 目录下,否则背景图不显示。
Material 图标依赖模板中使用了 <mat-icon>,需确保已在 index.html 中引入 Material Icons 字体库。

评论

动作测试