案例项目基本需求分析
前端页面布局使用Chakra-UI
后端服务使用Next.js
样式定义采用CSS-in-JS的方案,使用emotion库(需要在Next.js扩展babel配置)
Next.js 与Chakra-UI结合使用实现项目页面的功能
首页(列表页)的轮播图,以及影视资源列表展示
影视详情页使用基因动态路由的静态生成
项目代码初始化
运行安装依赖包命令:
npm init next-app aimovie
cd aimovie
#npm i @chakra-ui/react
#npm i @chakra-ui/react@^1 @emotion/react@^11 @emotion/styled@^11 framer-motion@^6
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
npm i react-icons
npm i @babel/core @emotion/babel-preset-css-prop -D
{
"name": "aimovie",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@babel/core": "^7.21.0",
"@chakra-ui/react": "^2.5.1",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"framer-motion": "^10.2.5",
"next": "13.2.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.8.0"
}
}
创建pages目录下的 _app.js 文件:
// import '@/styles/globals.css'
// pages/_app.js
import { ChakraProvider, CSSReset } from '@chakra-ui/react'
import theme from '../chakra'
function App ({ Component, pageProps }) {
return (
<ChakraProvider theme={theme}>
<CSSReset />
<Component {...pageProps} />
</ChakraProvider>
)
}
export default App
创建 .babelrc 文件作为babel的配置文件:(在新版的next.js中不需要配置,默认开启了cssProp,否则会产生next/font冲突)
{
"presets": [
"next/babel",
"@emotion/babel-preset-css-prop"
]
}
页面组件规划与布局实现
实现页面头部组件
头部组件包含三部分:左侧的登录注册按钮组件 中间的网站logo 右边的搜索按钮。
布局规划:左浮动 有浮动 中间logo始终居中。
创建components文件夹,用于存放非页面层级的组件。
头部组件基本布局:
import React from 'react'
import { Box, Button, Container, Image } from '@chakra-ui/react'
import { FaSearch, FaSignInAlt, FaUserAlt } from 'react-icons/fa'
import styled from '@emotion/styled'
import { css } from '@emotion/react'
const SignInAndJoin = styled.div`
height: 52px;
line-height: 52px;
color: #fff;
border-left: 1px solid #393939;
border-right: 1px solid #393939;
padding: 0 6px;
float: left;
`
const logo = css`
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 140px;
height: auto;
`
const Search = styled.a`
float: right;
height: 52px;
border-left: 1px solid #393939;
border-right: 1px solid #393939;
color: #fff;
padding: 0 10px;
display: flex;
align-items: center;
`
export default function Header () {
return (
<Box h='52px' bgColor='#202020' borderBottom='1px solid #393939'>
<Container pos='relative' h='52px' maxW='1200px'>
<SignInAndJoin>
<Button
mr='5px'
leftIcon={<FaSignInAlt />}
colorScheme='teal'
variant='solid'
>
登录
</Button>
<Button
leftIcon={<FaUserAlt />}
colorScheme='orange'
variant='outline'
>
注册
</Button>
</SignInAndJoin>
<Image css={logo} src='/images/logo.png' />
<Search>
<FaSearch size='16px' title='搜索' />
</Search>
</Container>
</Box>
)
}
实现导航组件布局
import React from 'react'
import { Box, HStack, Link } from '@chakra-ui/react'
import NextLink from 'next/link'
import styles from '@/styles/Navigation.module.css'
import { useRouter } from 'next/router'
export default function Navigation () {
const router = useRouter()
const isActiveLink = href => router.asPath === href
return (
<Box height='52px' bgColor='#202020' color='#fff'>
<HStack h='100%' spacing={3} justifyContent='center' alignItems='center'>
<Link
className={`${styles.navlink} ${
isActiveLink('/') ? styles.active : ''
}`}
href='/'
as={NextLink}
>
影片
</Link>
<Link
className={`${styles.navlink} ${
isActiveLink('/1') ? styles.active : ''
}`}
href='/1'
as={NextLink}
>
漫画
</Link>
<Link
className={`${styles.navlink} ${
isActiveLink('/2') ? styles.active : ''
}`}
href='/2'
as={NextLink}
>
电影
</Link>
<Link
className={`${styles.navlink} ${
isActiveLink('/3') ? styles.active : ''
}`}
href='/3'
as={NextLink}
>
电视
</Link>
<Link
className={`${styles.navlink} ${
isActiveLink('/4') ? styles.active : ''
}`}
href='/4'
as={NextLink}
>
新闻资讯
</Link>
</HStack>
</Box>
)
}
实现轮播图组件布局
这里使用到一个第三方组件react-responsive-carousel
npm i react-responsive-carousel
import React from 'react'
import { Carousel } from 'react-responsive-carousel'
import 'react-responsive-carousel/lib/styles/carousel.min.css'
import { css } from '@emotion/react'
import { Box, Stack, Heading, Text, Button } from '@chakra-ui/react'
import styled from '@emotion/styled'
const CarouselItem = styled.div`
position: relative;
& > div {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
color: #fff;
width: 80%;
height: 100%;
max-width: 1200px;
text-align: left;
& > h2 {
width: 450px;
}
& > p {
margin: 15px 0;
width: 450px;
}
}
& > img {
filter: brightness(50%);
}
`
const swiperContainer = css`
& > .carousel-root {
position: relative;
& > .carousel:last-child {
position: absolute;
left: 0;
bottom: 0;
& > .thumbs-wrapper > .thumbs {
display: flex;
justify-content: center;
}
}
}
`
export default function Swiper () {
return (
<Box css={swiperContainer}>
<Carousel
// autoPlay
infiniteLoop
emulateTouch
showArrows={false}
showIndicators={false}
showStatus={false}
>
<CarouselItem>
<img src='/images/1.jpg' />
<Stack justifyContent='center'>
<Heading as='h1' size={'lg'}>
King In Black
</Heading>
<Text>
The next shock chapter in Donny Cates and Ryan Stegman hello look
at me! shock chapter in Donny Cates and Ryan Stegman hello look at
me!
</Text>
<Button colorScheme='red' w='150px'>
Go To This
</Button>
</Stack>
</CarouselItem>
<CarouselItem>
<img src='/images/2.jpg' />
<Stack justifyContent='center'>
<Heading as='h1' size={'lg'}>
King In Black
</Heading>
<Text