没事造轮子没事造轮子

没事造轮子

网站首页实现

  • W_Z_C
  • 阅读约 11 分钟
网站首页实现

上篇文章建立了网站的初始框架,本篇文章需要在此基础之上逐步完善模块细节。按照前文提到的网站内容发布流程,首先需要创建三个文件夹,分别用来存放原始的 Markdown 文件、翻译后的国际化文件以及数据库文件。除此之外,我们尽量将源码放到一块,也存放到一个特定的文件夹内,经过整理目录结构如下:

blog             
 ├─ dbs                 //存放数据库
 ├─ docs                //存放原始 Markdown 文件
 ├─ i18n                //存放国际化需要的文件
 ├─ public              
 │    ├─ favicon.ico      
 │    └─ vercel.svg       
 ├─ src             
 |   ├─ components      //网站组件
 │   ├─ pages           //网站页面
 │   │    ├─ index.js      
 │   │    └─ _app.js       
 │   └─ styles
 │        └─ globals.css   
 ├─ next.config.js  
 ├─ package.json        
 ├─ postcss.config.js   
 ├─ README.md
 └─ tailwind.config.js           

dbs 用来存放以后使用的数据库文件,docs 用来存放原始 Markdown 文件,i18n 用来存放翻译后的文件,src 用来存放源码文件,现在已经将原来的 pages 和 styles 目录移动到里面,除此之外还新建了一个 components 的文件夹,以后用来存放系统组件。

因为改变了目录结构,所以需要同时修改引用这些目录文件的代码路径:

tailwind.config.js
module.exports = {  content: [     "./src/pages/**/*.{js,ts,jsx,tsx}",     "./src/components/**/*.{js,ts,jsx,tsx}",  ],  theme: {    extend: {},  },  plugins: [],}

重新运行 npm run dev,打开 http://localhost:3000/ 查看运行是否正常。

1. Nextjs 中的页面系统

Nextjs 中如果想要创建页面,只需要在 pages 文件夹下面添加一个文件,因为 Nextjs 的路由系统是基于文件系统的,所以该文件的路径对应着访问该文件的 URL,例如:如果增加 home.js 文件,则可以通过 http://localhost:3000/home 路径访问。

默认情况下,Nextjs 会预渲染每个页面,这表明 Nextjs 会将 pages 文件下的每个文件生成对应的 HTML 文件,每个生成的 HTML 文件都与该页面最少的 JavaScript 代码相关联,当页面加载时,JavaScript 代码将会运行显示最终的完整页面,这个处理过程被称为水合(hydration)。

Nextjs 初始状态下已经存在了一个 index.js 文件,该文件就是咱们的默认主页,修改它即可完成对主页样式的修改。

传统页面一般可以粗略划分为三个部分:页头、内容和页脚,有些页面还存在侧边栏之类的东西,因为咱们这里是主页,所以暂时不需要。

2. 页头和页脚

页头对于一个网站来说非常重要,因为网站的所有页面都需要显示页头,其主要包含网站的 logo、网站标题、菜单栏等内容。

因为页头会被多个页面复用,因此直接在 components 文件夹下创建一个页头组件,名称叫做 Header.js

Header.js
export function Header() {
    return (
        <div>页头</div>
    )
}

用同样的方式,创建一个页脚:

Footer.js
export function Footer() {
    return (
        <div>页脚</div>
    )
}

然后更改主页代码,引入它们:

index.js
import { Header } from "../components/Header"import { Footer } from "../components/Footer"
export default function Home() { return (   <>     <Header />        <h1 className="text-center text-3xl font-bold underline">          Hello world!        </h1>     <Footer />   </> )}

如果一切顺利,访问 http://localhost:3000 可以看到页面上方和下方分别显示 “页头”和“页脚”,这说明网站的组件运行正常,接下来就可以专注实现组件的内容了。

3. 完善页头

其实接下来的工作主要是前端 CSS 方面的内容,没啥好介绍的,可以按照自己的规划写 HTML 和 CSS,或者干脆直接复制下面的代码查看效果即可。因为需要使用动画、弹窗之类的效果,所以要添加额外的第三方组件:

