轮播
//Recommend/index.js
import React from 'react'
import Slider from '../../components/slider'
function Recommend() {
const bannerList = [1, 2, 3, 4].map(() => {
return {
imageUrl: 'http://p1.music.126.net/ZYLJ2oZn74yUz5x8NBGkVA==/109951164331219056.jpg'
}
})
return (
<div>
<Slider bannerList={bannerList}/>
</div>
)
}
export default Recommend
//components/slider/index.js
import React, { useState, useEffect } from 'react'
import { SliderContainer } from './style'
import 'swiper/css/swiper.css'
import Swiper from 'swiper'
function Slider(props) {
const { bannerList } = props
const [sliderSwiper, setSliderSwiper] = useState(null)
useEffect(() => {
if (bannerList.length && !sliderSwiper) {
const sliderSwiper = new Swiper('.swiper-container', {
loop: true,
autoplay: {
delay: 3000,
disableOnInteraction: false
},
pagination: {
el: '.swiper-pagination'
}
})
setSliderSwiper(sliderSwiper)
}
}, [bannerList.length, sliderSwiper])
return (
<SliderContainer>
<div className='before'></div>
<div className="swiper-container">
<div className="swiper-wrapper">
{
bannerList.map((banner, index) => {
return (
<div className="swiper-slide" key={index}>
<div className='swiper-nav'>
<img src={banner.imageUrl} width='100%' height='100%' alt='推荐'/>
</div>
</div>
)
})
}
</div>
<div className="swiper-pagination"></div>
</div>
</SliderContainer>
)
}
export default Slider
//components/scroll/style.js
import styled from "styled-components"
import style from '../../assets/global-style'
export const SliderContainer = styled.div`
position: relative;
width: 100%;
height: 100%;
.before {
position: absolute;
top: 0;
height: 60%;
width: 100%;
background: ${style['theme-color']}
}
.swiper-container {
position: relative;
width: 98%;
height: 160px;
overflow: hidden;
margin: auto;
border-radius: 6px;
.swiper-nav {
position: absolute;
width: 100%;
height: 100%;
}
--swiper-theme-color: ${style['theme-color']};
}
`
推荐列表-mock
//Recommend/index.js
import React from 'react'
import CommonList from '../../components/list';
function Recommend() {
const recommendList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(() => {
return {
id: 1,
picUrl: 'https://p1.music.126.net/fhmefjUfMD-8qtj3JKeHbA==/18999560928537533.jpg',
playCount: 17171122,
name: '朴树、许巍、李健、郑钧、老狼、赵雷'
}
})
return (
<div>
<CommonList list={recommendList}/>
</div>
)
}
export default Recommend
//components/list/index.js
import React from 'react'
import { ListWrapper, List, ListItem } from './style'
import { getCount } from '../../api/utils'
function CommonList(props){
const { list } = props
return (
<ListWrapper>
<h1 className='title'>推荐歌单</h1>
<List>
{
list.map((item, index) => {
return (
<ListItem key={item.id + index}>
<div className='image-wrapper'>
<div className='decorate'></div>
<img src={item.picUrl + '?param=300x300'} width='100%' height='100%' alt='歌单'/>
<div className='play-count'>
<span className='iconfont play'></span>
<span className='count'>{getCount(item.playCount)}</span>
</div>
</div>
<div className='desc'>{item.name}</div>
</ListItem>
)
})
}
</List>
</ListWrapper>
)
}
export default CommonList
//components/list/style.js
import styled from "styled-components"
import style from '../../assets/global-style'
export const ListWrapper = styled.div`
max-width: 100%;
.title {
font-weight: 700;
padding-left: 6px;
font-size: 14px;
line-height: 60px;
}
`
export const List = styled.div`
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
`
export const ListItem = styled.div`
position: relative;
width: 32%;
.image-wrapper {
.decorate {
position: absolute;
top: 0;
width: 100%;
height: 35px;
border-radius: 3px;
background: linear-gradient(hsla(0, 0%, 43%, 0.4), hsla(0, 0%, 100%, 0));
}
position: relative;
height: 0;
padding-bottom: 100%;
img {
position: absolute;
width: 100%;
height: 100%;
border-radius: 3px;
}
.play-count {
position: absolute;
right: 2px;
top: 2px;
line-height: 15px;
color: ${style['font-color-light']};
font-size: ${style['font-size-s']};
.play {
vertical-align: top;
}
}
}
.desc {
overflow: hidden;
margin-top: 2px;
padding: 0 2px;
height: 50px;
text-align: left;
font-size: ${style['font-size-s']};
line-height: 1.4;
color: ${style['font-color-desc']};
}
`
//api/utils.js
export const getCount = (count) => {
if (count < 0) return
if (count < 10000) {
return count
} else if (Math.floor(count/10000) < 10000) {
return Math.floor(count/1000)/10 + '万'
} else {
return Math.floor(count/10000000)/10 + '亿'
}
}
better-scroll 的初步应用
//baseUI/scroll/index.js
import React, { forwardRef, useState,useEffect, useRef, useImperativeHandle } from 'react'
import PropTypes from 'prop-types'
import BScroll from 'better-scroll'
import styled from 'styled-components'
const ScrollContainer = styled.div`
width: 100%;
height: 100%;
overflow: hidden;
`
const Scroll = forwardRef((props, ref) => {
const [bScroll, setBScroll] = useState();
const scrollContaninerRef = useRef();
const { direction, click, refresh, bounceTop, bounceBottom } = props;
const { pullUp, pullDown, onScroll } = props;
useEffect(() => {
const scroll = new BScroll(scrollContaninerRef.current, {
scrollX: direction === "horizental",
scrollY: direction === "vertical",
probeType: 3,
click: click,
bounce:{
top: bounceTop,
bottom: bounceBottom
}
});
setBScroll(scroll);
return () => {
setBScroll(null);
}
// eslint-disable-next-line
}, []);
useEffect(() => {
if(!bScroll || !onScroll) return;
bScroll.on('scroll', (scroll) => {
onScroll(scroll);
})
return () => {
bScroll.off('scroll');
}
}, [onScroll, bScroll]);
useEffect(() => {
if(!bScroll || !pullUp) return;
bScroll.on('scrollEnd', () => {
//判断是否滑动到了底部
if(bScroll.y <= bScroll.maxScrollY + 100){
pullUp();
}
});
return () => {
bScroll.off('scrollEnd');
}
}, [pullUp, bScroll]);
useEffect(() => {
if(!bScroll || !pullDown) return;
bScroll.on('touchEnd', (pos) => {
//判断用户的下拉动作
if(pos.y > 50) {
pullDown();
}
});
return () => {
bScroll.off('touchEnd');
}
}, [pullDown, bScroll]);
useEffect(() => {
if(refresh && bScroll){
bScroll.refresh();
}
});
useImperativeHandle(ref, () => ({
refresh() {
if(bScroll) {
bScroll.refresh();
bScroll.scrollTo(0, 0);
}
},
getBScroll() {
if(bScroll) {
return bScroll;
}
}
}));
return (
<ScrollContainer ref={scrollContaninerRef}>
{props.children}
</ScrollContainer>
);
})
Scroll.defaultProps = {
direction: "vertical",
click: true,
refresh: true,
onScroll:null,
pullUpLoading: false,
pullDownLoading: false,
pullUp: null,
pullDown: null,
bounceTop: true,
bounceBottom: true
}
Scroll.propTypes = {
direction: PropTypes.oneOf(['vertical', 'horizental']),
refresh: PropTypes.bool,
onScroll: PropTypes.func,
pullUp: PropTypes.func,
pullDown: PropTypes.func,
pullUpLoading: PropTypes.bool,
pullDownLoading: PropTypes.bool,
bounceTop: PropTypes.bool,//是否支持向上吸顶
bounceBottom: PropTypes.bool//是否支持向上吸顶
}
export default Scroll
// Recommend/index.js
import { Content } from './style'
import Scroll from '../../baseUI/scroll'
<Content>
<Scroll className="list">
<div>
<Slider list={bannerList}></Slider>
<RecommendList list={recommendList}></RecommendList>
</div>
</Scroll>
</Content>
// Recommend/style.js
import styled from'styled-components';
export const Content = styled.div`
position: fixed;
top: 90px;
bottom: 0;
width: 100%;
`
//slider/style.js
.before {
position: absolute;
top: -300px;
height: 400px;
width: 100%;
background: ${style ["theme-color"]};
}
数据层
1.
//api/config.js
import axios from 'axios'
export const baseUrl = 'http://localhost:8000'
//axios 的实例及拦截器配置
const axiosInstance = axios.create ({
baseURL: baseUrl
})
axiosInstance.interceptors.response.use (
res => res.data,
err => {
console.log (err, "网络错误")
}
);
export {
axiosInstance
}
2.
//api/request.js
import { axiosInstance } from "./config"
export const getBannerListRequest = () => {
return axiosInstance.get('/banner')
}
export const getRecommendListRequest = () => {
return axiosInstance.get('/personalized')
}
3.
//Recommond/store/reducer.js
import * as actionTypes from './actionTypes'
import { fromJS } from 'immutable'
const defaultState = fromJS ({
bannerList: [],
recommendList: [],
})
4.
//Recommond/store/actionTypes.js
export const CHANGE_BANNER_LIST = 'recommend/CHANGE_BANNER_LIST'
export const CHANGE_RECOMMEND_LIST = 'recommend/CHANGE_RECOMMEND_LIST'
5.
//Recommond/store/reducer.js
export default (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_BANNER_LIST:
return state.set('bannerList', action.data)
case actionTypes.CHANGE_RECOMMEND_LIST:
return state.set('recommendList', action.data)
default:
return state
}
}
6.
//Recommond/store/actionCreators.js
import * as actionTypes from './actionTypes'
import { fromJS } from 'immutable'
import { getBannerListRequest, getRecommendListRequest } from '../../../api/request'
export const changeBannerList = (data) => ({
type: actionTypes.CHANGE_BANNER_LIST,
data: fromJS(data)
})
export const changeRecommendList = (data) => ({
type: actionTypes.CHANGE_RECOMMEND_LIST,
data: fromJS(data)
})
export const getBannerList = () => {
return (dispatch) => {
getBannerListRequest().then (data => {
dispatch(changeBannerList(data.banners))
}).catch (() => {
console.log("轮播图数据传输错误")
})
}
}
export const getRecommendList = () => {
return (dispatch) => {
getRecommendListRequest().then (data => {
dispatch (changeRecommendList(data.result))
}).catch (() => {
console.log ("推荐歌单数据传输错误");
})
}
}
7.
//Recommond/store/index.js
import reducer from './reducer'
import * as actionCreators from './actionCreators'
export { reducer, actionCreators }
8.
//src/store/reducer.js
import { combineReducers } from 'redux-immutable'
import { reducer as recommendReducer } from '../application/Recommend/store'
export default combineReducers ({
recommend: recommendReducer,
})
9.
//Recommond/index.js
import React, { useEffect, memo } from 'react'
import { Content } from './style'
import Slider from '../../components/slider'
import CommonList from '../../components/list'
import Scroll from '../../baseUI/scroll'
import { connect } from "react-redux"
import * as actionCreators from './store/actionCreators'
function Recommend(props) {
const { bannerList, recommendList } = props
const { getBannerListDataDispatch, getRecommendListDataDispatch } = props
useEffect (() => {
getBannerListDataDispatch()
getRecommendListDataDispatch()
//eslint-disable-next-line
}, [])
const bannerListJS = bannerList ? bannerList.toJS() : []
const recommendListJS = recommendList ? recommendList.toJS() : []
return (
<Content>
<Scroll className='list'>
<div>
<Slider list={bannerListJS}/>
<CommonList list={recommendListJS}/>
</div>
</Scroll>
</Content>
)
}
const mapStateToProps = (state) => ({
bannerList: state.getIn(['recommend', 'bannerList']),
recommendList: state.getIn(['recommend', 'recommendList']),
})
const mapDispatchToProps = (dispatch) => {
return {
getBannerListDataDispatch() {
dispatch(actionCreators.getBannerList())
},
getRecommendListDataDispatch () {
dispatch(actionCreators.getRecommendList())
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(memo(Recommend))