开源移动端组件库具体可以查看这个:juejin.cn/post/740152… 参考文案:baijiahao.baidu.com/s?id=177814…
-
采用 github 上开源的 NodeJS 版 api 接口 NeteaseCloudMusicApi,提供音乐数据。在此特别鸣谢 Binaryify 大佬开源接口!
-
create-vite: React 脚手架,快速搭建项目
-
eslint: 知名代码风格检查工具
-
antd: 阿里巴巴图标库
-
fastclick: 解决移动端点击延迟 300ms 的问题
-
zustand: 作为状态管理器
-
scss:处理样式
初始化项目
npx create-vite
选择 react,js 就好了
npm i
npm run dev
文件结构
设置样式
npm install -D sass
基础样式设置src/index.scss
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
html, body {
background: #f2f3f4;;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
a {
text-decoration: none;
color: #fff;
}
提取共用样式, --theme-color代表样式变量,在组件里面直接color: var(--theme-color)使用就好了,在src\assets\global.css下定义文件
:root {
--theme-color: #d44439;
--theme-color-shadow: rgba (212; 68; 57; .5);
--font-color-light: #f1f1f1;
--font-color-desc: #2E3030;
--font-color-desc-v2: #bba8a8;
--font-size-ss: 10px;
--font-size-s: 12px;
--font-size-m: 14px;
--font-size-l: 16px;
--font-size-ll: 18px;
--border-color: #e4e4e4;
--background-color: #f2f3f4;
--background-color-shadow: rgba (0; 0; 0; 0.3);
--highlight-background-color: #fff;
--primary-color: #3498db;
--font-size-large: 18px;
}
/* 扩大可点击区域 */
.extend-Click {
position: relative;
&:before {
content: '';
position: absolute;
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
}
/* 一行文字溢出部分用... 代替 */
.word-no-wrap {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
设置路由
路由文档地址: reactrouter.com/en/main/com…
npm install react-router-dom --save
react-router-dom里面包含react-router的所有东西,新的react-router-dom里面没有 Redirect 组件。
//routes/index.js
import React from 'react';
import Home from '../application/Home';
import Recommend from '../application/Recommend';
import Singers from '../application/Singers';
import Rank from '../application/Rank';
export default [
{
path: "/",
element: <Home></Home>,
children: [
{
path: "/recommend",
element: <Recommend></Recommend>
},
{
path: "/singers",
element: <Singers></Singers>
},
{
path: "/rank",
element: <Rank></Rank>
}
]
}
];
main
import { createRoot } from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import App from './App.jsx';
import routes from './routes/index.jsx';
import './index.css';
const router = createBrowserRouter(routes);
createRoot(document.getElementById('root')).render(
<RouterProvider router={router}>
<App />
</RouterProvider>
);
测试
配置路由占位,根组件是Home,所以在Home利用Outlet占位
import React from 'react';
import { Outlet } from 'react-router-dom';
export default function Home() {
return <>
Home2222222222
<Outlet></Outlet>
</>;
}
测试
状态管理器
npm i jotai -S
组件库
组件库地址:mobile.ant.design/zh/guide/qu…
npm install --save antd-mobile
npm install --save antd-mobile-icons
自定义icon组件,进入阿里矢量库:www.iconfont.cn/
登录组件
目标:
login.jsx
import React, { useState } from 'react';
import { Form, Input, Button, Checkbox } from 'antd-mobile';
import classnames from 'classnames';
import Captcha from './Captcha';
import './index.scss';
const Login = () => {
const [isActive, setIsActive] = useState(true);
const handleClick = () => {
setIsActive(!isActive);
};
const loginClass = classnames({
'active': isActive,
'inactive': !isActive,
});
const registerClass = classnames({
'active': !isActive,
'inactive': isActive,
});
const handleSubmit = async (values) => {
console.log(values, 9090);
};
return <div className='login-container'>
<div className='login-content'>
<div className='title'>
<span className={loginClass} onClick={handleClick}>登录</span>
<span className={registerClass} onClick={handleClick} >注册</span>
</div>
<div className='form-content'>
<Form
form={form}
layout='horizontal'
mode='card'
onFinish={handleSubmit}
footer={
<Button block type='submit' size='large' >
{isActive ? '登录' : '注册'}
</Button>
}>
<Form.Item label="用户名" name="user" rules={[{ required: true, message: '姓名不能为空' }]}>
<Input placeholder='请输入' />
</Form.Item>
<Form.Item label="密码" name="password" rules={[{ required: true, message: '姓名不能为空' }]}>
<Input placeholder='请输入' />
</Form.Item>
<Form.Item label="验证码" name="registerId" hidden={isActive} >
<Captcha />
</Form.Item>
</Form>
</div>
<div >
<div className='foot-content'>
<Checkbox />
<label className="text-right">阅读并同意了《这个条款》并愿意承担相关的责任</label>
</div>
</div>
</div>
</div>;
};
export default Login;
Captcha.jsx
import React from 'react';
import Captcha from "react-captcha-code";
import { Form, Input, Button, Checkbox } from 'antd-mobile';
export default function CaptchaCom(props) {
console.log(props);
return <div>
<Input {...props} placeholder='请输入' />
<Captcha charNum={4} />
</div>;
}
验证码
npm i react-captcha-code -S
代码
...
import Captcha from "react-captcha-code"
...
<Input
clearable
type="text"
placeholder="请输入验证码"
onChange={(value) => setVerify(value)}
/>
<Captcha charNum={4} />
样式:
.login-container {
background: url('@/assets/img/login2.jpg');
height: 100vh;
overflow: hidden;
.login-content {
background: #fff;
color: #f83b49;
margin: 12px;
margin-top: 30vh;
padding: 16px;
border-radius: 5px;
.title {
height: 60px;
font-size: 26px;
padding-left: 10px;
&>span {
margin-right: 24px;
}
.active {
font-size: 26px;
}
.inactive {
font-size: 20px;
color: #ccc;
}
}
.form-content {
.adm-form {
--adm-border-color: #fff;
}
.adm-button {
background: #fd7c90;
}
.adm-list-item-content {
border-bottom: 1px solid #ccc;
}
}
}
.foot-content {
margin-left: 8px;
.text-right {
margin-left: 24px;
}
}
.react-captcha {
position: absolute;
top: 4px;
left: 48vw;
}
}
.login-label-container {
display: inline-block;
margin-left: 8px;
}
效果
Home组件
实现效果
Home/index.jsx文件
import React, { useState } from 'react';
import { Outlet, NavLink } from 'react-router-dom';
import { MenuOutlined, SearchOutlined } from '@ant-design/icons';
import '../../assets/global.css';
import './index.scss';
export default function Home() {
return <>
<div className="top-container style.global">
<MenuOutlined />
<span className="title">音乐吧</span>
<SearchOutlined />
</div>
<div className='tab' >
<NavLink to="/recommend" className={({ isActive }) => isActive ? "selected" : ""}>
<div className="tab-item"><span > 推荐 </span></div>
</NavLink>
<NavLink to="/singers" className={({ isActive }) => isActive ? "selected" : ""}>
<div className="tab-item"><span > 歌手 </span></div>
</NavLink>
<NavLink to="/rank" className={({ isActive }) => isActive ? "selected" : ""}>
<div className="tab-item"><span> 排行榜 </span></div>
</NavLink>
</div>
<Outlet></Outlet>
</>;
}
Home/index.css
.top-container {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 5px 10px;
background: var(--theme-color);
&>span {
line-height: 40px;
color: #f1f1f1;
font-size: 20px;
&.iconfont {
font-size: 25px;
}
}
}
.tab {
height: 44px;
display: flex;
flex-direction: row;
justify-content: space-around;
background: var(--theme-color);
a {
flex: 1;
padding: 2px 0;
font-size: 14px;
color: #e4e4e4;
&.selected {
span {
padding: 3px 0;
font-weight: 700;
color: #f1f1f1;
border-bottom: 2px solid #f1f1f1;
}
}
}
.tab-item {
height: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
}
注意点:
- 声明变量的时候,变量名前面要加两根连词线(--)
- var()函数用于读取变量。
- :root表示的根标签。
body {
--foo: #7F583F;
--bar: #F7EFD2;
}
测试:
轮播图
实现目标:
实现办法有2个你可以用swiper,也可以用antd里面的组件。
react-swipeable 是一个React组件,可以让你的元素响应滑动事件。以下是一个简单的例子,展示如何使用 react-swipeable 来创建一个可以通过滑动来切换图片的简单轮播组件。
下载工具包:
npm i swiper@8.4.6 -S
文档地址: v8.swiperjs.com/react
我选用antd的Swiper组件
import React from 'react';
import { Swiper } from 'antd-mobile';
import imgSrc1 from '@/assets/img/005.jpg';
import imgSrc2 from '@/assets/img/002.jpg';
import imgSrc3 from '@/assets/img/003.jpg';
import imgSrc4 from '@/assets/img/004.jpg';
import './index.css';
const Recommend = () => {
return <div className='recommend-container'>
<div className='mark'></div>
<div className='carousel-content'>
<Swiper autoplay>
<Swiper.Item>
<img src={imgSrc1} alt="图片" />
</Swiper.Item>
<Swiper.Item>
<img src={imgSrc2} alt="图片" />
</Swiper.Item>
<Swiper.Item>
<img src={imgSrc3} alt="图片" />
</Swiper.Item>
<Swiper.Item>
<img src={imgSrc4} alt="图片" />
</Swiper.Item>
</Swiper>
</div>
</div>;
};
export default Recommend;
样式
.recommend-container {
position: relative;
height: 300px;
.mark {
height: 50%;
background: var(--theme-color);
}
.carousel-content {
position: absolute;
top: 0px;
left: 5px;
width: calc(100% - 10px);
margin: 0 auto;
.ant-carousel {
border-radius: 2%;
}
}
}
.slick-list {
border-radius: 5px;
}
优化后
测试
推荐列表
预计实现效果
基础实现如下:
Recommend/index.jsx
import React from 'react';
import Slider from '@/components/Slider';
import imgSrc1 from '@/assets/img/005.jpg';
import imgSrc2 from '@/assets/img/002.jpg';
import imgSrc3 from '@/assets/img/003.jpg';
import imgSrc4 from '@/assets/img/004.jpg';
import RecommendList from '@/components/RecommendList';
import img1 from '@/assets/img/101.jpg';
import img2 from '@/assets/img/102.jpg';
import img3 from '@/assets/img/103.jpg';
import img4 from '@/assets/img/104.jpg';
import img5 from '@/assets/img/105.jpg';
import img6 from '@/assets/img/106.jpg';
const Recommend = () => {
const sliderList = [imgSrc1, imgSrc2, imgSrc3, imgSrc4];
const recommendList = [img1, img2, img3, img4, img5, img6, img1, img2, img3,].map(item => {
return {
id: 1,
picUrl: item,
playCount: 17171122,
name: "朴树、许巍、李健、郑钧、老狼、赵雷"
};
});
return <div>
<Slider sliderList={sliderList}></Slider>
<RecommendList recommendList={recommendList}></RecommendList>
</div>;
};
export default Recommend;
RecommendList/index.jsx
import React from 'react';
import { getCount } from "../../api/utils";
import { AudioOutline } from 'antd-mobile-icons';
import './index.scss';
function RecommendList(props) {
return (
<div className='list-wrapper'>
<h1 className="title">
<div className='circle'>
<div className='white'></div>
</div>
热门推荐
</h1>
<div className='list'>
{
props.recommendList?.map((item, index) => {
return (
<div className='list-item' key={item.id + index}>
<div className="img_wrapper">
<div className="decorate"></div>
{/* 加此参数可以减小请求的图片资源大小 */}
<img src={item.picUrl + "?param=300x300"} width="100%" height="100%" alt="music" />
<div className="play_count">
<AudioOutline />
<span className="count">{getCount(item.playCount)}</span>
</div>
</div>
<div className="desc">{item.name}</div>
</div>
);
})
}
</div>
</div>
);
}
export default React.memo(RecommendList);
RecommendList/index.scss
.list-wrapper {
max-width: 100%;
.title {
display: flex;
align-items: center;
font-weight: 700;
padding-left: 6px;
font-size: 14px;
line-height: 60px;
.circle {
width: 16px;
height: 16px;
background-color: var(--theme-color);
border-radius: 50%;
margin-right: 8px;
.white {
width: 8px;
height: 8px;
background-color: var(--font-color-light);
border-radius: 50%;
position: relative;
top: 4px;
left: 4px;
}
}
}
}
.list {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
}
.list-item {
position: relative;
width: 32%;
.img_wrapper {
.decorate {
position: absolute;
top: 0;
width: 100%;
height: 35px;
border-radius: 3px;
background: linear-gradient (hsla (0, 0%, 43%, .4), hsla (0, 0%, 100%, 0));
}
position: relative;
height: 0;
padding-bottom: 100%;
.play_count {
position: absolute;
right: 2px;
top: 2px;
font-size: var(--font-size-s);
line-height: 15px;
color: var(--font-color-light);
.play {
vertical-align: top;
}
}
img {
position: absolute;
width: 100%;
height: 100%;
border-radius: 3px;
}
}
.desc {
overflow: hidden;
margin-top: 2px;
padding: 0 2px;
height: 50px;
text-align: left;
font-size: var(--font-size-s);
line-height: 1.4;
color: var(--font-color-desc);
}
}
效果
后台数据
数据选择
文档在线:front-end-class.github.io/uniapp-musi…
$ git clone https://github.com/front-end-class/uniapp-music-back-code.git
$ npm install
$ node app.js
进入下载下来的项目的app.js
api地址: front-end-class.github.io/uniapp-musi… 你知道启动了然后既可以在前端里面使用了。
调试接口
npm install axios --save
一个前端一个后端,他们的接口不要一样就好了
在api文档里面找到推荐歌单