npm install @headlessui/react

除此之外,还需要添加头部使用的图片,特别是网站的 logo。首先咱们在根目录的 public 文件中增加一个 assert 文件夹目录,然后在其内部建立一个 images 文件夹,最后将咱们的网站 logo 文件放进去,当然你也可以不显示 logo 直接显示网站标题,得看你自己的网站样式规划是什么样子的,完成后整个目录的样式如下:

└── public/
    ├── assert/
    │   └── images/
    │       └── logo.png
    └── favicon.ico

接着修改页头组件代码:

页头代码
Header.js
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Popover, Tab, Transition } from '@headlessui/react'
import Link from 'next/link'

export function Header() {
	const [open, setOpen] = useState(false)

	return (
		<div className="relative bg-white">
			<header className="border-b-2 border-gray-100 w-full fixed bg-white top-0 z-30">

				<div className="max-w-7xl mx-auto px-6 md:px-4">
					<div className="flex justify-between items-center py-4 lg:py-6 lg:justify-start lg:space-x-10">

						<div className="flex justify-start items-center lg:w-0 lg:flex-1">
							<img width={64} height={64} src="/assert/images/logo.png" className="inline-block w-auto h-12 md:h-14 transition-transform hover:animate-spin" alt="没事造轮子" />
							<span className="sr-only">没事造轮子</span>
							<h1 className="inline-block ml-2 text-3xl md:text-4xl">
								<Link href="/" passHref>
									<a rel="home" aria-current="page">没事造轮子</a>
								</Link>
							</h1>
						</div>

						<div className="-mr-2 -my-2 lg:hidden">
							<button type="button" className="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary" aria-expanded="false" onClick={() => setOpen(true)}>
								<span className="sr-only">打开菜单</span>
								<svg className="h-10 w-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
									<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
								</svg>
							</button>
						</div>


						<Popover.Group className="hidden lg:flex space-x-10">
							<Popover className="relative">
								<Popover.Button className="text-gray-600 group rounded-md inline-flex items-center text-xl font-medium hover:text-primary" aria-expanded="false">
									<span>教程</span>
									<svg className="text-gray-400 ml-2 h-5 w-5 group-hover:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
										<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
									</svg>
								</Popover.Button>

								<Popover.Panel className="absolute z-10 -ml-4 transform px-2 max-w-md pt-10 w-[28rem] sm:px-0 lg:left-1/2 lg:-translate-x-1/2 lg:ml-0">
									<div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 overflow-hidden border-t-4 border-primary">
										<div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8">
											<Link href="#" passHref>
												<a className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 hover:text-primary">
													<div className="ml-4">
														<p className="text-base font-medium">
															Win32 入门教程
														</p>
														<p className="mt-1 text-sm text-gray-500">
															Windows 操作系统的核心操作接口,独领风骚二十年。
														</p>
													</div>
												</a>
											</Link>
											<Link href="#" passHref>
												<a className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 hover:text-primary">
													<div className="ml-4">
														<p className="text-base font-medium">
															HTML 入门教程
														</p>
														<p className="mt-1 text-sm text-gray-500">
															超文本标记语言,前端网页的骨架,强调语义化。
														</p>
													</div>
												</a>
											</Link>
										</div>
									</div>
								</Popover.Panel>
							</Popover>

							<Popover className="relative">
								<Popover.Button className="text-gray-600 group rounded-md inline-flex items-center text-xl font-medium hover:text-primary" aria-expanded="false">
									<span>项目</span>
									<svg className="text-gray-400 ml-2 h-5 w-5 group-hover:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
										<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
									</svg>
								</Popover.Button>

								<Popover.Panel className="absolute z-10 -ml-4 transform px-2 max-w-md pt-10 w-60 sm:px-0 lg:left-1/2 lg:-translate-x-1/2 lg:ml-0">
									<div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 overflow-hidden border-t-4 border-primary">
										<div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8">
											<Link href="#" passHref>
												<a className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 hover:text-primary">
													<div className="ml-4">
														<p className="text-base font-medium">
															俄罗斯方块
														</p>
														<p className="mt-1 text-sm text-gray-500">
														</p>
													</div>
												</a>
											</Link>
											<Link href="#" passHref>
												<a className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 hover:text-primary">
													<div className="ml-4">
														<p className="text-base font-medium">
															方块赛车
														</p>
														<p className="mt-1 text-sm text-gray-500">
														</p>
													</div>
												</a>
											</Link>
										</div>
									</div>
								</Popover.Panel>
							</Popover>

							<Popover className="relative">
								<Popover.Button className="text-gray-600 group rounded-md inline-flex items-center text-xl font-medium hover:text-primary" aria-expanded="false">
									<span>码读</span>
									<svg className="text-gray-400 ml-2 h-5 w-5 group-hover:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
										<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
									</svg>
								</Popover.Button>

								<Popover.Panel className="absolute z-10 -ml-4 transform px-2 max-w-md pt-10 w-60 sm:px-0 lg:left-1/2 lg:-translate-x-1/2 lg:ml-0">
									<div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 overflow-hidden border-t-4 border-primary">
										<div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8">
											<Link href="#" passHref>
												<a className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 hover:text-primary">
													<div className="ml-4">
														<p className="text-base font-medium">
															Express 源码刨析
														</p>
														<p className="mt-1 text-sm text-gray-500">

														</p>
													</div>
												</a>
											</Link>
										</div>
									</div>
								</Popover.Panel>
							</Popover>

							<Popover className="relative">
								<Popover.Button className="text-gray-600 group rounded-md inline-flex items-center text-xl font-medium hover:text-primary" aria-expanded="false">
									<span>更多</span>
									<svg className="text-gray-400 ml-2 h-5 w-5 group-hover:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
										<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
									</svg>
								</Popover.Button>

								<Popover.Panel className="absolute z-10 -ml-4 transform px-2 max-w-md pt-10 w-[28rem] sm:px-0 lg:right-0 lg:ml-0">
									<div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 overflow-hidden border-t-4 border-primary">
										<div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8">
											<Link href="#" passHref>
												<a className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 hover:text-primary">
													<div className="ml-4">
														<p className="text-base font-medium">
															下载
														</p>
														<p className="mt-1 text-sm text-gray-500">
															博客文章中提到的一些第三方资源、演示程序等内容。
														</p>
													</div>
												</a>
											</Link>
											<Link href="#" passHref>
												<a className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 hover:text-primary">
													<div className="ml-4">
														<p className="text-base font-medium">
															关于
														</p>
														<p className="mt-1 text-sm text-gray-500">
															关于博主、博客的一些基本信息。
														</p>
													</div>
												</a>
											</Link>
										</div>
									</div>
								</Popover.Panel>
							</Popover>
						</Popover.Group>
					</div>
				</div>

			</header>
		</div>
	)
}

