• 搭建nuxt3项目(框架构建)


    需求

    目标:
    	我想搭建一个nuxt3的框架,实现一些基本的组件和路由、页面,方便后续遇到相关ssr项目直接复用。
    同时:
    	记录关于nuxt3的使用介绍
    
    • 1
    • 2
    • 3
    • 4

    关于Nuxt(详解以及周边)

    Nuxt 框架
    	1、一种基于 Node.js 的服务端渲染方案 SSR(Server Side Rendering)
    	支持 Vue 应用、服务器端渲染、提高页面的加载速度和 SEO
    工作原理:
    	1、服务端渲染:Nuxt.js 在服务器端执行 Vue 组件的渲染过程,并生成初始 HTML
    	2、客户端激活:一旦初始 HTML 被发送到浏览器,Vue.js 会接管页面,并在客户端激活成一个交互式应用程序。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    Nuxt 优势
    SPA 和 SSR 是什么?
    	1、SPA(Single Page Application)单页面应用,在客户端通过 JS 动态地构建页面。
    		 优势:页面切换流畅,动态渲染变化,用户体验好,搜索引擎的支持不够友好。
    		 适用企业内部项目,如管理平台。
    	2、SSR(Server-Side Rendering)服务器端渲染,在服务器端生成 HTML 页面并发送给客户端。
    		 优势:对搜索引擎友好,页面首次加载速度快 和 SEO,页面切换可能不够流畅(每次都是请求一个完整的 HTML 页面)
    		 适用官网,对 SEO 搜索友好。
    	3、Nuxt 框架
    		 Nuxt 采用了混合的架构模式,可以同时支持 SSR 和 SPA。
    		首次访问页面是 SSR 方式,也就是在服务器端生成 HTML 页面并发送给客户端
    		后续的页面切换则使用 SPA 的方式进行,从而兼顾了 SSR 和 SPA 的优点。
    
    场景:企业网站、商品展示 等 C 端网站,对 SEO 搜索更友好,且页面切换流畅,用户体验更好
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    项目搭建

    1、安装项目命令和报错分析

    npx nuxi init 
    
    • 1

    在这里插入图片描述

    错误分析

    1、安装报以上错误(其原因是大陆访问受限)
    2、但是我开了代理也一样下载不下来
    3、从官网issue159:原因是脚手架node程序没有走代理

    错误解决方案

    1、官网zip

    在这里插入图片描述
    2、在终端添加host代理,进行DNS域名映射

    mac修改方式如下:

    sudo vi /etc/hosts
    输入 i 进入编辑模式,编辑完成后按 Esc后:wq 保存退出
    
    添加
    185.199.108.133 raw.githubusercontent.com
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Windows修改host参考:修改本地host代理

    在这里插入图片描述

    运行

    npm run dev
    or
    npm i
    npm run dev

    #

    目录以及初始化

    1、先来理解下目录

    ├─.nuxt              非工程代码,存放运行或发行的编译结果
    ├─node_modules       项目依赖
    ├─public             网站资源目录
    ├─server             接口目录
    ├─.gitignore         git 忽略文件
    ├─.npmrc             npm 配置文件
    ├─app.vue            根组件
    ├─nuxt.config.ts     nuxt 配置文件
    ├─package.json       项目配置文件
    ├─README.md          项目说明文件
    └─tsconfig.json      ts 配置文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、nuxt 有一些约定的目录,有特殊功能,如 pages 目录的 vue 文件会自动注册路由。

    ├─ pages             页面目录,自动注册路由
    
    • 1

    Nuxt.js 自带路由功能,不需要额外安装和配置,无需安装 vue-router 。

    ├─ pages               页面目录,自动注册路由
    │  └─index.vue         主页
    │  └─user.vue          用户页
    ├─app.vue              根组件
    
    • 1
    • 2
    • 3
    • 4

    测试一下

    <template>
      <!-- 路由链接 -->
      <NuxtLink to="/">首页</NuxtLink>
      <NuxtLink to="/user">用户页</NuxtLink>
      <!-- 路由占位 -->
      <NuxtPage />
    </template>
    //  相当于 
    //  相当于 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    在这里插入图片描述### SEO 优化
    实现:
    通过设置网页 title 和 description 等 SEO 优化信息,由服务端渲染
    SEO and Meta

    // 在app.vue中新增
    <script setup lang="ts">
    // SEO 优化
    useSeoMeta({
      title: 'demo - 找工作|博客|提升',
      description:
        '求职之前,需要有经验,技术。祝愿大家找到好工作,拿到好offer。',
      author: '卡卡——程序员',
    })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    组件库和vw适配

    安装 nuxt 版 vant-ui
    npm i @vant/nuxt
    
    • 1

    nuxt.config.ts添加配置

    // https://nuxt.com/docs/api/configuration/nuxt-config
    // PS: 在 Nuxt 项目中,vant 组件会自动按需导入(需重启)
    export default defineNuxtConfig({
      devtools: { enabled: false },
      modules: ['@vant/nuxt'],
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    index.vue测试

    <van-button type="primary">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    
    • 1
    • 2
    vw 适配

    安装

    npm i postcss-px-to-viewport
    
    • 1

    nuxt.config.ts添加配置

    // https://nuxt.com/docs/api/configuration/nuxt-config
    export default defineNuxtConfig({
      devtools: { enabled: false },
      modules: ['@vant/nuxt'],
      // 移动端适配
      postcss: {
        plugins: {
          'postcss-px-to-viewport': {
            viewportWidth: 375,
          },
        },
      },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    页面效果px会转化为vw
    在这里插入图片描述

    路由配置

    自动路由

    在 pages 目录的 vue 文件会自动注册路由,创建以下vue文件
    登录页 login.vue
    注册页 register.vue
    文章详情页 detail.vue
    文章(TabBar)article.vue
    收藏(TabBar)collect.vue
    喜欢(TabBar)like.vue
    我的(TabBar)user.vue
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    路由中间件
    新建 middleware 目录,通过 Nuxt 路由中间件实现路由重定向,包括导航守卫等路由功能。

    export default defineNuxtRouteMiddleware((to, from) => {
      // 首页重定向
      if (to.path === '/') {
        return navigateTo('/article')
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    layouts 布局

    目标:我们想在首页底部实现TabBar导航栏
    
    • 1

    通过 layout 实现布局的复用,新建文件 layouts/tabbar.vue

    // route 开启路由模式
    // to 跳转路由
    <template>
      <div class="layout-page">
        <!-- 插槽 -->
        <slot />
        <!-- tabbar -->
        <van-tabbar route>
          <van-tabbar-item to="/article" icon="notes-o">面经</van-tabbar-item>
          <van-tabbar-item to="/collect" icon="star-o">收藏</van-tabbar-item>
          <van-tabbar-item to="/like" icon="like-o">喜欢</van-tabbar-item>
          <van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
        </van-tabbar>
      </div>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    页面中使用 layout 布局
    article.vue

    <template>
      <NuxtLayout name="tabbar">
        <h1>文章首页</h1>
      </NuxtLayout>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为这几个路由分别配置
    在这里插入图片描述
    修改主题色
    在 app.vue 的样式全局生效。

    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    注册页

    直接复用、一些基础vant组件使用

    <template>
      <div class="register-page">
        <!-- 导航栏部分 -->
        <van-nav-bar title="用户注册" />
    
        <!-- 一旦form表单提交了,就会触发submit,可以在submit事件中
            根据拿到的表单提交信息,发送axios请求
        -->
        <van-form @submit="onSubmit">
          <!-- 输入框组件 -->
          <!-- \w 字母数字_   \d 数字0-9 -->
          <van-field
            v-model="form.username"
            name="username"
            label="用户名"
            placeholder="用户名"
            :rules="[
              { required: true, message: '请填写用户名' },
              { pattern: /^\w{5,}$/, message: '用户名至少包含5个字符' },
            ]"
          />
          <van-field
            v-model="form.password"
            type="password"
            name="password"
            label="密码"
            placeholder="密码"
            :rules="[
              { required: true, message: '请填写密码' },
              { pattern: /^\w{6,}$/, message: '密码至少包含6个字符' },
            ]"
          />
          <div style="margin: 16px">
            <van-button block type="primary" native-type="submit">注册</van-button>
          </div>
        </van-form>
        <NuxtLink class="link" to="/login">已注册,去登录</NuxtLink>
      </div>
    </template>
    
    <script setup lang="ts">
    // 表单数据
    const form = reactive({
      username: 'admin',
      password: '123456',
    })
    
    // 表单提交
    const onSubmit = async () => {
      //
    }
    </script>
    
    <style scoped>
    .link {
      color: #069;
      font-size: 12px;
      padding-right: 20px;
      float: right;
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    请求封装axios

    安装 axios,登录请求可用 axios 发送。

    	npm i axios
    
    • 1

    新建 utils/request.ts 封装 axios 模块
    nuxt3 utils模块
    在这里插入图片描述

    /* 封装axios用于发送请求 */
    
    import axios, { AxiosResponse } from 'axios'
    
    const service = axios.create({
      baseURL: 'http://interview-api-t.itheima.net/h5',
      timeout: 90000,
    })
    const request = (config: any) => {
    //   if (getToken()) {
    //     config.headers['csrf-token'] = getToken()
    //   }
      if (config.cType) {
        config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
      }
      return config
    }
    
    const requestError = (error: any) => {
      console.error(error)
      return Promise.reject(error)
    }
    
    const response = (response: AxiosResponse) => {
      return response.data
    }
    
    const responseError = async (error: any) => {
      if (error.response.status === 403) {
        showFailToast('登录过期')
        navigateTo('/login')
        return Promise.reject(error)
      }
      if (error.response.data.code === 400) {
        showFailToast(error.response.data.message)
        return Promise.reject(error)
      }
      if (error.response.status === 500) {
        // No permission
        return Promise.reject(error)
      }
      return Promise.reject(error)
    }
    
    service.interceptors.request.use(request, requestError)
    service.interceptors.response.use(response, responseError)
    
    export default service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    // register.vue 表单提交调用接口
    // 表单提交
    const onSubmit = async () => {
      // 注册请求
      await request({
        method: 'POST',
        url: '/user/register',
        data: form,
      })
      // 成功提示
      showSuccessToast('注册成功')
      // 跳转页面
      navigateTo('/login')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    持久化存储 use-cookie

    注意:Nuxt 服务器端不支持 localStorage 所以运行会报错,可通过 cookie 代替。
    而且Nuxt支持 useCookie API存储cookie
    
    • 1
    • 2

    在util中新建cookie.ts

    const KEY = 'key'
    
    // 获取
    export const getToken = () => {
      return useCookie(KEY).value
    }
    
    // 设置
    export const setToken = (newToken: string) => {
      useCookie(KEY, { maxAge: 60 * 60 * 24 * 14 }).value = newToken
    }
    
    // 删除
    export const delToken = () => {
      useCookie(KEY).value = undefined
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    登录页逻辑

    <script setup lang="ts">
    // 表单数据
    const form = reactive({
      username: 'admin111',
      password: '123456',
    })
    
    // 表单提交
    const onSubmit = async () => {
      // 登录请求
      const res = await request({
        method:"post",
        url:'/user/login',
        data:form
      })
      // 保存 token
      setToken(res.data.token)
      // 成功提示
      showSuccessToast('登录成功')
      // 跳转页面
      navigateTo('/', {replace: true})
    }
    </script>
    
    <template>
      <div class="login-page">
        <!-- 导航栏部分 -->
        <van-nav-bar title="用户登录" />
    
        <!-- 一旦form表单提交了,就会触发submit,可以在submit事件中
             根据拿到的表单提交信息,发送axios请求
         -->
        <van-form @submit="onSubmit">
          <!-- 输入框组件 -->
          <!-- \w 字母数字_   \d 数字0-9 -->
          <van-field
            v-model="form.username"
            name="username"
            label="用户名"
            placeholder="用户名"
            :rules="[
              { required: true, message: '请填写用户名' },
              { pattern: /^\w{5,}$/, message: '用户名至少包含5个字符' },
            ]"
          />
          <van-field
            v-model="form.password"
            type="password"
            name="password"
            label="密码"
            placeholder="密码"
            :rules="[
              { required: true, message: '请填写密码' },
              { pattern: /^\w{6,}$/, message: '密码至少包含6个字符' },
            ]"
          />
          <div style="margin: 16px">
            <van-button block type="info" native-type="submit">提交</van-button>
          </div>
        </van-form>
        <NuxtLink class="link" to="/register">注册账号</NuxtLink>
      </div>
    </template>
    
    <style scoped>
    .link {
      color: #069;
      font-size: 12px;
      padding-right: 20px;
      float: right;
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    路由鉴权-导航守卫

    Nuxt 有路由中间件,简化了导航守卫的实现。use-router

    // middleware下的route.global.ts
    // 白名单列表,记录无需权限访问的所有页面
    const whiteList = ['/login', '/register']
    
    export default defineNuxtRouteMiddleware((to, from) => {
      // 首页重定向
      if (to.path === '/') {
        return navigateTo('/article')
      }
      // 获取 token
      const token = getToken()
      if (!token && !whiteList.includes(to.path)) {
        return navigateTo('/login')
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    渲染页面-封装useFetch 通过 useFetch 发送请求

    为什么封装useFetch发送请求?
    1、服务端先渲染静态页面,axios请求接收到数据需要客户端进行渲染页面。
    2、useFetch发送的请求直接在服务端处理好,随着页面一起回来。利于seo
    
    • 1
    • 2
    • 3
    // 注意、函数组件都不用引入,可以直接使用,但type类型需要导入才能使用
    export const useRequest = async (url: string,options?: any) => {
      const { data, error } = await useFetch(url, {
        baseURL: 'http://interview-api-t.itheima.net/h5/',
        headers: {
          Authorization: `Bearer ${getToken()}`,
        },
        ...options,
      })
    
      if (error.value) {
        return Promise.reject(error)
      } else {
        return data.value.data
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    补充

    在这里插入图片描述

    其中 [id].vue  表示动态路由为detail/:id的路由
    
    • 1

    git 地址

  • 相关阅读:
    渗透测试-C段主机信息收集
    自动挂载磁盘
    「SpringCloud Alibaba」Sentinel实现熔断与限流
    【电驱动】驱动电机系统讲解
    JavaScript 验证 API
    光会面向对象基础做不了项目,还得掌握这些进阶知识
    计算机存储器与存储结构
    怎么看电脑实时充电功率
    Mybatis-Plus
    Android BitmapFactory.decodeResource读取原始图片装载成原始宽高Bitmap,Kotlin
  • 原文地址:https://blog.csdn.net/weixin_43909743/article/details/133995931