天天看點

React實作(Web端)網易雲音樂項目(三),錯過了真的可惜呀

接着前面的繼續寫了,這篇部落格就寫這兩個頁面,下一篇就主要講歌曲播放功能,進度條拉伸以及歌曲時間的變化了

React實作(Web端)網易雲音樂項目(三),錯過了真的可惜呀

先完成新碟上架Demo

一.第一步不用說肯定是先擷取我們這個資料對吧

去到我們services檔案夾裡面的recommend.js

export function getNewAlbums(limit) {
  return request({
    url: "/top/album",
    params: {
      limit,
    }
  })
}
           

二.就要把這個資料存儲到我們的redux中了

React實作(Web端)網易雲音樂項目(三),錯過了真的可惜呀

到這個store,我們是每個部分裡面都有一個子redux,然後再通過合并到我們的主redux裡面

首先肯定是定義常量了

constants.js

export const CHANGE_NEW_ALBUM = "recommend/CHANGE_NEW_ALBUM";
           

然後去actionCreators.js寫方法

import * as actionTypes from './constants';

import { 
  getNewAlbums
} from '@/services/recommend';

const changeNewAlbumAction = (res) => ({
  type: actionTypes.CHANGE_NEW_ALBUM,
  newAlbums: res.albums
})

export const getNewAlbumAction = (limit) => {
  return dispatch => {
    getNewAlbums(limit).then(res => {
      dispatch(changeNewAlbumAction(res));
    })
  }
}
           

然後去reducer.js中

import { Map } from 'immutable';
//導入所有常量
import * as actionTypes from './constants';

const defaultState = Map({
  newAlbums: [],
});

function reducer(state = defaultState, action) {
  switch (action.type) {
    case actionTypes.CHANGE_NEW_ALBUM:
      return state.set("newAlbums", action.newAlbums);
    default:
      return state;
  }
}

export default reducer;
           

三.這個時候就得去我們的元件中通過redux-hooks發送網絡請求了

在recommend中的c-cpns裡面建立一個new-album檔案夾,裡面在建立一個index.js,一個style.js
           

index.js

還是分三步來寫,第一步寫我們導入的配置

import React, { memo,useEffect,useRef } from 'react';

import {useDispatch, useSelector, shallowEqual} from 'react-redux'

import { getNewAlbumAction } from '../../store/actionCreators';

import { Carousel } from 'antd';
import HYAlbumCover from '@/components/album-cover';
import HYThemeHeaderRCM from '@/components/theme-header-rcm';
import { AlbumWrapper } from './style';
           

HYAlbumCover,HYThemeHeaderRCM這兩個元件是我在components中定義的複用型元件

下面來寫一下這個元件,HYAlbumCover

在components中建立一個album-cover檔案夾,裡面在建立一個index.js,一個style.js
           

index.js

import React, { memo } from 'react';

import { getSizeImage } from '@/utils/format-utils';

import { AlbumWrapper } from './style';

export default memo(function HYAlbumCover(props) {
  // state and props
  const { info, size = 130, width = 153, bgp = "-845px" } = props;

  return (
    <AlbumWrapper size={size} width={width} bgp={bgp}>
      <div className="album-image">
        <img src={getSizeImage(info.picUrl, size)} alt="" />
        <a href="/todo" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  className="cover image_cover">{info.name}</a>
      </div>
      <div className="album-info">
        <div className="name text-nowrap">{info.name}</div>
        <div className="artist text-nowrap">{info.artist.name}</div>
      </div>
    </AlbumWrapper>
  )
})
           

getSizeImage是什麼,之前的文章提到過,網易雲音樂為了加快圖檔的加載速度,在圖檔後面可以拼接一個param參數,然後傳入寬高,這樣就可以讓我們的圖檔變為我們傳入的寬高,這樣加載的時候就很快

style.js

import styled from 'styled-components';

export const AlbumWrapper = styled.div`
  width: ${props => props.width + "px"};

  .album-image {
    position: relative;
    width: ${props => props.width + "px"};
    height: ${props => props.size + "px"};
    overflow: hidden;
    margin-top: 15px;

    img {
      width: ${props => props.size + "px"};
      height: ${props => props.size + "px"};
    }

    .cover {
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      background-position: 0 ${props => props.bgp};
      text-indent: -9999px;
    }
  }

  .album-info {
    font-size: 12px;
    width: ${props => props.size};
    .name {
      color: #000;
      white-space: nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
    }

    .artist {
      color: #666;
    }
  }
`
           

HYThemeHeaderRCM這個元件,我就不寫了,之前的文章寫過

然後看第二步,邏輯代碼,我把上面的也複制過來吧

import React, { memo,useEffect,useRef } from 'react';
import {useDispatch, useSelector, shallowEqual} from 'react-redux'
import { getNewAlbumAction } from '../../store/actionCreators';
import { Carousel } from 'antd';
import HYAlbumCover from '@/components/album-cover';
import HYThemeHeaderRCM from '@/components/theme-header-rcm';
import { AlbumWrapper } from './style';

