入驻歌手

封装服务

  • 首先定义请求返回的入驻歌手数据格式
1
2
3
4
5
6
7
// 定义入驻歌手列表数据
export type Singer = {
id: number;
name: string;
picUrl: string;
albumSize: number
}
  • service文件夹下新建singer.service.ts,用于请求歌手信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { HttpClient, HttpParams } from "@angular/common/http";
import { Inject, Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { Singer } from "./data-types/common.types";
import { API_CONFIG, ServicesModule } from "./services.module";
import { map } from "rxjs/internal/operators";

type SingerParams = {
offset: number; // 分页索引
limit: number; //每页多少条
cat?: string;
};
// 默认参数
const defaultParams: SingerParams = {
offset: 0,
limit: 9,
cat: "5001",
};

@Injectable({
providedIn: ServicesModule,
})
export class SingerService {
constructor(
private http: HttpClient,
@Inject(API_CONFIG) private uri: string
) {}

// 获取入驻歌手信息
getEnterSinger(args: SingerParams = defaultParams): Observable<Singer[]> {
// 传参数
const str = `offset=${args.offset}&limit=${args.limit}&cat=${args.cat}`;
const params = new HttpParams({
// 序列化:args是对象需要转为字符串
fromString: str,
});
return this.http
.get(this.uri + "artist/list", { params })
.pipe(map((res: { artists: Singer[] }) => res.artists));
}
}
  • 然后在home.component.ts中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Component, OnInit, ViewChild } from "@angular/core";
import {Singer} from "src/app/services/data-types/common.types";
import { SingerService } from "src/app/services/singer.service";

@Component({
selector: "app-home",
templateUrl: "./home.component.html",
styleUrls: ["./home.component.less"],
})
export class HomeComponent implements OnInit {
// 前端遍历即可
singers : Singer[]

// 依赖注入服务
constructor(private singerService: SingerService) {
// 获取数据
this.getEnterSinger();
}
// 获取入驻歌手信息
private getEnterSinger(){
this.singerService.getEnterSinger().subscribe((singer=>{
this.singers = singer
console.log('singer',this.singers)
}))
}
}

封装组件

  • 把登陆界面封装成一个组件
1
ng g c pages/home/components/member-card
1
2
3
4
5
6
<div class="member">
<div class="login">
<p>登陆网易云音乐,可以享受无限收藏的乐趣,并且无限同步到手机</p>
<button nz-button class="btn">用户登陆</button>
</div>
</div>
  • 然后在home.component.html中使用即可

Resolve守卫

  • Resolve守卫的作用就是写一个类实现Resolve,然后实现相应的方法,在该方法中,提前把数据请求等工作做好,然后在路由跳转(如跳转到A组件)的时候把这些数据传过去,这样A组件中要展示的数据就会及时展示出来
  • 如果没有Resolve守卫这种机制的话,就只能在A组件显示再从服务器请求数据,则会导致页面展示不及时的问题
  • home文件夹下新建home-resolve.service.ts,输入获取数据的服务,在这个文件里把所有数据获取到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { Injectable } from "@angular/core";
import { Resolve } from "@angular/router";
import { forkJoin, Observable } from "rxjs";
import { first } from "rxjs/internal/operators";
import { Banner, HotTag, Singer, SongSheet } from "src/app/services/data-types/common.types";
import { HomeService } from "src/app/services/home.service";
import { SingerService } from "src/app/services/singer.service";

type HomeDataType = [Banner[],HotTag[],SongSheet[],Singer[]]

@Injectable({
providedIn: "root",
})
export class HomeResolverService implements Resolve<HomeDataType> {
constructor(
private homeService: HomeService,
private singerService: SingerService
) {}
resolve(): Observable<HomeDataType> {
// 会对每一个流完全完成后,把最新值返回
return forkJoin([
this.homeService.getBanners(),
this.homeService.getHotTags(),
this.homeService.getPersonalSheetList(),
this.singerService.getEnterSinger()
]).pipe(first())
}
}
  • 然后在home-routing.module.ts中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { HomeResolverService } from "./home-resolve.service";
import { HomeComponent } from "./home.component";