代码非常长,并且还没有包含移动端的代码,这里不多做解读,因为这些都是基础的前端内容不是教程的主要内容,你可以直接粘贴临时使用或者原创自己的网站头部代码。目前的代码只拿 PC 端进行举例,这不会妨碍教程的介绍,毕竟咱们这个教程主要介绍 Nextjs。

这里面其实还有一个细节,例如 text-primary 样式并不是 Tailwind CSS 框架自带的样式,这里运用了 Tailwind 提供的一个功能,你可以在 tailwind.config.js 文件中自定主题样式,下面是我目前使用的配置:

Tailwind 样式配置
tailwind.config.js
const colors = require('tailwindcss/colors')
const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx}",
    "./src/components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    colors: {
      transparent: 'transparent',
      current: 'currentColor',

      black: '#000',
      white: '#fff',
      gray: colors.slate,

      rose: colors.rose,
      teal: colors.teal,
      sky: colors.sky,
      pink: colors.pink,

      primary: '#ff4500', //'#F86011',   //主题颜色,用于所有控件的首要颜色
      // secondary: '#fd5d0a', //提示性颜色,提示动作之类的信息
      // accent: '#F86011', //交互性颜色,连接颜色之类的
      
    },
    screens: {
      'sm': '640px',
      'md': '768px',
      'lg': '1024px',
      'xl': '1280px',
    },
    extend: {
      typography: (theme) => ({
        DEFAULT: {
          css: {
            maxWidth: '42rem',
            color: theme('colors.gray.700'),
            'h1, h2, h3, h4, h5': {
              color: theme('colors.gray.600'),
            },
            h4: {
              fontSize: '1.125em',
            },
            p: {
              textAlign: 'justify',
              textJustify: 'inter-ideograph',
            },
            
            'ul > li': {
              paddingLeft: '1.5em',
            },
            'ul > li::before': {
              width: '0.45em',
              height: '0.1em',
              top: 'calc(0.875em - 0.0625em)',
              left: '0.25em',
              borderRadius: 0,
              backgroundColor: theme('colors.gray.300'),
            },
            a: {
              color: theme('colors.primary'),
              textDecoration: 'none',
              '&:hover': {
                textDecoration: 'underline',
                textUnderlineOffset: '4px'
              },
            },
            code: {
              fontWeight: 500,
              fontSize: '85%',
              padding: '0.2em 0.4em',
              borderRadius: '6px',
              backgroundColor: theme('colors.gray.100')
            },
            'code::before': {
              content: 'none',
            },
            'code::after': {
              content: 'none',
            },

            'blockquote p:first-of-type::before': {
              content: 'none',
            },

            'blockquote p:last-of-type::after': {
              content: 'none',
            },

            details: {
              margin: '0.5rem 0px',
              padding: '0.5rem 1rem',
              backgroundColor: theme('colors.gray.100'),
              border: '1px solid',
              borderColor: theme('colors.gray.300'),
              borderRadius: 3,
            },

            pre: {
              // color: theme('colors.black'),
              // borderWidth: 0,
              // // borderColor: theme('colors.black'),
              // // backgroundColor: 'none',
              // backgroundColor: theme('colors.gray.100'),
              // borderRadius: 3,
              // fontSize: '85%',
              paddingTop: 0,
              marginTop: '1em',
              marginBottom: '1em',
            },
            table: {
              fontSize: theme('fontSize.sm')[0],
              lineHeight: theme('fontSize.sm')[1].lineHeight,
            },
            thead: {
              color: theme('colors.gray.600'),
              borderBottomColor: theme('colors.gray.200'),
            },
            'thead th': {
              paddingTop: 0,
              fontWeight: theme('fontWeight.semibold'),
            },
            'tbody tr': {
              borderBottomColor: theme('colors.gray.200'),
            },
            'tbody tr:last-child': {
              borderBottomWidth: '1px',
            },
            'tbody code': {
              fontSize: theme('fontSize.xs')[0],
            },

          }
        }
      }),

      fontFamily: {
        sans: ['Inter var', ...defaultTheme.fontFamily.sans],
        // mono: ['Fira Code VF', ...defaultTheme.fontFamily.mono],
        source: ['Source Sans Pro', ...defaultTheme.fontFamily.sans],
        'ubuntu-mono': ['Ubuntu Mono', ...defaultTheme.fontFamily.mono],
        system: defaultTheme.fontFamily.sans,
        flow: 'Flow',
      }
    },
  },
  
  plugins: [
    require('@tailwindcss/typography'),
  ],
}