export default memo(function HYNewAlbum() {

  const {newAlbums} = useSelector(state =>({
    newAlbums:state.getIn(['recommend','newAlbums'])
  }),shallowEqual)
  
  const dispath = useDispatch()
  
  // other hooks
  const pageRef = useRef();
  useEffect(()=>{
    dispath(getNewAlbumAction(10))
  },[dispath])
})
           

useSelector就是擷取我們redux中定義的state,shallowEqual是為了淺層比較,性能優化,然後就在useEffect中請求我們儲存在redux中的資料

第三步

return (
    <AlbumWrapper>
      <HYThemeHeaderRCM title="新碟上架"/>
      <div className="content">
        <button className="arrow arrow-left sprite_02" 
                onClick={e => pageRef.current.prev()}></button>
        <div className="album">
          <Carousel dots={false} ref={pageRef}>
            {
              [0, 1].map(item => {
                return (
                  <div key={item} className="page">
                    {
                      newAlbums.slice(item * 5, (item + 1) * 5).map(iten => {
                        return <HYAlbumCover key={iten.id} 
                                             info={iten} 
                                             size={100} 
                                             width={118} 
                                             bgp="-570px"/>
                      })
                    }
                  </div>
                )
              })
            }
          </Carousel>
        </div>
        <button className="arrow arrow-right sprite_02"
                onClick={e => pageRef.current.next()}></button>
      </div>
    </AlbumWrapper>
  )
           

這個地方的資料我用slice處理了一下,因為它一次就展示5條,當點選next時候就展示另外5條,prev也是

然後就是我們的style.js

import styled from "styled-components";

export const AlbumWrapper = styled.div`
  margin-top: 50px;

  .content {
    height: 186px;
    background-color: #f5f5f5;
    border: 1px solid #d3d3d3;
    margin: 20px 0 37px;
    display: flex;
    align-items: center;

    .arrow {
      width: 25px;
      height: 25px;
      cursor: pointer;
    }

    .arrow-left {
      background-position: -260px -75px;
    }

    .arrow-right {
      background-position: -300px -75px;
    }

    .album {
      width: 640px;
      height: 150px;

      .ant-carousel .slick-slide {
        height: 150px;
        overflow: hidden;
      }

      .page {
        display: flex !important;
        justify-content: space-between;
        align-items: center;
      }
    }
  }
`
           

ok,這個頁面完成了,下面就開始寫這個頁面了

React實作(Web端)網易雲音樂項目(三),錯過了真的可惜呀

第一步,首先定義網絡請求資料

去到我們services檔案夾裡面的recommend.js

export function getTopList(idx) {
  return request({
    url: "/top/list",
    params: {
      idx
    }
  })
}
           

第二步,就得把資料存儲到我們的redux裡面了

還是先定義常量

constants.js

export const CHANGE_UP_RANKING = "recommend/CHANGE_UP_RANKING";
export const CHANGE_NEW_RANKING = "recommend/CHANGE_New_RANKING";
export const CHANGE_ORIGIN_RANKING = "recommend/CHANGE_ORIGIN_RANKING";
           

actionCreators.js

import * as actionTypes from './constants';

import { 
  getTopList
} from '@/services/recommend';
const changeUpRankingAction = (res) => ({
  type: actionTypes.CHANGE_UP_RANKING,
  upRanking: res.playlist
})

const changeNewRankingAction = (res) => ({
  type: actionTypes.CHANGE_NEW_RANKING,
  newRanking: res.playlist
})

const changeOriginRankingAction = (res) => ({
  type: actionTypes.CHANGE_ORIGIN_RANKING,
  originRanking: res.playlist
})
export const getTopListAction = (idx) => {
  return dispatch => {
    getTopList(idx).then(res => {
      switch (idx) {
        case 0:
          dispatch(changeUpRankingAction(res));
          break;
        case 2:
          dispatch(changeNewRankingAction(res));
          break;
        case 3:
          dispatch(changeOriginRankingAction(res));
          break;
        default:
      }
    });
  }
}
           

再去我們的reducer.js中

import { Map } from 'immutable';

//導入所有常量
import * as actionTypes from './constants';

const defaultState = Map({
  upRanking: {},
  newRanking: {},
  originRanking: {},
});

function reducer(state = defaultState, action) {
  switch (action.type) {
    case actionTypes.CHANGE_UP_RANKING:
      return state.set("upRanking", action.upRanking);
    case actionTypes.CHANGE_NEW_RANKING:
      return state.set("newRanking", action.newRanking);
    case actionTypes.CHANGE_ORIGIN_RANKING:
      return state.set("originRanking", action.originRanking);
    default:
      return state;
  }
}

export default reducer;
           

第三步,就可以去元件裡面寫内容了

在recommend中的c-cpns裡面建立一個recommend-ranking檔案夾,裡面在建立一個index.js,一個style.js
           

index.js

還是分三步,第一步導入配置

import React, { memo, useEffect } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';

import HYThemeHeaderRCM from '@/components/theme-header-rcm';
import HYTopRanking from '@/components/top-ranking';
import { RankingWrapper } from './style';
import { getTopListAction } from '../../store/actionCreators';	
           