const routes: Routes = [
{
path: "home",
component: HomeComponent,
data: {
title: "发现",
},
resolve: {
homeDatas: HomeResolverService, // 使用,homeDatas包含了请求的所有数据
},
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [HomeResolverService], // 提供
})
export class HomeRoutingModule {}
  • 然后home.component.ts中就可以简化,不需要服务请求就得到数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { Component, OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { map } from "rxjs/internal/operators";
import {
Banner,
HotTag,
Singer,
SongSheet,
} from "src/app/services/data-types/common.types";

@Component({
selector: "app-home",
templateUrl: "./home.component.html",
styleUrls: ["./home.component.less"],
})
export class HomeComponent implements OnInit {

banners: Banner[];
hotTags: HotTag[];
songSheetList: SongSheet[];
singers: Singer[];

// 轮播图组件实例
@ViewChild(NzCarouselComponent, { static: true })
private nzCarousel: NzCarouselComponent;

// 依赖注入服务
constructor(private route: ActivatedRoute) {
this.route.data
.pipe(map((res) => res.homeDatas))
.subscribe(([banners, tags, sheets, singer]) => {
this.banners = banners;
this.hotTags = tags;
this.songSheetList = sheets;
this.singers = singer;
});
}

ngOnInit() {}
}

歌单详情

封装服务

  • 根据歌单ID获取其包含的每个歌曲URL,首先定义数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 歌曲
export type Song = {
id: number;
name: string;
url: string;
ar: Singer[];
al: {
id: number;
name: string;
picUrl : string
};
dt:number
}
// 歌单
export type SongSheet = {
id: number;
name: string;
picUrl: string;
playCount: number;
tracks: Song[]
}
  • service文件夹下新建sheet.service.ts封装服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { HttpClient, HttpParams } from "@angular/common/http";
import { Inject, Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { map } from "rxjs/internal/operators";
import { SongSheet } from "./data-types/common.types";
import { API_CONFIG, ServicesModule } from "./services.module";

@Injectable({
providedIn: ServicesModule,
})
export class SheetService {
constructor(
private http: HttpClient,
@Inject(API_CONFIG) private uri: string
) {}

// 获取歌单详情
getSongSheetDetail(id: number): Observable<SongSheet> {
const params = new HttpParams().set("id", id.toString());
return this.http
.get(this.uri + "playlist/detail", { params })
.pipe(map((res: { playlist: SongSheet }) => res.playlist));
}
}
  • 歌单是一个小组件,点击播放按钮,把歌单ID传出去
1
<i class="icon play" (click)="playSheet(sheet.id)"></i>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { SongSheet } from 'src/app/services/data-types/common.types';

@Component({
selector: 'app-single-sheet',
templateUrl: './single-sheet.component.html',
styleUrls: ['./single-sheet.component.less'],
// 简单组件都推荐改变一下显示策略
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SingleSheetComponent implements OnInit {
@Input() sheet:SongSheet
// 声明事件
@Output() onPlay = new EventEmitter<number>();
constructor() { }

ngOnInit() {
}
// 把id发送出去
playSheet(id: number){
this.onPlay.emit(id)
}
}
  • 然后在父组件声明接收并触发
1
2
<!--使用歌单组件,传数据进去-->
<app-single-sheet (onPlay)="onPlaySheet($event)"></app-single-sheet>`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component,  OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute } from "@angular/router";

@Component({
selector: "app-home",
templateUrl: "./home.component.html",
styleUrls: ["./home.component.less"],
})
export class HomeComponent implements OnInit {

// 依赖注入服务
constructor() {}

// 播放歌单的事件
onPlaySheet(id: number) {
this.sheetService.playSheet(id).subscribe((res) => {
console.log(res);
});
}
}

处理数据

  • 每个表单中有多首歌曲,需要获取它们的播放地址,首先定义数据类型
1
2
3
4
5
// 歌曲Url
export type SongUrl = {
id: number;
url: string;
}
  • 修改sheet.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { HttpClient, HttpParams } from "@angular/common/http";
import { Inject, Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { map, pluck, switchMap } from "rxjs/internal/operators";
import { Song, SongSheet } from "./data-types/common.types";
import { API_CONFIG, ServicesModule } from "./services.module";
import { SongService } from "./song.service";

@Injectable({
providedIn: ServicesModule,
})
export class SheetService {
constructor(
private http: HttpClient,
private songService: SongService,
@Inject(API_CONFIG) private uri: string
) {}

// 获取歌单详情
getSongSheetDetail(id: number): Observable<SongSheet> {
const params = new HttpParams().set("id", id.toString());
return this.http
.get(this.uri + "playlist/detail", { params })
.pipe(map((res: { playlist: SongSheet }) => res.playlist));
}

// 获取tracks
playSheet(id: number): Observable<Song[]> {
return (
this.getSongSheetDetail(id)
// 筛选
.pipe(
pluck("tracks"),
switchMap((tracks) => this.songService.getSongList(tracks))
)
);
}
}
  • 然后封装服务,新建song.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { HttpClient, HttpParams } from "@angular/common/http";
import { Inject, Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { map } from "rxjs/internal/operators";
import { Song, SongSheet, SongUrl } from "./data-types/common.types";
import { API_CONFIG, ServicesModule } from "./services.module";

@Injectable({
providedIn: ServicesModule,
})
export class SongService {
constructor(
private http: HttpClient,
@Inject(API_CONFIG) private uri: string
) {}

// 获取歌曲Url
getSongUrl(ids: string): Observable<SongUrl[]> {
const params = new HttpParams().set("id", ids);
return this.http
.get(this.uri + "song/url", { params })
.pipe(map((res: { data: SongUrl[] }) => res.data));
}

getSongList(songs: Song | Song[]): Observable<Song[]> {
// 转为数组形式
const songArr = Array.isArray(songs) ? songs.slice() : [songs];
const ids = songArr.map((item) => item.id).join(",");
return Observable.create((observer) => {
this.getSongUrl(ids).subscribe((urls) => {
observer.next(this.generateSongList(songArr, urls));
});
});
}
// 拼接转变形式
generateSongList(songs: Song[], urls: SongUrl[]): Song[] {
const result = [];
songs.forEach((song) => {
const url = urls.find((url) => url.id === song.id).url;
if (url) {
result.push({ ...song, url });
}
});
return result;
}
}

底部播放器

创建模块

  • 播放器也是一个公共的组件,可以创建一个模块
1
2
ng g m share/wy-ui/wy-player
ng g c share/wy-ui/wy-player
  • 然后在wy-player.module.ts模块中需要导出去
1
2
3
4
5
6
7
8
9
10
11
12
import { NgModule } from '@angular/core';
import { WyPlayerComponent } from './wy-player.component';

@NgModule({
declarations: [WyPlayerComponent],
imports: [],
// 导出模块
exports: [
WyPlayerComponent
]
})
export class WyPlayerModule { }
  • 然后在wy-ui.module.ts主模块中导入导出
1
2
3
4
5
6
7
8
9
10
11
import { NgModule } from "@angular/core";
import { SingleSheetComponent } from "./single-sheet/single-sheet.component";
import { PlayCountPipe } from "../play-count.pipe";
import { WyPlayerModule } from "./wy-player/wy-player.module";

@NgModule({
declarations: [SingleSheetComponent, PlayCountPipe],
imports: [WyPlayerModule],
exports: [SingleSheetComponent, PlayCountPipe, WyPlayerModule],
})
export class WyUiModule {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<div class="m-player">
<div class="lock">
<div class="left"><i></i></div>
</div>
<div class="head"></div>
<div class="container">
<div class="wrap">
<div class="btns">
<i class="prev"></i>
<i class="toggle"></i>
<i class="next"></i>
</div>
<div class="head">
<img src="" alt="">
<i class="mask"></i>
</div>
<div class="play">
<div class="words clearfix">
<p class="ellipsis">歌名</p>
<ul class="songs clearfix">
<li>
<a>歌手1</a>/
</li>
<li>
<a>歌手1</a>
</li>
</ul>
</div>
<div class="bar">
<div class="slider-wrap">
<div>进度条</div>
</div>
<span class="time">
<em>02:11</em> / 04:35
</span>
</div>
</div>
<div class="oper">
<i class="like" title="收藏"></i>
<i class="share" title="分享"></i>
</div>
<div class="ctrl">
<i class="volume" title="音量"></i>
<i class="loop" title="循环"></i>
<p class="open">
<span></span>
</p>
</div>
</div>
</div>
</div>