Foreword: worked hard half a month, finally my netease cloud sound hot to line up. Without saying a word, go directly to the link. Thanks to Gao for helping to configure Nginx, thanks to Binaryify for the netease interface support, if you think the project is good, please give a star!
Gitee source address Hooks project address
First look at the implemented functionality
- The homepage UI
- Song Details Page
- Playlist details page
- Album Details Page
- Details page for playing and adding playlists
- List of Music Players
- Song single cycle
Here is some core code
react-router-dom
The nested routine in
// Level 1 routing
import React from "react";
import { Route, BrowserRouter as Router, Switch, withRouter, Redirect } from "react-router-dom";
import Container from ".. /Content/MainContent";
import Discover from ".. /Content/Discover";
import Mine from ".. /Content/Mine";
import Friend from ".. /Content/Friend";
import Shop from ".. /Content/Shop";
import Musician from ".. /Content/Musician";
import Download from ".. /Content/Download";
import NotFound from ".. /Content/NotFound";
import SongDetail from '.. /Content/Discover/SongDetail'
import DiscoverRoutes from ".. /Routes/discoverRouter";
export default withRouter(function Routes(props) {
return (
// <Router>
<Switch>
<Route
exact
path="/"
render={()= > (
<Discover>
<DiscoverRoutes />
</Discover>
)}
></Route>
<Route
path="/discover"
render={()= > (
<Discover>
<DiscoverRoutes />
</Discover>
)}
></Route>
<Route path="/mine" component={Mine} />
<Route path="/friend" component={Friend} />
<Route path="/shop" component={Shop} />
<Route path="/musician" component={Musician} />
<Route path="/download" component={Download} />
<Route path="/ 404" component={NotFound} />
<Route exact path="/song/:id/:type" component={SongDetail}></Route>
<Redirect from="*" to="/ 404"></Redirect>// The last redirection operation. No other routes can be inserted after it</Switch>
// </Router>
);
})
// Secondary route
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Recommend from '.. /Content/Discover/Recommend'
import Rank from '.. /Content/Discover/Rank'
import Musiclist from '.. /Content/Discover/Musiclist'
import DiscoverDj from '.. /Content/Discover/DiscoverDj'
import DiscoverMusician from '.. /Content/Discover/DiscoverMusician'
import Newsong from '.. /Content/Discover/Newsong'
const RankTest = ({}) = > {
return <div>RankTest{Date.now()}</div>
}
const DisCoverRouter = () = > {
return <Switch>
<Route exact path="/" component={Recommend}></Route>
<Route exact path="/discover" component={Recommend}></Route>
<Route path="/discover/recommend" component={Recommend}></Route>
<Route path="/discover/rank" component={Rank}>
<Route path="/discover/rank" component={Rank}></Route>
<Route path="/discover/rank/one" component={RankTest}></Route>
<Route path="/discover/rank/two" component={RankTest}></Route>
<Route path="/three" component={RankTest}></Route>
</Route>
<Route path="/discover/musiclist" component={Musiclist}></Route>
<Route path="/discover/discoverdj" component={DiscoverDj}></Route>
<Route path="/discover/discovermusician" component={DiscoverMusician}></Route>
<Route path="/discover/newsong" component={Newsong}></Route>
</Switch>
}
export default DisCoverRouter
Copy the code
redux
Some of the core code
// Click add Playlist action
export const addSongListAction = (data) = > {
console.log(data, 'data');
return async dispatch => {
const res = await songDetailwithIdsReq({ids: data.ids})
console.log(res, ' rdata');
if (res.data.code === 200) {
let data = res.data.songs
dispatch({type: ADD_SONGLIST, payload: data})
}
}
}
// Add playlists and play songs
export const playSongAction = (id, type = 2, songArr = []) = > {
return dispatch= > {
// Accept an IDS: String adds the last song to the playlist
if (type == 1) {
// Play all of them
let idarr = []
songArr.forEach(item= > {
idarr.push(item.id)
});
let ids = idarr.join(', ')
console.log(ids, 'ids')
dispatch(addSongListAction({ids}));
let id = ' '
if (ids && ids.length) {
let idarr = ids.split(', ')
id = idarr[idarr.length - 1]}else{
id = ids
}
console.log(id, 'id')
dispatch(setCurrentSongDetail(id));
dispatch(addBrothernodechangetime())
}else{
dispatch(addSongListAction({ids: id}));
dispatch(setCurrentSongDetail(id));
dispatch(addBrothernodechangetime())
}
}
}
// Add a playlist
export const addSongAction = (ids, type = 2, songArr = []) = > {
return dispatch= > {
// Receive IDS: String adds to the playlist directly
if (type == 1) {
let idarr = []
songArr.forEach(item= > {
idarr.push(item.id)
});
let idstr = idarr.join(', ')
dispatch(addSongListAction({ids: idstr}));
}else{
dispatch(addSongListAction({ids: ids})); }}}// reducer.js
const initDoinginfo = {
currentSongMp3: {},
currentSongDetail: {},
currentSongsList: [].err_msg: null.sendlrcDetail: {},
currenttime: 0.isPlaying: false.//true means playing (playing)
lrcArrTotal: {},
mp3List: [].onEnd: false.brotherNodeChangeTime: 1
}
export const currentDoingInfo = (state = initDoinginfo, action) = > {
switch (action.type) {
case SEND_LRC:
return{... state,sendlrcDetail: {... action.payload}}case SET_CURRENTSONG_STATUS:
console.log(action.payload, 'SET_CURRENTSONG_STATUS');
return{... state,isPlaying: action.payload}
case ADD_SONGLIST:
let cList = [...state.currentSongsList]
let pList = [...action.payload]
cList.forEach((item1,index) = > {
pList.forEach(item2= > {
if (item1.id === item2.id) {
cList.splice(index,1)
console.log('Added playlist song item2 =', item2.name); }})});let currentSongsList = [...cList, ...pList]
return{... state, currentSongsList} } }Copy the code
Three detail pages (song Details, Lyrics details, Playlist details)
I’m too lazy to write a comment, so I just posted a little code here. All code if what do not understand you can always private message me.
import React, { memo, useCallback, useRef, forwardRef, useMemo } from "react";
import MinAndMax from "@/components/MinAndMax";
import { Link, withRouter } from "react-router-dom";
import {
handleDataLrc,
addSongAction
} from "@/redux/actions";
import { songDetailwithIdsReq, getLrcWithIdReq } from "@/utils/api/user";
import {
albumReq,
commentPlaylistReq,
pubCommentReq
} from "@/utils/api/album";
import { getTime } from "@/utils/date";
import style from "./style.css";
import ZjDetail from './GezjDetail'
function SongDetail(props) {
const {
match,
addSong
} = props;
const [songDetail, setSongDetail] = useState(null);
const commentRef = useRef(null);
useEffect(() = > {
if (currentType == 2 && album) {
setAuthorid(album.artist.id);
} else if (currentType == 1 && songDetail) {
setAuthorid(songDetail.ar[0].id);
} else if (currentType == 3) {
// setAuthorid('match.params.id')
}
}, [history.location.pathname]);
useEffect(() = > {
console.log(currentType, ".params.type");
let getData;
if (currentType == 1) {
console.log(currentType, "111");
let data = { id: match.params.id };
getData = async() = > {const res = await songDetailwithIdsReq({ ids: match.params.id });
if (res.data.code === 200) {
setSongDetail(res.data.songs[0]);
}
const resTwo = await getLrcWithIdReq(data);
if (resTwo.data.code === 200) {
let lrcArr = handleDataLrc(false, resTwo.data.lrc.lyric);
setLrcData(lrcArr);
}
const resthree = await commentMusicReq(data);
if (resthree.data.code === 200) { setComments(resthree.data.comments); setHotcomments(resthree.data.hotComments); setTotal(resthree.data.total); }}; setSongType(0)
}
getData();
}, [history.location.pathname, authorid]);
const noOpenup = useCallback(() = > {
alert('Not open yet! ')}, [])const scrollComment = useCallback(() = > {
let height = commentRef.current.offsetTop
document.documentElement.scrollTop = height
})
if (currentType == 2 && !album) {
return <div>Loading...</div>;
}
if (currentType == 1 && !songDetail) {
return <div>Loading...</div>;
}
return (
<>
<TopRedBorder />
<MinAndMax>
</MinAndMax>
</>
);
}
const mState = (state) = > {
return {};
};
const mDispatch = (dispatch) = > {
return {
playSong: (. arg) = >{ dispatch(playSongAction(... arg)) },addSong: (. arg) = >{ dispatch(addSongAction(... arg)) } }; };export default withRouter(connect(mState, mDispatch)(SongDetail));
Copy the code
How do I avoid exposing all configurations using YARN eject
Refer to the address
-
Use two packages ()
yarn add -D customize-cra react-app-rewired
-
Replace the script of package.json
"scripts": { "start": "react-app-rewired start"."build": "react-app-rewired build"."test": "react-app-rewired test"."_eject": "react-scripts eject" }, Copy the code
-
Config-overrides. Js: config-overrides. Js: config-overrides
const { override, addWebpackAlias } = require('customize-cra') const path = require('path') function resolve(dir) { return path.join(__dirname, dir) } module.exports = { webpack: override( addWebpackAlias({ '@': resolve('src') }) } Copy the code
-
Restarting the running project (YARN start)
player
The player code here is only a part, please do not copy and paste, specific code to the project source code.
// player.jsx
import React, {
memo,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { connect } from "react-redux";
import { withSoundCloudAudio } from "react-soundplayer/addons";
import {
PlayButton,
Progress,
VolumeControl,
PrevButton,
NextButton,
} from "react-soundplayer/components";
import MinAndMax from ".. /MinAndMax";
import style from "./index.css";
import PlayerListTop from "./PlayerListTop";
import { setCurrentTime, setCurrentSongDetail } from '@/redux/actions'
const stylePass = {
position: "fixed".bottom: "0".width: "100%".borderTop: `1px solid #ececec`};const Player = memo((props) = > {
const { soundCloudAudio, playing } = props;
const playbtnRef = useRef(null)
const getTimeWithMh = useCallback(
(duration) = > {
let minOne = parseInt(duration / 60);
let secondOne = (duration / 60 - parseInt(minOne)).toFixed(2);
secondOne = parseInt(secondOne * 60);
if (secondOne / 10 < 1) {
secondOne = "0" + secondOne;
}
return `${minOne}:${secondOne}`;
},
[duration, currentTime]
);
return (
<MinAndMax bgc="#2e2d2d" stylePass={stylePass}>
<div className={[style.content, style.playerContaniner].join(" ")} >
<div className={style.btngroup}>
<img
src={require("./Icon/last.svg").default}
alt="svg"
className={style.iconNormal}
onClick={()= > lastandnextChick('prev')}
/>
<img
src={require(`./Icon/ ${playing ? 'pause' : 'play'}.svg`).default}
alt={` ${playing? 'suspend':'play'} `}onClick={togglePauseAndPlay}
className={[style.iconNormal, style.iconpp].join(" ")} / >
<img
src={require("./Icon/last.svg").default}
alt="svg"
className={style.iconNormal}
onClick={()= > lastandnextChick('next')}
style={{ transform: "rotate(180deg)" }}
/>
{/* <NextButton onPrevClick={()= >lastandnextChick("next")} {... props} /> */}<VolumeControl
className="mr2 flex flex-center"
buttonClassName="flex-none h2 button button-transparent button-grow rounded"
rangeClassName="custom-track-bg"
volume=0.5} {
{. props} / >
</div>
<div className={style.slider}>
<img
src={
currentSongDetail &&
currentSongDetail.al &&
currentSongDetail.al.picUrl
? currentSongDetail.al.picUrl
: "http://s4.music.126.net/style/web2/img/default/default_album.jpg"}alt="musicPic"
className="musicPic"
/>
</div>
<div className={style.btngroup}>
<div>
{showPlaylist ? (
<div className={style.playlistItem}>
<PlayerListTop toggleShowPlaylist={showPlaylistFun} songsList={songsList} playSongGlobal={playSong} playing={playing} soundCloudAudio={soundCloudAudio}/>
</div>
) : null}
<div onClick={(e)= > showPlaylistFun(e)} className={style.playListicon}>
<img
src={require("./Icon/plist.svg").default}
alt="svg"
className={style.iconNormal}
/>
<div className={style.playCount}>{songsList.length}</div>
</div>
</div>
</div>
</div>
</MinAndMax>
);
});
const mDispatch = (dispatch) = > {
console.log('dispatch');
return {
setNewSongs: (data) = > {
console.log(data, 'data setCurrentSongDetail');
dispatch(setCurrentSongDetail(data))
},
}
}
const mState = (state) = > {
return {
onEnd: state.currentDoingInfo.onEnd,
brotherNodeChangeTime: state.currentDoingInfo.brotherNodeChangeTime
}
}
export default withSoundCloudAudio(connect(mState, mDispatch)(Player));
Copy the code