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

  1. The homepage UI
  2. Song Details Page
  3. Playlist details page
  4. Album Details Page
  5. Details page for playing and adding playlists
  6. List of Music Players
  7. Song single cycle

Here is some core code

react-router-domThe 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

reduxSome 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

  1. Use two packages ()

    yarn add -D customize-cra react-app-rewired

  2. 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
  3. 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
  4. 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

Thank you for watching