代码中重新定义了一些可能用到的颜色属性,并设置了屏幕断点,最重要的是引入了 @tailwindcss/typography 插件,并简单修改了插件默认的文本样式。

在添加完代码后,不要忘记安装 @tailwindcss/typography 插件,否则运行会报找不到模块的错误:

npm install @tailwindcss/typography

该插件主要用于文章排版的默认样式,在以后的文章显示章节中会具体介绍使用方法。到目前为止网站的头部算是初步搭建完毕,如果一切顺利,你已经可以在浏览器端查看网站头部的效果了。

网站头部展示

4. 完善页脚

接下来继续完善页脚部分,页脚部分相对来说比较简单,至少目前我并不想把页脚的样式弄的很重,所以基本上就是一行代码而已:

Footer.js
export function Footer({trans}) {
    return (
        <footer className="py-8 border-t">
            <p className="text-left text-base max-w-7xl px-6 mx-auto">
                <span>{`Copyright © 2020 - ${(new Date).getFullYear()} ${trans.Title} | `}</span>
                <a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow noreferrer noopener">ICP2021026646| </a>
                <span>
                    <img className="inline-block" alt="beiantubiao" width="16" height="16" src="/assert/images/1631844241-beiantubiao.png"></img>
                    <a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=33078202001694" target="_blank" rel="nofollow noreferrer noopener">浙公网安备 33078202001694</a>
                </span>
            </p>
        </footer>
    )
}

