# react-antd-music
**Repository Path**: swantb/react-antd-music
## Basic Information
- **Project Name**: react-antd-music
- **Description**: 基于react的网易云音乐移动端
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2021-05-25
- **Last Updated**: 2021-11-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# react-项目
## 1-开发环境搭建
### 1.1-数据接口服务器
- 数据接口服务器基于`nodejs`平台
- 一定要保证网络畅通
- 数据接口服务器访问地址
```js
http://localhost:4000
```
#### 步骤
1. 安装项目依赖
```js
npm i
```
2. 启动项目
```js
npm start
```
### 2.1-前端开发环境的搭建
#### 步骤
1. 使用`create-react-app`脚手架初始化一个全新的项目`my-react-music`
```js
create-react-app my-react-music
```
2. 启动项目
```js
npm start
```
3. 修改默认监听端口
> 打开`package.json` 文件, 做如下修改
```js
"scripts": {
"start": "set PORT=5000 && node scripts/start.js"
}
```
#### 项目依赖项目
```js
npm i antd react-router-dom axios -S
```
## 2-准备工作
### 2.1-跨域请求配置
1. 安装插件`http-proxy-middleware`
```jsx
npm i http-proxy-middleware -D
```
2. 在项目根目录下的`src`目录中创建一个文件`setupProxy.js`, 配置如下
```jsx
// 导入跨域中间件
const proxy=require('http-proxy-middleware');
module.exports=function(app){
app.use(proxy.createProxyMiddleware('/api',{
target:'http://localhost:4000',
pathRewrite:{
'^/api':''
}
}));
}
```
### 2.2 axios的封装
> utils/http.js
```js
import React from 'react';
// 导入axios
import axios from 'axios';
// 注册响应拦截器
axios.interceptors.response.use(function(config){
return config.data;
});
// 设置baseURL
if(process.env.NODE_ENV==='development'){ //开发环境
var baseURL='/api';
}else if(process.env.NODE_ENV==='production'){ //生产环境
var baseURL='http://localhost:4000';
}
axios.defaults.baseURL=baseURL;
React.Component.prototype.$http=axios;
export default axios;
```
### 2.3-路由配置
> /router/routes.js
```jsx
import React from 'react';
// 定义路由规则数组
const Recommend=React.lazy(()=>import('../pages/Recommend'));
const Hot=React.lazy(()=>import('../pages/Hot'));
const Search=React.lazy(()=>import('../pages/Search'));
const SongList=React.lazy(()=>import('../pages/SongList'));
const Play=React.lazy(()=>import('../pages/Play'));
// 路由规则数组
const routes=[
// 路由重定向
{
path:'/',
to:'/recommend',
exact:true
},
{
path:'/recommend',
component:Recommend,
exact:false
},
{
path:'/hot',
component:Hot,
exact:false
},
{
path:'/search',
component:Search,
exact:false
},
// 动态路由
{
path:'/songlist/:id',
component:SongList,
exact:false
},
{
path:'/play/:id',
component:Play,
exact:false
}
];
// 导出路由规则数据
export default routes;
```
> /router/RouterView.jsx
```jsx
import { Switch,Route,Redirect } from 'react-router-dom';
// 函数组件: 在页面中通过循环遍历的方式创建Route路由规则组件
// routes: 路由规则数据
function RouterView(props){
return
{
props.routes.map((item,index)=>{
if(item.component){ // 普通路由规则
return
}else{ // 路由重定向
return
}
})
}
}
export default RouterView;
```
## 3-布局组件
> /pages/Layout.jsx
```jsx
import React, { Component,Suspense } from 'react'
import { PageHeader,Spin} from 'antd';
// 导入样式文件
import '../assets/css/layout.css';
// 导入路由
import { HashRouter as Router, NavLink } from 'react-router-dom';
// 导入路由
import routes from '../router/routes';
import RouterView from '../router/RouterView';
export default class Layout extends Component {
render() {
return (
{/* 页面头部 */}
{/* 导航栏 */}
推荐
热歌
搜索
{/* 路由出口 */}
}>
)
}
}
```
> assets/css/layout.css
```css
/* 页面头部 */
.site-page-header{
background-color: #ff4b00;
}
.ant-page-header-heading-title{
color:#FFF;
}
.header{
position: relative;
}
.btn-download{
position: absolute;
top:50%;
transform: translateY(-50%);
right: 10px;
background-color: #FFF;
border:none;
width: 100px;
line-height: 36px;
border-radius: 18px;
text-align: center;
outline: none;
font-weight: 700;
color:#ff4b00;
}
/* 导航栏 */
.navbar{
display: flex;
height: 60px;
line-height: 60px;
text-align: center;
}
.navbar a{
flex:1;
font-size: 16px;
font-weight: 700;
color:#666;
}
.navbar a.active{
border-bottom:2px solid #ff4b00;
}
/* 轮播图区域 */
.ant-carousel img{
height: 220px;
width: 100%;
}
.section{
padding:0 10px;
margin:10px 0;
}
.section h3{
font-size: 16px;
font-weight: 700;
color:#333;
padding-left:10px;
border-left:4px solid #ff4b00;
}
.ant-card-body{
padding:0;
}
.ant-card-body img{
width: 100%;
height:100%;
}
.ant-card-body p{
text-align: center;
font-size: 16px;
}
```
## 4-推荐
> pages/recommend.jsx
```jsx
import React, { Component } from 'react';
import { Carousel, List, Card } from 'antd';
import { PlayCircleOutlined } from '@ant-design/icons';
export default class Recommend extends Component {
constructor() {
super();
this.state = {
// 轮播图
banners: [],
// 歌单推荐列表
recommendList: [],
// 最新音乐
newSongs: []
}
}
render() {
return (
{/* 轮播图区域 */}
this.onChange(event)}>
{
this.state.banners.map(item => (
))
}
{/* 歌单推荐 */}
歌单推荐
(
this.props.history.push(`/songlist/${item.id}`)}>
{item.name.substr(0, 6)}...
)}
/>
{/* 最新音乐 */}
最新音乐
{/* */}
this.props.history.push(`/play/${item.id}`)}
actions={[]}
>{item.name}}
/>
)
}
// 轮播图相关事件处理
onChange(event) {
console.log(event);
}
componentWillMount() {
this.getBanner();
this.getRecommend();
this.getNewSong();
}
// 获取轮播图
async getBanner() {
const res = await this.$http.get('/api/banner');
if (res.code === 200) {
// 更新数据
this.setState({ banners: res.banners });
}
}
// 歌单推荐
async getRecommend() {
const res = await this.$http.get('/api/personalized');
if (res.code === 200) {
// 更新数据
this.setState({ recommendList: res.result });
}
}
// 最新音乐
async getNewSong() {
const res = await this.$http.get('/api/personalized/newsong');
if (res.code === 200) {
// 更新数据
this.setState({ newSongs: res.result });
}
}
// 组件即将卸载的时候, 自动执行
componentWillUnmount(){
// 对this.setState()方法的功能进行重写
this.setState=()=>false;
}
}
```
## 5-热歌
> pages/hot.jsx
```jsx
import React, { Component } from 'react';
import { List,Skeleton } from 'antd';
import { PlayCircleOutlined } from '@ant-design/icons';
// 导入样式
import '../assets/css/hot.css'
export default class Hot extends Component {
constructor(){
super();
this.state={
loading:true,
// 歌曲列表数组
songList:[1,2,3,4],
banner:''
}
}
render() {
const bannerStyle={
backgroundImage:`url(${this.state.banner})`
}
return (
{/* 歌单列表 */}
this.props.history.push(`/play/${item.id}`)}
actions={[]}
>{item.name}}
/>
)
}
componentWillMount(){
this.getHotList();
}
// 请求热歌列表
async getHotList(){
const res=await this.$http.get('/top/list?idx=1');
if(res.code===200){
// 更新数据
this.setState({songList:res.playlist.tracks,banner:res.playlist.coverImgUrl,loading:false});
}
}
// 组件即将卸载的时候, 自动执行
componentWillUnmount(){
// 对this.setState()方法的功能进行重写
this.setState=()=>false;
}
}
```
> assets/css/hot.css
```css
.hot-container .banner {
height: 220px;
width: 100%;
background-size: cover;
background-position: center center
}
```
## 6-搜索
> pages/search.jsx
```jsx
import React, { Component } from 'react';
import { Input, Button, List, Divider, message } from 'antd';
import { SearchOutlined, PlayCircleOutlined } from '@ant-design/icons';
// 导入样式文件
import '../assets/css/search.css';
export default class Search extends Component {
constructor() {
super();
this.state = {
// 歌曲列表
songList: [],
// 热门搜索列表
hotKeywords: [],
// 表单数据模型
keywords: ''
}
}
render() {
return (
this.submit(e)} onChange={(e) => this.inputUpdate(e)} size="large" placeholder="请输入搜索关键词" prefix={} />
{/* 搜索热词 */}
{
this.state.hotKeywords.map(item => (
))
}
{/* 搜索结果 */}
this.props.history.push(`/play/${item.id}`)}
actions={[]}
>{item.name}}
/>
)
}
componentWillMount() {
this.getHotKeywords();
}
// 监听用户输入
inputUpdate(event) {
const keywords = event.target.value;
// 更新数据模型
this.setState({ keywords });
}
// 提交表单
submit(event) {
if (event.keyCode === 13) {
// 发送数据请求
this.search();
}
}
// 热门搜索点击事件处理函数
clickHandle(keywords) {
// 1-更新搜索关键词
this.setState({ keywords }, () => {
// 2-进行搜索
this.search();
});
}
// 根据关键词进行搜索
async search() {
const keywords = this.state.keywords.trim();
if (keywords === '') {
return message.error('请输入搜索关键词');
}
const res = await this.$http.get('/search', { params: { keywords } });
if (res.code === 200) {
// 更新搜索结果
this.setState({ songList: res.result.songs });
}
}
// 获取热门搜索
async getHotKeywords() {
const res = await this.$http.get('/search/hot');
if (res.code === 200) {
// 更新数据
this.setState({ hotKeywords: res.result.hots });
}
}
// 组件即将卸载的时候, 自动执行
componentWillUnmount(){
// 对this.setState()方法的功能进行重写
this.setState=()=>false;
}
}
```
> assets/css/search.css
```css
.search-container .ant-input-affix-wrapper-lg{
border-radius: 20px;
}
.search-container .hot-keywords{
display: flex;
flex-wrap: wrap;
}
.search-container .hot-keywords button{
margin:3px 2px;
}
```
## 7-音乐播放
- 静态结构
> /pages/Play.jxs
```jsx
import React, { Component } from 'react'
import '../assets/css/play.css';
export default class Play extends Component {
constructor() {
super();
this.state = {
// 音乐播放状态: true显示播放按钮, false显示暂停按钮
status: true,
deg: '-15deg',
// 歌曲信息
info: {},
// 歌词
lyric:[],
// 音乐播放地址
playUrl:''
}
}
render() {
return (
{/* 音乐播放控件 */}
this.toggle()} className={['btn', this.state.status ? 'btn-play' : 'btn-pause'].join(' ')}>
{/* 歌词 */}
{this.state.info.name}
{
this.state.lyric.map((item,index)=>
{item}
)
}
)
}
componentWillMount() {
this.getDetail();
this.getLyric();
this.getPlayUrl();
}
// 切换音乐播放状态
toggle() {
// 1-切换按钮
const deg = this.state.status ? 0 : '-15deg';
this.setState({ status: !this.state.status, deg },()=>{
// 2-切换音乐播放
if(this.state.status){
this.music.pause();
}else{
this.music.play();
}
});
}
// 获取音乐详情
async getDetail() {
const ids = this.props.match.params.id;
const res = await this.$http.get('/song/detail', { params: { ids } });
if (res.code === 200) {
// console.log(res);
this.setState({ info: res.songs[0].al });
}
}
// 获取音乐播放地址
async getPlayUrl() {
const id = this.props.match.params.id;
const res = await this.$http.get('/song/url', { params: { id } });
if (res.code === 200) {
const playUrl=res.data[0].url;
this.setState({playUrl});
}
}
// 获取歌词
async getLyric() {
const id = this.props.match.params.id;
const res = await this.$http.get('/lyric', { params: { id } });
if (res.code === 200) {
// console.log(res);
// 处理歌词
const lyric=this.lyricFmt(res.lrc.lyric);
this.setState({lyric});
}
}
// 歌词格式化
lyricFmt(lyric){
const reg=/\[(.*)\](.*)/g;
const arr=[];
lyric.replace(reg,(a,b,c)=>{
arr.push(c);
});
return arr;
}
}
```
- 组件样式
> assets/css/play.css
```css
.play-container {
position: relative;
background-color: #7F7F7F;
overflow: hidden;
}
/* 音乐播放控件 */
.play-container .play-control{
position: absolute;
top:0;
left:45%;
width: 100px;
height: 160px;
background: url('../imgs/needle-ip6.png') no-repeat;
background-size: 100% 100%;
z-index:1;
/* 设置旋转中心 */
transform-origin: top left;
/* 添加过渡效果 */
transition: transform 2s;
/* transform: rotate(-15deg); */
}
/* 音乐播放区域 */
.play-container .play-box {
position: relative;
width: 300px;
height: 300px;
margin: auto;
margin-top:80px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background: url('../imgs/disc-ip6.png');
background-size: 100% 100%;
}
/* 歌曲封面图 */
.play-container .play-box img {
width: 200px;
height: 200px;
border-radius: 50%;
}
.play-container .play-box img.rotate {
/* animation: 动画名称 动画周期 动画函数 动画延时 执行此处 direction fill-mode; */
animation: rotate linear 12s infinite;
}
/* 音乐播放按钮 */
.play-container .btn {
position: absolute;
width: 40px;
height: 40px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: url('../imgs/list_sprite.png');
background-size: 40px;
}
.play-container .play-box .btn-play {
background-position: 0 0;
}
.play-container .play-box .btn-pause {
background-position: 0 -42px;
}
.play-container .content {
text-align: center;
}
.play-container .content h3 {
font-size: 18px;
font-weight: 700;
color: #FFF;
}
.play-container .content div {
color: #FFF;
}
/* 定义旋转动画 */
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
```
- 完整功能
> /pages/Play.jsx
```jsx
import React, { Component } from 'react'
import '../assets/css/play.css';
export default class Play extends Component {
constructor() {
super();
this.state = {
// 音乐播放状态: true显示播放按钮, false显示暂停按钮
status: true,
deg: '-15deg',
// 歌曲信息
info: {},
// 歌词
lyric:[],
// 音乐播放地址
playUrl:''
}
}
render() {
return (
{/* 音乐播放控件 */}
this.toggle()} className={['btn', this.state.status ? 'btn-play' : 'btn-pause'].join(' ')}>
{/* 歌词 */}
{this.state.info.name}
{
this.state.lyric.map((item,index)=>
{item}
)
}
)
}
componentWillMount() {
this.getDetail();
this.getLyric();
this.getPlayUrl();
}
// 切换音乐播放状态
toggle() {
// 1-切换按钮
const deg = this.state.status ? 0 : '-15deg';
this.setState({ status: !this.state.status, deg },()=>{
// 2-切换音乐播放
if(this.state.status){
this.music.pause();
}else{
this.music.play();
}
});
}
// 获取音乐详情
async getDetail() {
const ids = this.props.match.params.id;
const res = await this.$http.get('/api/song/detail', { params: { ids } });
if (res.code === 200) {
// console.log(res);
this.setState({ info: res.songs[0].al });
}
}
// 获取音乐播放地址
async getPlayUrl() {
const id = this.props.match.params.id;
const res = await this.$http.get('/api/song/url', { params: { id } });
if (res.code === 200) {
const playUrl=res.data[0].url;
this.setState({playUrl});
}
}
// 获取歌词
async getLyric() {
const id = this.props.match.params.id;
const res = await this.$http.get('/api/lyric', { params: { id } });
if (res.code === 200) {
// console.log(res);
// 处理歌词
const lyric=this.lyricFmt(res.lrc.lyric);
this.setState({lyric});
}
}
// 歌词格式化
lyricFmt(lyric){
const reg=/\[(.*)\](.*)/g;
const arr=[];
lyric.replace(reg,(a,b,c)=>{
arr.push(c);
});
return arr;
}
}
```
## 8-歌单详情
> pages/SongList.jsx
```jsx
import React, { Component } from 'react';
import { List,Skeleton } from 'antd';
import { PlayCircleOutlined } from '@ant-design/icons';
// 导入样式
import '../assets/css/hot.css'
export default class SongList extends Component {
constructor(){
super();
this.state={
loading:true,
// 歌曲列表数组
songList:[1,2,3,4],
banner:''
}
}
render() {
const bannerStyle={
backgroundImage:`url(${this.state.banner})`
}
return (
{/* 歌单列表 */}
]}
>{item.name}}
/>
)
}
componentWillMount(){
this.getList();
}
// 请求热歌列表
async getList(){
const id=this.props.match.params.id;
const res=await this.$http.get('/playlist/detail?id='+id);
if(res.code===200){
// 更新数据
this.setState({songList:res.playlist.tracks,banner:res.playlist.coverImgUrl,loading:false});
}
}
// 组件即将卸载的时候, 自动执行
componentWillUnmount(){
// 对this.setState()方法的功能进行重写
this.setState=()=>false;
}
}
```
## 9-scss
> scss动态样式语言
### creat-react-app中启用scss
1. 安装node-sass编译工具
```jsx
npm i node-sass@4.14.0 -S
```
2. 重启开发服务器
```jsx
npm start
```
### 9-1变量定义
```scss
$baseColor:red;
.box{
color:$baseColor;
}
```
### 9.2-嵌套语法
```scss
.container{
.box{
h1{
}
}
}
```
### 9.3-引用父级选择器
```scss
a{
&:hover{
}
}
```
### 9.4-注释符
```scss
// 这是一段scss注释
```
### 9.5-导入语法
```scss
@import 'scss文件路径';
```
## 10-yarn包管理工具
### 安装
1. 去官网下载yarn的安装包
2. 通过npm在线安装
```jsx
npm i yarn -g
```
### 10.1-初始化项目
```jsx
yarn init -y
```
### 10.2-安装依赖
```jsx
// 默认保存为生产依赖
yarn add 依赖名
// 如果需要保存为开发依赖
yarn add 依赖名称 --dev
// 或者
yarn add 依赖名称 -D
```
### 10.3-安装所有依赖
```jsx
yarn
```
### 10.4-切换下载镜像
```jsx
// yarn config set registry 下载地址
yarn config set registry http://registry.npm.taobao.org
```