接着前面的继续写了,这篇博客就写这两个页面,下一篇就主要讲歌曲播放功能,进度条拉伸以及歌曲时间的变化了
先完成新碟上架Demo
一.第一步不用说肯定是先获取我们这个数据对吧
去到我们services文件夹里面的recommend.js
export function getNewAlbums(limit) {
return request({
url: "/top/album",
params: {
limit,
}
})
}
二.就要把这个数据存储到我们的redux中了
到这个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" 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,这个页面完成了,下面就开始写这个页面了
第一步,首先定义网络请求数据
去到我们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" className="image_cover">ranking</a>
</div>
<div className="info">
<a href="/todo">{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">查看全部 ></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;
}
`
好了,这个页面也完成了
下一篇就主要讲歌曲播放功能,进度条拉伸以及歌曲时间的变化了