成果展示
因为之前没玩把握,所以先玩了几天再决定记录下,现在已经完成首页的60%左右的功能。还原度95%左右。
等项目完成得差不多我会上传到 git 给小伙伴参考。
下半部分还有2个模块没完成。
共享组件设计方面
把大多数页面都用到布局抽离成共享组件:
头部组件
import React, { memo } from 'react'; // 导入React功能组件
import { headerLinks } from '@/common/local-data'; // 导入页面内部数据
import { NavLink } from 'react-router-dom'; // 导入路由组件
import { Input } from 'antd'; // 导入蚂蚁金服输入框组件
import { SearchOutlined } from '@ant-design/icons'; // 导入蚂蚁金服图标
import { HeaderWrapper, HeaderLeft, HeaderRight } from './style'; // 导入js-css组件
// 顶部导航
export default memo(function RTAppHeader() {
// UI布局逻辑代码
const showSelectItem = (item,index) => {
if(index < 3){
return (
<NavLink to={item.link}>
{item.title}
<i className="sprite_01 icon"></i>
</NavLink>
)
} else {
return ( <a href={item.link}>{item.title}</a> )
}
}
// UI布局模块
return (
<HeaderWrapper>
<div className="content wrap-v1">
<HeaderLeft>
<a href="#/" target="_blank" rel="external nofollow" className="logo sprite_01">网易云音乐</a>
<div className="select-list">
{
headerLinks.map((item,index) => {
return (
<div key = {item.title} className="select-item">
{showSelectItem(item,index)}
</div>
)
})
}
</div>
</HeaderLeft>
<HeaderRight>
<Input className="search" placeholder="音乐/视频/电台/用户" prefix={<SearchOutlined/>} />
<div className="center" >创作者中心</div>
<div >登录</div>
</HeaderRight>
</div>
<div className="divider"></div>
</HeaderWrapper>
)
})
底部组件
import React, { memo, Fragment } from 'react'; // 引入React功能组件
import { footerLinks, footerImages } from '@/common/local-data'; // 引入数据组件
import { AppFooterWrapper, FooterLeft, FooterRight, } from './style'; // 引入js-css组件
// 底部导航
export default memo(function RTAppFooter() {
return (
<AppFooterWrapper>
<div className="wrap-v2 content">
<FooterLeft className="left">
<div className="link">
{
footerLinks.map(item => {
return (
<Fragment key={item.title}>
<a href={item.link} target="_blank" rel="noopener noreferrer">{item.title}</a>
<span className="line">|</span>
</Fragment>
)
})
}
</div>
<div className="copyright">
<span>网易公司版权所有©1997-2020</span>
<span>
杭州乐读科技有限公司运营:
<a href="https://p1.music.126.net/Mos9LTpl6kYt6YTutA6gjg==/109951164248627501.png" target="_blank" rel="external nofollow" rel="noopener noreferrer" target="_blank">浙网文[2018]3506-263号</a>
</span>
</div>
<div className="report">
<span>违法和不良信息举报电话:0571-89853516</span>
<span>
举报邮箱:
<a href="mailto:[email protected]" target="_blank" rel="external nofollow" target="_blank" rel="noopener noreferrer">[email protected]</a>
</span>
</div>
<div className="info">
<span>粤B2-20090191-18</span>
<a href="http://www.beian.miit.gov.cn/publish/query/indexFirst.action" target="_blank" rel="external nofollow" rel="noopener noreferrer" target="_blank">
工业和信息化部备案管理系统网站
</a>
</div>
</FooterLeft>
<FooterRight className="right">
{
footerImages.map((item, index) => {
return (
<li className="item" key={item.link}>
<a className="link" href={item.link} rel="noopener noreferrer" target="_blank"> </a>
<span className="title">{item.title}</span>
</li>
)
})
}
</FooterRight>
</div>
</AppFooterWrapper>
)
})
歌曲封面组件
import React, { memo } from 'react'; // 导入recat组件
import { getCount, getSizeImage } from '@/utils/format-utils'; // 导入点播量计算工具,图片大小设置工具
import { SongsCoverWrapper } from './style'; //导入js-css样式
// 歌曲封面组件
export default memo(function RTSongsCover(props) {
// 传入数据
const { info } = props;
// UI布局模块
return (
<SongsCoverWrapper>
<div className="cover-top">
<img src={getSizeImage(info.picUrl, 140)} alt={info.name} />
<div className="cover sprite_cover">
<div className="info sprite_cover">
<span>
<i className="sprite_icon erji" />
{getCount(info.playCount)}
</span>
<i className="sprite_icon play"/>
</div>
</div>
</div>
<div className="cover-bottom text-nowrap">
{info.name}
</div>
<div className="cover-source text-nowrap">
by {info.copywriter || info.creator.nickname}
</div>
</SongsCoverWrapper>
)
})
插件使用
轮播图使用:antd 风格与组件稳定符合项目要求,如果真实开发组件稳定考虑优先,除非风格完全不匹配,才考虑原生。
import React, { memo, useEffect, useRef, useCallback, useState } from 'react'; // 引入react组件
import { useDispatch, useSelector, shallowEqual } from 'react-redux'; // 引入共享数据管理hooks
import { getTopBannerAction } from '../../store/actionCreators'; // 引入网络请求接口
import { Carousel } from 'antd'; // 导入 antdesign 轮播图组件
import { BannerWrapper, BannerLeft, BannerRight, BannerControl } from './style'; // 导入js-css样式
// 轮播图组件
export default memo(function RTTopBanner() {
/* 组件内部数据 */
// 使用 hooks:useState 保存轮播图操作数据
const [currentIndex, setCurrentIndex] = useState(0);
/* 组件功能模块 */
// hooks不做浅层比较只做引用比较,所以会有性能浪费情况。性能优化添加浅层比较:shallowEqual
// 使用 hooks:useSelector 获取 state(immutable对象)
// immutable 对象需要使用 get()方法取值, 多层嵌套时可以使用 getIn()
const { topBanners } = useSelector(state => ({
topBanners: state.getIn(["recommend", "topBanners"])
}), shallowEqual);
// 使用 hooks:useDispatch 获取dispatch函数
const dispatch = useDispatch();
// 发送请求(组件被渲染是发送请求)
useEffect(() => {
dispatch(getTopBannerAction())
},[dispatch]); // 当对象更改将会重新渲染
// 使用 hooks:useRef 操作真实dom元素
const bannerRef = useRef();
// 使用 hooks:useCallback 可以保存函数到缓存,下次直接引用,多次生成引起组件刷新
const bannerChange = useCallback((from, to) => {
setCurrentIndex(to)
},[]); // []:不做任何对比,不会刷新
/* 组件逻辑模块 */
// 获取当前图片 url 拼接参数,获取该图片的高斯模糊背景图 url
const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")
// UI布局模块
return (
// bgImage:向 js-css 传入背景图片url 地址
<BannerWrapper bgImage={bgImage}>
<div className="banner wrap-v2">
<BannerLeft>
<Carousel effect="fade" autoplay ref={bannerRef} beforeChange={bannerChange}>
{
topBanners.map ((item, index) =>{
return (
<div className="banner-item" key={item.imageUrl}>
<img className="image" src={item.imageUrl} alt={item.typeTitle}></img>
</div>
)
})
}
</Carousel>
</BannerLeft>
<BannerRight></BannerRight>
<BannerControl>
<button className="btn left" onClick={ e => bannerRef.current.prev()}></button>
<button className="btn right" onClick={ e => bannerRef.current.next()}></button>
</BannerControl>
</div>
</BannerWrapper>
)
})