这里只显示网站的名称和备案信息,都是最简单的 HTML 代码,显示的效果还算可以:

网站页脚展示

5. Hero 模块

Hero 模块主要用于网站的头部,一般被放在网站的首页顶部,大尺寸横幅展示,可以有效的吸引用户的目光。咱们这里还是低调一点,只显示一个动画和文字即可。

首先在 components 文件夹下建立 Hero/index.js 文件,内容如下:

Hero 模块
Hero.js
import Lottie from 'lottie-react'
import animationData from './girl-studying-on-laptop.json'
import Link from 'next/link'

export function Hero() {
    return (
        <div className="py-20 md:py-32">
            <section className="container md:flex mx-auto">
                <div className="w-full mt-10 md:w-1/2">
                    <Lottie
                        animationData={animationData}
                        loop={true}
                        autoPlay={true}
                    />
                </div>

                <div className="w-full mt-10 md:w-1/2">
                    <h2 className="text-6xl text-center leading-normal md:leading-relaxed md:text-[4rem] lg:text-[5rem]">
                        <span>学习、实战、总结、</span>
                        <span className="relative inline-block overflow-visible">
                            <span className="font-black z-10 relative">分享</span>
                            <svg className="text-primary absolute -translate-y-1/2 -translate-x-1/2 top-1/2 left-1/2 h-[calc(100%)] w-[calc(100%+2rem)]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 150" preserveAspectRatio="none"><path fill="none" stroke="currentColor" strokeWidth="9" d="M7.7,145.6C109,125,299.9,116.2,401,121.3c42.1,2.2,87.6,11.8,87.3,25.7"></path></svg>
                        </span>
                    </h2>

                    <div className="text-center mt-16">
                        <Link href="#tutorial-hero" passHref>
                            <a className="inline-block py-4 px-14 rounded-md bg-primary transform duration-500  hover:scale-110 motion-reduce:transform-none">
                                <span className="flex text-xl text-white font-bold">
                                    <svg className="w-4 mr-2 fill-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M381.2 172.8C377.1 164.9 368.9 160 360 160h-156.6l50.84-127.1c2.969-7.375 2.062-15.78-2.406-22.38S239.1 0 232 0h-176C43.97 0 33.81 8.906 32.22 20.84l-32 240C-.7179 267.7 1.376 274.6 5.938 279.8C10.5 285 17.09 288 24 288h146.3l-41.78 194.1c-2.406 11.22 3.469 22.56 14 27.09C145.6 511.4 148.8 512 152 512c7.719 0 15.22-3.75 19.81-10.44l208-304C384.8 190.2 385.4 180.7 381.2 172.8z"/></svg>
                                    <span>开始学习</span>
                                </span>
                            </a>
                        </Link>
                    </div>
                </div>
            </section>
        </div>
    )
}

该模块使用了一个第三方的组件 lottie-react,其主要用于显示 Lottie 动画。Lottie 动画主要使用 JSON 来描述动画内容,代码中使用的 girl-studying-on-laptop.json 文件就是 Lottie 动画的核心数据,你可以在网上找一些开源免费的动画模板用在自己网站上,也可以使用 AE 之类的工具手工原创。

