项目初始化准备
创建项目:create-react-app 项目名
删除无用文件
建立工程化目录:apis utils views hooks router assets components
快速初始化页面:rfc
安装:npm i sass sass-loader react-router-dom -D
启动项目:npm start
/ npm run start
重置样式结构更改为.scss .jsx文件
index.scss中重置样式:去掉margin padding
百度配置 react @别名:
创建页面
一级页面(占整个页面):登录、注册、layout、电影详情、选座位、选择城市
二级页面:热映、影院、我的
配置路由
配置路由表router/index.js
import Cinema from "@/pages/cinema"
import Citys from "@/pages/citys"
import Detail from "@/pages/detail"
import Layout from "@/pages/layout"
import Login from "@/pages/login"
import Register from "@/pages/register"
import Seat from "@/pages/seat"
import Show from "@/pages/show"
export default[
{
path:'/login',
element:<Login/>
},
{
path:'/register',
element:<Register/>
},
{
path:'/layout',
element:<Layout/>,
children:[
{
path:'show',
element:<Show/>
},
{
path:'cinema',
element:<Cinema/>
},
{
path:'my',
element:<My/>
},
]
},
{
path:'/detail',
element:<Detail/>
},
{
path:'/seat',
element:<Seat/>
},
{
path:'/citys',
element:<Citys/>
},
]
工具函数中计算路由的函数 /utils/getRoutes.js
import { Route } from 'react-router-dom'
import router from '@/router'
import { Suspense } from 'react'
export const getRoutes = (routes = router)=>{
return routes.map(route =>{
return (
<Route path={route.path} key={route.path} element={
<Suspense>
{route.element}
</Suspense>
}>
{
route.children && route.children.length?getRoutes(route.children):""
}
</Route>
)
})
}
App.jsx
import React from 'react'
import { HashRouter, Routes } from 'react-router-dom'
import './App.scss'
import {getRoutes} from './utils/getRoutes'
export default function index() {
return (
<div className='app'>
<HashRouter>
<Routes>
{
getRoutes()
}
</Routes>
</HashRouter>
</div>
)
}
安装:yarn add antd-mobile -D
安装axios:npm i axios
验证码防抖安装:npm i react-simple-verify
/utils/config.js配置
export const baseURL = 'http://129.211.169.131:3333'
封装axios /utils/http.js
import axios from "axios"
import { baseURL } from "./config"
//创建axios实例
const instance = axios.create({
baseURL,//服务器地址
timeout:30000 //超时时间
})
//请求拦截器
instance.interceptors.request.use(
config =>{
return config
},
err =>{
return Promise.reject(err)
}
)
//响应拦截器
instance.interceptors.response.use(
res =>{
return res
},
err =>{
return Promise.reject(err)
}
)
export default instance
模块化api /apis/user
import http from '@/utils/http'
//用户登录
export const api_user_login =(data) =>{
return http({
url:'/login',
method:'POST',
data
})
}
登录页面 /login
import React, { useRef, useState } from 'react'
import { Button, Input, Toast } from 'antd-mobile'
import './index.scss'
import { api_user_login } from '@/apis/user'
import ReactSimpleVerify from 'react-simple-verify'
import 'react-simple-verify/dist/react-simple-verify.css'
import { useNavigate } from 'react-router-dom'
export default function Login() {
let [acc, setAcc] = useState('')
let [pwd, setPwd] = useState('')
let [isNone, setIsNone] = useState(true) // true:隐藏
let verify = useRef(null)
let navigate = useNavigate()
const verifySuccess = async () => {
const res = await api_user_login({
acc,
pwd
})
// console.log(111, res);
if (res.data.code === 0) {
console.log('verify', verify);
// 登陆失败重置验证码
verify.current.reset()
} else {
// 登陆成功跳转至首页
navigate('/layout/show')
}
}
const handleLogin = () => {
console.log('登录', acc, pwd);
if (!acc || !pwd) {
Toast.show({
content: '请输入用户名和密码',
})
return
}
setIsNone(false)
}
return (
<div className='login'>
<div>
<Input placeholder='请输入用户名' clearable value={acc} onChange={val => {
setAcc(val)
}} />
</div>
<Input placeholder='请输入密码' clearable type='password' value={pwd} onChange={val => {
setPwd(val)
}} />
<div style={{ display: isNone ? 'none' : '' }}>
<ReactSimpleVerify ref={verify} success={verifySuccess} />
</div>
<Button block color='primary' fill='solid' onClick={handleLogin}>
登录
</Button>
</div>
)
}
框架layout,使用tabBar
import { TabBar } from 'antd-mobile'
import React, { useEffect, useState } from 'react'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import './index.scss'
import {
AppOutline,
CalendarOutline ,
UserOutline,
} from 'antd-mobile-icons'
export default function Layout() {
let [curKey,setCurkey] = useState('show')
// 可以从useLocation hooks函数中获取路由的信息
const { pathname } = useLocation()
useEffect(()=>{
let key = pathname.substring(pathname.lastIndexOf('/')+1)
setCurkey(key)
},[])
const navigate = useNavigate()
const handleChange = (key) =>{
console.log(key)
setCurkey(key)
navigate(`/layout/${key}`)
}
return (
<div className='layout'>
<Outlet/>
<div className='tabbar'>
<TabBar onChange={handleChange} activeKey={curKey}>
<TabBar.Item key='show' title="热映" icon={<AppOutline />}/>
<TabBar.Item key='cinema' title="影院" icon={<CalendarOutline />}/>
<TabBar.Item key='my' title="我的" icon={<UserOutline />}/>
</TabBar>
</div>
</div>
)
}
热映界面 /layout/show
import React, { Suspense } from 'react'
import './index.scss'
import { Tabs } from 'antd-mobile'
import Showing from './components/showing'
import WillShow from './components/willShow'
export default function Show() {
return (
<div className='show'>
<Tabs>
<Tabs.Tab title='正在热映' key='show'>
<Suspense>
<Showing />
</Suspense>
</Tabs.Tab>
<Tabs.Tab title='即将上映' key='vegetables'>
<Suspense>
<WillShow />
</Suspense>
</Tabs.Tab>
</Tabs>
</div>
)
}
/show/components/movieItem
import { baseURL } from '@/utils/config'
import { Button } from 'antd-mobile'
import React from 'react'
import './index.scss'
export default function MovieItem(props) {
// console.log(props)
const {actors,director,image,name,point,type} = props
return (
<div className='movie-item'>
<div className='left'>
<img src={baseURL+image} />
</div>
<div className='right'>
<div className='cont'>
<h3>{name}</h3>
<p>淘票票评分{point}</p>
<p>导演:{director}</p>
<p>主演:{actors}</p>
</div>
<div className='btn'>
<Button block shape='rounded' color='danger' size='small'>{type===1?'购票':'预售'}</Button>
</div>
</div>
</div>
)
}
/show/components/showing
import React, { useEffect, useState } from 'react'
import {api_search_movielist} from '@/apis/showing'
import MovieItem from '../movieItem'
export default function Showing() {
let [list,setList] = useState([])
useEffect(()=>{
getList()
})
const getList = async() =>{
const res = await api_search_movielist({
state:1
})
// console.log(res)
setList(res.data.data)
}
return (
<div>
{
list.map(item => {
return <MovieItem key={item.id} {...item} type={1}/>
})
}
</div>
)
}
/show/components/willShow
import React, { useEffect, useState } from 'react'
import {api_search_movielist} from '@/apis/showing'
import MovieItem from '../movieItem'
export default function WillShow() {
let [list,setList] = useState([])
useEffect(()=>{
getList()
})
const getList = async() =>{
const res = await api_search_movielist({
state:2
})
// console.log(res)
setList(res.data.data)
}
return (
<div>
{
list.map(item => {
return <MovieItem key={item.id} {...item} type={2}/>
})
}
</div>
)
}
H5App使用高德地图,获取当前城市
地图JS API -》定位-》地图初始化时加载定位到当前城市-》相关示例=》获取经纬度
在Index.html中引入,此处 key:是自己申请
控制台 -> 应用管理 -> 我的应用 -> 创建新应用(如果没有的话) -> 添加(服务平台选择:web端(JS API))
<!-- 引入高德地图的IP城市定位文件 -->
<script type="text/javascript" src="//webapi.amap.com/maps?v=2.0&key=31d40fd2642d893417c1752a510f3591"></script>
获取经纬度后,再调用web服务API 逆地理编码API去获取城市
//初始化地图时,若center属性缺省,地图默认定位到用户所在城市的中心
let map = new AMap.Map('container', {
zoom: 11,
});
console.log(map.getCenter())
// 获取经纬度
let {lng,lat} = map.getCenter()
在http.js中添加
// 高德服务
export const amap_server = axios.create({
baseURL: 'https://restapi.amap.com',
timeout: 30000
})
在apis/amap.js中
/**
* https://restapi.amap.com ---> 高德服务器地址
* /v3/geocode/regeo ---> 接口
* ---- 参数 ----
* key ---> 用户在高德地图官网申请Web服务API类型Key
* location ---> 经纬度坐标 (经度,纬度)
*/
import { amap_server } from '@/utils/http'
export const api_amap_geocode = (params) => {
return amap_server({
url: '/v3/geocode/regeo',
method: 'GET',
params
})
}
获取到城市(不知道为啥一直获取的是北京)
import { api_amap_geocode } from '@/apis/amap'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import './index.scss'
export default function Cinema() {
// 当前城市
let [city, setCity] = useState('定位中...')
let navigate = useNavigate()
useEffect(() => {
//初始化地图时,若center属性缺省,地图默认定位到用户所在城市的中心
let map = new AMap.Map('container', {
zoom: 11,
});
// console.log(map.getCenter())
// 获取经纬度
let {lng,lat} = map.getCenter()
// 发送逆地理编码的请求
api_amap_geocode({
// 此处key值是 web服务的key
key: '959ea2812874784b56a44afb19d96219',
location: lng + ',' + lat
}).then(res => {
// console.log(res)
setCity(res.data.regeocode.addressComponent.province)
})
}, [])
//跳转到城市列表页
const handleJumpCity = () =>{
navigate('/citys')
}
return (
<div className='cinema'>
<p onClick={handleJumpCity}>{city}</p>
<div id="container" style={{ display: 'none' }}></div>
</div>
)
}
获取城市列表,并渲染
http.js中
// 城市
export const city_server = axios.create({
baseURL: 'http://129.211.169.131:6368',
timeout: 30000
})
apis/city.js中
import { city_server } from '@/utils/http'
// 获取城市数据
export const api_get_citys = () => {
return city_server({
url: '/citys',
method: 'GET'
})
}
通过BetterScroll来联动城市选择
官网1.x:https://better-scroll.github.io/docs-v1/#/
安装:npm install better-scroll@1.11.1
引入:import BScroll from 'better-scroll'
使用:
// 实例化BScroll
let scroll = new BScroll('滚动容器')
// 滚动到指定元素
scroll.scrollToElement(元素, 滚动动画时间)
实现代码/citys/index.jsx
import { api_get_citys } from '@/apis/city'
import React, { useEffect, useState } from 'react'
import './index.scss'
import BScroll from 'better-scroll'
let scroll = null
export default function ChooseCity() {
const [cityList, setCityList] = useState([
// {
// letter: 'C',
// citys: [{
// city_name: "重庆",
// city_pinyin: "Chongqing",
// city_pre: "C",
// city_short: "cq",
// count: "230",
// id: "41"
// }
// ]
// },
])
useEffect(() => {
getCitys()
// 实例化BScroll
scroll = new BScroll('.city-cont')
console.log('scroll', scroll);
}, [])
// 获取城市列表数据
const getCitys = async () => {
const res = await api_get_citys()
getCityData(res.data)
}
// 处理城市数据
const getCityData = (data) => {
// 过滤首字母
let letter = data.map(item => item.city_pre.toUpperCase())
// 去重
letter = [...new Set(letter)]
// 排序
letter.sort()
let list = []
letter.forEach(item => {
let obj = {
letter: item,
citys: []
}
data.forEach(v => {
if (item === v.city_pre.toUpperCase()) {
obj.citys.push(v)
}
})
list.push(obj)
})
setCityList(list)
}
// 点击右侧字母
const handleLetter = (id) => {
scroll.scrollToElement('#' + id, 300)
}
return (
<div className="city-container">
<div className="city-cont">
<div>
{
cityList.map(item => {
return (
<div id={item.letter} key={item.letter} className="city-type-wrap">
<div className="city-letter">
<span>{item.letter}</span>
</div>
<div className="city-list-wrap">
{
item.citys.map(city => {
return (
<span className="city-name" key={city.id}>{city.city_name}</span>
)
})
}
</div>
</div>
)
})
}
</div>
</div>
<div className="letter">
{
cityList.map((item, i) => {
return <div key={i} onClick={() => { handleLetter(item.letter) }} className="letter-item">{item.letter}</div>
})
}
</div>
</div>
)
}
样式文件
.city-container {
background-color: #fff;
height: 100%;
overflow: hidden;
.city-cont {
padding: 10px;
height: 100%;
overflow-y: scroll;
}
.city-type-wrap {
.city-letter {
padding: 5px 0;
}
.city-list-wrap {
display: flex;
flex-wrap: wrap;
.city-name {
padding: 13px 18px;
}
}
}
.letter {
position: fixed;
right: 0;
top: 0;
bottom: 0;
width: 20px;
display: flex;
flex-direction: column;
justify-content: center;
.letter-item {
padding: 5px 0;
width: 100%;
text-align: center;
}
}
}
获取影院列表并进行渲染
cinema完整代码,带参跳转到影院详情页,react的三种路由传参方式
import { api_amap_geocode } from '@/apis/amap'
import { api_cenema_list } from '@/apis/showing'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import './index.scss'
export default function Cinema() {
// 当前城市
let [city, setCity] = useState('定位中...')
// 影院列表
let [cinemaList, setCinemaList] = useState([
// {
// address: "成华区建设北路3段6号龙湖三千集购物中心5号楼",
// cid:1,
// name:"UME影城(成都成华店)",
// price:28.9,
// type:"3D 观影小食 DMAX 可停车",
// }
])
let navigate = useNavigate()
//获取城市
useEffect(() => {
//初始化地图时,若center属性缺省,地图默认定位到用户所在城市的中心
let map = new AMap.Map('container', {
zoom: 11,
});
// console.log(map.getCenter())
// 获取经纬度
let { lng, lat } = map.getCenter()
// 发送逆地理编码的请求
api_amap_geocode({
// 此处key值是 web服务的key
key: '959ea2812874784b56a44afb19d96219',
location: lng + ',' + lat
}).then(res => {
// console.log(res)
setCity(res.data.regeocode.addressComponent.province)
})
}, [])
//跳转到城市列表页
const handleJumpCity = () => {
navigate('/citys')
}
//获取影院
useEffect(() => {
getCinemaList()
},[])
//获取影院列表
const getCinemaList = async () => {
const res = await api_cenema_list()
// console.log(res)
if (res.data.code === 1) {
setCinemaList(res.data.data)
}
}
//跳转到详情页
const handleJumpDetail = (item) => {
// console.log(item)
//react路由的三种传参方式
// navigate('/detail/' + JSON.stringify(item))
// search方式
// navigate('/detail?cinemaInfo=' + JSON.stringify(item))
// state方式
navigate('/detail', {
state: item
})
}
return (
<div className='cinema'>
<p onClick={handleJumpCity}>{city}</p>
<div id="container" style={{ display: 'none' }}></div>
<div className='list-wrap'>
{
cinemaList.map(item => {
return (
<div className='list' key={item.cid} onClick={() => handleJumpDetail(item)}>
<div className='title'>
<h3>{item.name}</h3>
<span className='price'>¥{item.price}起</span>
</div>
<h6>{item.address}</h6>
<p className='type'>{item.type}</p>
</div>
)
})
}
</div>
</div>
)
}
react的三种路由跳转传参方式
方式一:
//跳转传参
const handleJumpDetail = (item) => {
// console.log(item)
navigate('/detail/' + JSON.stringify(item))
}
//router.js
{
path:'/detail/:cinemaInfo',
element:<Detail />
}
//获取
// 刷新不会丢失
const params = JSON.parse(useParams().cinemaInfo)
方式二:传递的是search参数,路由不需要像上面那样配置
//传参
const handleJumpDetail = (item) => {
// console.log(item)
// search方式
navigate('/detail?cinemaInfo=' + JSON.stringify(item))
}
//获取
// search接收 刷新不会丢失
const [searchParams] = useSearchParams()
console.log('searchParams', searchParams.get('cinemaInfo'));
方式三:传递的是state参数 【推荐】
const handleJumpDetail = (item) => {
// console.log(item)
// state方式
navigate('/detail', {
state: item
})
}
// state接收 刷新不会丢失
const { state } = useLocation()