赞
踩
前端页面布局使用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>
- 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/3.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>
- </Carousel>
- </Box>
- )
- }
- import React from 'react'
- import { Box, Heading, HStack, Text } from '@chakra-ui/react'
- import { FaFilm } from 'react-icons/fa'
- import { MdMovie } from 'react-icons/md'
-
- export default function Movie () {
- return (
- <Box maxW='1200px' mx='auto' mt='20px'>
- <HStack fontSize='24px' my='10px'>
- <MdMovie size='24px' />
- <Heading as='h3'>电影</Heading>
- </HStack>
- <HStack mt='20px' spacing={3}>
- <Box w={'290px'}>
- <img src='/images/item_1.jpg' />
- <Text mt='10px' as='section'>
- Expected corresponding JSX closing tag for Black Hero
- </Text>
- </Box>
- <Box w={'290px'}>
- <img src='/images/item_1.jpg' />
- <Text mt='10px' as='section'>
- Expected corresponding JSX closing tag for Black Hero
- </Text>
- </Box>
- <Box w={'290px'}>
- <img src='/images/item_1.jpg' />
- <Text mt='10px' as='section'>
- Expected corresponding JSX closing tag for Black Hero
- </Text>
- </Box>
- <Box w={'290px'}>
- <img src='/images/item_1.jpg' />
- <Text mt='10px' as='section'>
- Expected corresponding JSX closing tag for Black Hero
- </Text>
- </Box>
- <Box w={'290px'}>
- <img src='/images/item_1.jpg' />
- <Text mt='10px' as='section'>
- Expected corresponding JSX closing tag for Black Hero
- </Text>
- </Box>
- <Box w={'290px'}>
- <img src='/images/item_1.jpg' />
- <Text mt='10px' as='section'>
- Expected corresponding JSX closing tag for Black Hero
- </Text>
- </Box>
- </HStack>
- </Box>
- )
- }
- import Layout from '@/components/Layout'
- import React from 'react'
- import { Box, HStack, Text, Heading, Divider } from '@chakra-ui/react'
- import { css } from '@emotion/react'
-
- const DetailContainer = css`
- padding: 10px;
- & > p {
- font-size: 14px;
- margin-bottom: 10px;
- }
- & > img {
- display: block;
- margin-bottom: 10px;
- }
- `
- export default function Detail () {
- return (
- <Layout>
- <Box maxW='1200px' mx='auto' width='80%' mt='70px'>
- <Heading as='h2' size='xl'>
- Marvel Mission recap: Captain Marvel's Star of Hala
- </Heading>
- <Heading
- as='h4'
- size='lg'
- mt='10px'
- color='gray.500'
- fontWeight='light'
- >
- The result are out this world!
- </Heading>
- <Divider mt='10px' />
- <HStack
- overflow='hidden'
- justifyContent='space-between'
- spacing={3}
- my='10px'
- >
- <Text>作者: MarkTony</Text>
- <Text>发布时间: 2040-05-10</Text>
- </HStack>
- <Divider mt='10px' />
- <Box css={DetailContainer}>
- <p>
- event - compiled client and server successfully in 377 ms (1213
- modules) wait - compiling... event - compiled client and server
- successfully in 520 ms (1213 modules) wait - compiling... event -
- compiled client and server successfully in 646 ms (1213 modules)
- wait - compiling...
- </p>
- <p>
- event - compiled client and server successfully in 377 ms (1213
- modules) wait - compiling... event - compiled client and server
- successfully in 520 ms (1213 modules) wait - compiling... event -
- compiled client and server successfully in 646 ms (1213 modules)
- wait - compiling...
- </p>
- <p>
- event - compiled client and server successfully in 377 ms (1213
- modules) wait - compiling... event - compiled client and server
- successfully in 520 ms (1213 modules) wait - compiling... event -
- compiled client and server successfully in 646 ms (1213 modules)
- wait - compiling...
- </p>
- <p>
- event - compiled client and server successfully in 377 ms (1213
- modules) wait - compiling... event - compiled client and server
- successfully in 520 ms (1213 modules) wait - compiling... event -
- compiled client and server successfully in 646 ms (1213 modules)
- wait - compiling...
- </p>
- </Box>
- </Box>
- </Layout>
- )
- }
这里我们需要使用到axios库来发送请求以获取我们所需的数据:
npm i axios
在根目录下创建一个axiosConfig.js文件:
- export const baseURL = 'http://localhost:3000'
- export default {
- // thrid-party API base URL
- baseURL
- }
在pages/api目录下创建swiper.js的文件,模拟获取swiper的API供组件调用:
- import { baseURL as url } from '@/axiosConfig'
-
- export default function swiper (req, res) {
- res.status(200).json([
- {
- id: 1,
- title: 'Event-Sized Episode!',
- description:
- "Paul Scheer and Steve Wacker are joined by Anthony Carboni of 'The Star Wars Show' for an event sized episode!",
- url: `${url}/apiresources/images/api_swiper_1.jpg`,
- vid: 1
- },
- {
- id: 2,
- title: 'Time Travel Tips',
- description:
- 'Traveling back in time is never easy? Let us help by consulting the pros!',
- url: `${url}/apiresources/images/api_swiper_2.jpg`,
- vid: 2
- },
- {
- id: 3,
- title: 'KING IN BLACK',
- description:
- "The next shocking chapter in Donny Cates and Ryan Stegman's Venom Saga is revealed!",
- url: `${url}/apiresources/images/api_swiper_3.jpg`,
- vid: 3
- },
- {
- id: 4,
- title: "LET'S PLAY FORTNITE",
- description:
- 'Watch as we stream the brand new Captain America outfit in Fortnite!',
- url: `${url}/apiresources/images/api_swiper_4.jpg`,
- vid: 4
- },
- {
- id: 5,
- title: 'HAPPY ULTRAMAN DAY!',
- description:
- "Celebrate by getting a sneak peek at 'Rise of Ultraman #1'!",
- url: `${url}/apiresources/images/api_swiper_5.jpg`,
- vid: 5
- }
- ])
- }
components/Swiper.js组件文件中,创建并导出loadSwiper函数:
- // components/Swiper.js
- export function loadSwiper () {
- // 这离获取数据不建议从本地通过API接口获取,而应该通过封装后端的方法函数直接操作数据库获取
- // 如果是第三方API可以通过API接口获取数据
- return axios.get('/api/swiper', { baseURL: 'http://localhost:3000/' })
- }
然后在首页的Home组件(pages/index.js)中,创建并导出getStaticProps函数,并且在Home组件中解构组件的props获取swiper数据对象,把swiper作为data属性传递给Swiper组件:
- import Image from 'next/image'
- // import { Inter } from 'next/font/google'
- import styles from '@/styles/Home.module.css'
- import Swiper, { loadSwiper } from '@/components/Swiper'
- import Movie from '@/components/Movie'
- import Layout from '@/components/Layout'
-
- // const inter = Inter({ subsets: ['latin'] })
-
- export default function Home ({ swiper }) {
- return (
- <>
- <Layout>
- <Swiper data={swiper} />
- <Movie />
- </Layout>
- </>
- )
- }
-
- export async function getStaticProps () {
- let { data: swiper } = await loadSwiper()
- return {
- props: {
- swiper
- }
- }
- }
使用传递给components/Swiper.js组件的数据修改组件内容展示:
- 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'
- import axios from 'axios'
- import { useRouter } from 'next/router'
-
- 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 15px;
- width: 450px;
- font-size: 14px;
- }
- }
- & > 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 ({ data }) {
- const router = useRouter()
- return (
- <Box css={swiperContainer}>
- <Carousel
- // autoPlay
- infiniteLoop
- emulateTouch
- showArrows={false}
- showIndicators={false}
- showStatus={false}
- >
- {data.map(swiper => (
- <CarouselItem key={swiper.id}>
- <img src={swiper.url} />
- <Stack justifyContent='center'>
- <Heading as='h2' fontSize='4xl'>
- {swiper.title}
- </Heading>
- <Text>{swiper.description}</Text>
- <Button
- colorScheme='red'
- w='120px'
- size='lg'
- onClick={() =>
- router.push({
- pathname: '/detail/[id]',
- query: { id: swiper.vid }
- })
- }
- >
- CHECK DETAIL
- </Button>
- </Stack>
- </CarouselItem>
- ))}
- </Carousel>
- </Box>
- )
- }
-
- export function loadSwiper () {
- // 这离获取数据不建议从本地通过API接口获取,而应该通过封装后端的方法函数直接操作数据库获取
- // 如果是第三方API可以通过API接口获取数据
- return axios.get('/api/swiper', { baseURL: 'http://localhost:3000/' })
- }
在pages/api目录下创建movie.js的文件,模拟获取movie的API供组件调用:
- import { baseURL as url } from '@/axiosConfig'
-
- export default function movie (req, res) {
- res.status(200).json([
- {
- id: 1,
- vid: 6,
- url: `${url}/apiresources/images/api_movie_1.jpg`,
- title: 'Marvel Mission Recap: Captain Marvel’s Star of Hala'
- },
- {
- id: 2,
- vid: 7,
- url: `${url}/apiresources/images/api_movie_2.jpg`,
- title: 'Make Your Video Calls Worthy With These Backgrounds'
- },
- {
- id: 3,
- vid: 8,
- url: `${url}/apiresources/images/api_movie_3.jpg`,
- title: 'Make Your Video Calls Worthy With These Backgrounds'
- },
- {
- id: 4,
- vid: 9,
- url: `${url}/apiresources/images/api_movie_4.jpg`,
- title:
- 'Marvel At Home: Here’s How to Stay Connected With Your Favorite Super Heroes'
- }
- ])
- }
components/Movie.js组件文件中,创建并导出loadMovie函数:
- export function loadMovie () {
- return axios.get('/api/movie', { baseURL })
- }
然后在首页的Home组件(pages/index.js)中,创建并导出getStaticProps函数,并且在Home组件中解构组件的props获取movie数据对象,把movie作为data属性传递给Movie组件:
- import Image from 'next/image'
- // import { Inter } from 'next/font/google'
- import styles from '@/styles/Home.module.css'
- import Swiper, { loadSwiper } from '@/components/Swiper'
- import Movie, { loadMovie } from '@/components/Movie'
- import Layout from '@/components/Layout'
-
- // const inter = Inter({ subsets: ['latin'] })
-
- export default function Home ({ swiper, movie }) {
- return (
- <>
- <Layout>
- <Swiper data={swiper} />
- <Movie data={movie} />
- </Layout>
- </>
- )
- }
-
- export async function getStaticProps () {
- const { data: swiper } = await loadSwiper()
- const { data: movie } = await loadMovie()
- return {
- props: {
- swiper,
- movie
- }
- }
- }
使用传递给components/Movie.js组件的数据修改组件内容展示:
- import React from 'react'
- import { Box, Heading, HStack, Text, Image } from '@chakra-ui/react'
- import { FaFilm } from 'react-icons/fa'
- import { MdMovie } from 'react-icons/md'
- import { baseURL } from '@/axiosConfig'
- import axios from 'axios'
- import { useRouter } from 'next/router'
-
- export default function Movie ({ data }) {
- const router = useRouter()
- return (
- <Box maxW='1200px' mx='auto' px='10px' mt='20px'>
- <HStack>
- <MdMovie size='18px' />
- <Heading as='h3' fontSize='18px'>
- 电影
- </Heading>
- </HStack>
- <HStack
- mt='20px'
- spacing='0'
- flexFlow='wrap'
- justifyContent='space-between'
- >
- {data.map(movie => (
- <Box
- onClick={() => router.push(`/detail/${movie.vid}`)}
- key={movie.id}
- w='290px'
- cursor='pointer'
- >
- <Image w='290px' src={movie.url} />
- <Text h='52px' overflow='hidden' mt='10px' as='section'>
- {movie.title}
- </Text>
- </Box>
- ))}
- </HStack>
- </Box>
- )
- }
-
- export function loadMovie () {
- return axios.get('/api/movie', { baseURL })
- }
详情页属于基于动态路由的静态生成,首先我们要做的就是在首页点击链接或按钮跳转到对应的详情页。
这需要使用到Link或者next/router。
上面的页面已经实现了此功能,这里就不再重复。
首先,创建pages/api/videos.js 来模拟获取所有video id列表的API:
- import { baseURL as url } from '@/axiosConfig'
-
- export const videos = [
- {
- id: '1',
- title:
- "It's an Event-Sized Episode of 'Marvel Presents: The World's Greatest Book Club with Paul Scheer' in Celebration of 'Empyre'",
- sub:
- "Paul Scheer and Steve Wacker are joined by Anthony Carboni of 'The Star Wars Show'!",
- author: 'JAMIE FREVELE',
- publish: '2050-05-26',
- content:
- "<p>Time for a new episode of Marvel Presents: The World's Greatest Book Club with Paul Scheer -- and this one, Marvelites, is super-sized! Why? Because there's a new Marvel comic event in our midst, and EMPYRE deserves no less than a big celebration with hosts Paul Scheer and Steve Wacker! Paul and Steve are joined by guest Anthony Carboni (The Star Wars Show) for a calamitous conversation about other notable Marvel events.</p><video controls src='" +
- url +
- "/apiresources/videos/1.mp4'></video><p>But first -- EMPYRE! Steve provided an inside look at the creation of the intergalactic conflict and what Marvel fans can expect:</p><p>“What [writers Al Ewing and Dan Slott] definitely wanted to get away from was making it a [Fantastic Four] versus the Avengers, yet another story where friends fight each other and try to kill each other. Much like this show.”</p><p>He went on to predict the lasting effects of EMPYRE on the Marvel Universe:</p><p>“There are some big changes coming, and I think when we’re in our sweet spot is when we at Marvel are a little nervous about how the fans are going to react. It’s our job to put them through the ringer, to put them through hell. I think EMPYRE is not the story at the beginning that you think it is.”</p>"
- },
- {
- id: '2',
- title: "Time Travel Tips from 'Marvel's Agents of S.H.I.E.L.D.'",
- sub:
- 'Traveling back in time is never easy? Let us help by consulting the pros!',
- author: 'CHRISTINE DINH',
- publish: '2050-03-13',
- content:
- "<img src='" +
- url +
- "/apiresources/images/detail_2.jpg'/><p>Look, we all know hopping through the decades ain't easy. In fact, who can keep up with all the different rules of time travel.</p><video controls src='" +
- url +
- "/apiresources/videos/2.mp4'></video><p>Luckily, we know a bunch of experts. During the production of Marvel's Agents of S.H.I.E.L.D. Season 7, Marvel.com had the opportunity to consult the cast and showrunners how to remain composure while navigating time travel. Watch what they have to say, learn the Do's and Don't's, and word of advice, it's probably best to avoid the shenanigans of Deke Shaw. We haven't forgotten the events of Season 6, Deke 声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/446473推荐阅读
相关标签
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。