hero 模块显示效果

6. 教程列表模块

Hero 模块下面显示网站的核心内容,在本教程中则直接显示一个教程列表。代码如下:

教程列表模块
TutorialList.js
import Link from 'next/link'

export function TutorialList() {
    return (
        <>
            <div className="text-center py-5 text-gray-600">
                <h2 id="tutorial-hero" className="text-5xl md:text-6xl">技术教程</h2>
                <p className="text-xl p-5">专注于某项技术的系统性课程,从零开始,深入简出。</p>
            </div>

            <section className="text-center">
                <div className="flex flex-col max-w-6xl mx-auto justify-center px-4 md:space-x-8 md:flex-row ">

                    <div className="shadow-lg my-8 py-8 px-4 rounded-lg md:w-1/3">
                        <div className="flex flex-col text-gray-600 px-4 space-y-8">
                            <h3 className="text-5xl font-semibold">Win32</h3>
                            <p className="text-xl">Windows 系统的核心操作接口,独领风骚二十年。</p>
                            <svg className="h-36 mx-auto text-primary" aria-hidden="true" focusable="false" data-prefix="fab" data-icon="windows" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z"></path></svg>
                            <div className="mx-auto">
                                <Link href="/windows-bian-cheng-kai-fa" passHref>
                                    <a className="inline-block py-4 px-10 rounded-md bg-primary hover:shadow-xl">
                                        <span className="flex text-xl font-bold text-white ">
                                            <span>开始学习</span>
                                        </span>
                                    </a>
                                </Link>
                            </div>
                        </div>
                    </div>

                    <div className="shadow-lg my-8 py-8 px-4 rounded-lg md:w-1/3">
                        <div className="flex flex-col text-gray-600 px-4 space-y-8">
                            <h3 className="text-5xl font-semibold">SDL2</h3>
                            <p className="text-xl">开源的多媒体跨平台开发库,纯 C 接口,易于上手。</p>
                            <svg className="h-36 mx-auto text-primary" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="dice-d20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M20.04 317.3C18 317.3 16 315.8 16 313.3V150.5c0-2.351 1.91-4.012 4.001-4.012c.6882 0 1.396 .18 2.062 .5748l76.62 45.1l-75.28 122.3C22.59 316.8 21.31 317.3 20.04 317.3zM231.4 405.2l-208.2-22.06c-4.27-.4821-7.123-4.117-7.123-7.995c0-1.401 .3725-2.834 1.185-4.161L122.7 215.1L231.4 405.2zM31.1 420.1c0-2.039 1.508-4.068 3.93-4.068c.1654 0 .3351 .0095 .5089 .0291l203.6 22.31v65.66C239.1 508.6 236.2 512 232 512c-1.113 0-2.255-.2387-3.363-.7565L34.25 423.6C32.69 422.8 31.1 421.4 31.1 420.1zM33.94 117.1c-1.289-.7641-1.938-2.088-1.938-3.417c0-1.281 .6019-2.567 1.813-3.364l150.8-98.59C185.1 10.98 187.3 10.64 188.6 10.64c4.32 0 8.003 3.721 8.003 8.022c0 1.379-.3788 2.818-1.237 4.214L115.5 165.8L33.94 117.1zM146.8 175.1l95.59-168.4C245.5 2.53 250.7 0 255.1 0s10.5 2.53 13.62 7.624l95.59 168.4H146.8zM356.4 207.1l-100.4 175.7L155.6 207.1H356.4zM476.1 415.1c2.422 0 3.93 2.029 3.93 4.068c0 1.378-.6893 2.761-2.252 3.524l-194.4 87.66c-1.103 .5092-2.241 .7443-3.35 .7443c-4.2 0-7.994-3.371-7.994-7.994v-65.69l203.6-22.28C475.7 416 475.9 415.1 476.1 415.1zM494.8 370.9C495.6 372.3 496 373.7 496 375.1c0 3.872-2.841 7.499-7.128 7.98l-208.2 22.06l108.6-190.1L494.8 370.9zM316.6 22.87c-.8581-1.395-1.237-2.834-1.237-4.214c0-4.301 3.683-8.022 8.003-8.022c1.308 0 2.675 .3411 4.015 1.11l150.8 98.59c1.211 .7973 1.813 2.076 1.813 3.353c0 1.325-.6488 2.649-1.938 3.429L396.5 165.8L316.6 22.87zM491.1 146.5c2.091 0 4.001 1.661 4.001 4.012v162.8c0 2.483-2.016 4.006-4.053 4.006c-1.27 0-2.549-.5919-3.353-1.912l-75.28-122.3l76.62-45.1C490.6 146.7 491.3 146.5 491.1 146.5z"></path></svg>
                            <div className="mx-auto">
                                <Link href="/sdl2-you-xi-kai-fa-zui-xiao-zhi-shi-zhan" passHref>
                                    <a className="inline-block py-4 px-10 rounded-md bg-primary hover:shadow-xl">
                                        <span className="flex text-xl font-bold text-white">
                                            <span>开始学习</span>
                                        </span>
                                    </a>
                                </Link>
                            </div>
                        </div>
                    </div>

                    <div className="shadow-lg my-8 py-8 px-4 rounded-lg md:w-1/3">
                        <div className="flex flex-col text-gray-600 px-4 space-y-8">
                            <h3 className="text-5xl font-semibold">CSS</h3>
                            <p className="text-xl">前端学习的必备知识,控制网站样式的专用语言。</p>
                            <svg className="h-36 mx-auto text-primary" aria-hidden="true" focusable="false" data-prefix="fab" data-icon="css3-alt" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M0 32l34.9 395.8L192 480l157.1-52.2L384 32H0zm313.1 80l-4.8 47.3L193 208.6l-.3 .1h111.5l-12.8 146.6-98.2 28.7-98.8-29.2-6.4-73.9h48.9l3.2 38.3 52.6 13.3 54.7-15.4 3.7-61.6-166.3-.5v-.1l-.2 .1-3.6-46.3L193.1 162l6.5-2.7H76.7L70.9 112h242.2z"></path></svg>
                            <div className="mx-auto">
                                <a href="#" className="inline-block py-4 px-10 rounded-md bg-primary hover:shadow-xl">
                                    <span className="flex text-xl font-bold text-white">
                                        <span>开始学习</span>
                                    </span>
                                </a>
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        </>
    )
}