HYThemeHeaderRCM這個元件之前的部落格封裝過就不寫了,接下來要寫的那個清單,三塊很明顯是一模一樣的,是以封裝一個元件HYTopRanking

在components檔案夾中建立一個top-ranking檔案夾,裡面建立一個index.js,一個style.js
           

index.js

import React, { memo } from 'react';

import { getSizeImage } from '@/utils/format-utils';

import { TopRankingWrapper } from './style';

export default memo(function HYTopRanking(props) {
  const { info } = props;
  const { tracks = [] } = info;

  return (
    <TopRankingWrapper>
      <div className="header">
        <div className="image">
          <img src={getSizeImage(info.coverImgUrl)} alt="" />
          <a href="/todo" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  className="image_cover">ranking</a>
        </div>
        <div className="info">
          <a href="/todo" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >{info.name}</a>
          <div>
            <button className="btn play sprite_02"></button>
            <button className="btn favor sprite_02"></button>
          </div>
        </div>
      </div>
      <div className="list">
        {
          tracks.slice(0, 10).map((item, index) => {
            return (
              <div key={item.id} className="list-item">
                <div className="rank">{index + 1}</div>
                <div className="info">
                  <span className="name text-nowrap">{item.name}</span>
                  <div className="operate">
                    <button className="btn sprite_02 play"></button>
                    <button className="btn sprite_icon2 addto"></button>
                    <button className="btn sprite_02 favor"></button>
                  </div>
                </div>
              </div>
            )
          })
        }
      </div>
      <div className="footer">
        <a href="/todo" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >檢視全部 &gt;</a>
      </div>
    </TopRankingWrapper>
  )
})
           

style.js

import styled from 'styled-components';

export const TopRankingWrapper = styled.div`
  flex: 1;

  .header {
    height: 100px;
    display: flex;

    margin: 20px 0 0 20px;

    .image {
      width: 80px;
      height: 80px;
      position: relative;

      img {
        width: 80px;
        height: 80px;
      }
    }

    .info {
      margin: 5px 0 0 10px;

      a {
        font-size: 14px;
        color: #333;
        font-weight: 700;
      }

      .btn {
        display: inline-block;
        text-indent: -9999px;
        width: 22px;
        height: 22px;
        margin: 8px 10px 0 0;
        cursor: pointer;
      }

      .play {
        background-position: -267px -205px;
      }

      .favor {
        background-position: -300px -205px;
      }
    }
  }

  .list {
    .list-item {
      position: relative;
      display: flex;
      align-items: center;
      height: 32px;

      :nth-child(-n+3) .rank {
        color: #c10d0c;
      }

      .rank {
        width: 35px;
        text-align: center;
        margin-left: 10px;
        font-size: 16px;
      }

      .info {
        color: #000;
        width: 170px;
        height: 17px;
        line-height: 17px;
        display: flex;
        justify-content: space-between;

        .name {
          flex: 1;
        }

        .operate {
          display: flex;
          align-items: center;
          display: none;
          width: 82px;

          .btn {
            width: 17px;
            height: 17px;
            margin-left: 8px;
            cursor: pointer;
          }

          .play {
            background-position: -267px -268px;
          }

          .addto {
            position: relative;
            top: 2px;
            background-position: 0 -700px;
          }

          .favor {
            background-position: -297px -268px;
          }
        }
      }

      

      &:hover {
        .operate {
          display: block;
        }
      }
    }
  }

  .footer {
    height: 32px;
    display: flex;
    align-items: center;
    margin-right: 32px;
    justify-content: flex-end;

    a {
      color: #000;
    }
  }
`
           

好了,接下來開始第二步,邏輯代碼

export default memo(function HYRecomendRanking() {
  // redux hooks
  const { upRanking, newRanking, originRanking } = useSelector(state => ({
    upRanking: state.getIn(["recommend", "upRanking"]),
    newRanking: state.getIn(["recommend", "newRanking"]),
    originRanking: state.getIn(["recommend", "originRanking"]),
  }), shallowEqual);
  const dispatch = useDispatch();

  // other hooks
  useEffect(() => {
    dispatch(getTopListAction(0));
    dispatch(getTopListAction(2));
    dispatch(getTopListAction(3));
  }, [dispatch]);
})
           

這塊就沒啥好說的了

第三步

return (
    <RankingWrapper>
      <HYThemeHeaderRCM title="榜單" />
      <div className="tops">
        <HYTopRanking info={upRanking}/>
        <HYTopRanking info={newRanking}/>
        <HYTopRanking info={originRanking}/>
      </div>
    </RankingWrapper>
  )
           

style.js

import styled from "styled-components";

export const RankingWrapper = styled.div`
  .tops {
    margin: 30px 0;
    display: flex;
    background-image: url(${require("@/assets/img/recommend-top-bg.png")});
    height: 472px;
  }
`
           

好了,這個頁面也完成了

下一篇就主要講歌曲播放功能,進度條拉伸以及歌曲時間的變化了

React實作(Web端)網易雲音樂項目(三),錯過了真的可惜呀

github項目位址:https://github.com/lsh555/WYY-Music