# 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 ```