最终的显示效果如下:

教程列表

7. 最新文章模块

主页的最后则显示博客的最新文章,因为需要涉及到文章的动态加载,所以这里先初始化大致的框架,具体代码等以后再完善:

RecentPosts.js
export function RecentPosts({ posts }) {
    return (
        <>
            <div className="text-center pt-24 pb-16 text-gray-600">
                <h2 className="text-5xl md:text-6xl">最新文章</h2>
            </div>

            <section className="flex flex-wrap text-gray-600 max-w-screen-lg mx-auto mb-10">
                文章列表。。。
            </section>
        </>
    )
}

创建完这些模块之后,不要忘记修改首页代码:

index.js
import { Header } from "../components/Header"
import { Footer } from "../components/Footer"
import { Hero } from "../components/Hero"
import { TutorialList } from "../components/TutorialList"
import { RecentPosts } from "../components/RecentPosts"

export default function Home() {
  return (
    <>
      <Header />
      <Hero />
      <TutorialList />
      <RecentPosts />
      <Footer />
    </>
  )
}

打完收工!

8. 总结

到此为止,第一轮的首页迭代已经完成,其实核心内容基本和 Nextjs 无关,只是单纯的运用 CSS 和 HTML 写网页而已,所以文章并没有进行过多的介绍,咱们的目的还是 Nextjs,但是你又不能不搞,毕竟最终的任务是用 Nextjs 搭建一个完整的网站。

本篇文章的重点在于初始化目录的结构,拆分首页各个模块,因为页首和页脚都可以复用,所以这里将它们全部封装为组件,这些组件可以无缝兼容的运用在其它页面上,快捷方便。