【🍀新鲜出炉 】十个 “如何”从零搭建 Nuxt3 项目

一、如何在 Nuxt3 中更好的处理页面布局

可以通过 <NuxtLayout><NuxtPage> 来实现不同布局

app.vue

vue 复制代码
<template>
  <NuxtLayout> <!-- 布局容器  默认加载 layouts/default.vue -->
    <NuxtPage /> <!-- 页面路由出口 -->
  </NuxtLayout>
</template>
<script setup lang="ts">
</script>

布局一:layouts/default.vue

vue 复制代码
<template>
  <div>
    <header>常规导航栏</header>
    <main>
      <slot /> <!-- 页面内容插入位置 -->
    </main>
    <footer>网站底部信息</footer>
  </div>
</template>

布局二:layouts/login.vue

vue 复制代码
<template>
  <div class="login-layout">
    <div class="auth-bg">登录 header</div>
    <slot /> <!-- 登录表单将插入这里 -->
    <div>登录 footer</div>
  </div>
</template>
首页 登录页

路由一:首页

javascript 复制代码
<script setup>
// 不指定 layout 则默认使用 default.vue
</script>

<template>
  <div>首页内容</div>
</template>

路由二:登录页

javascript 复制代码
<script setup>
definePageMeta({
  layout: 'login'  // 指定使用 layouts/login.vue 布局
})
</script>

<template>
  <div class="login-form">
    <!-- 登录表单内容 -->
    表单内容
  </div>
</template>

二、如何在 Nuxt3 中使用 Pina

安装 pina

json 复制代码
npm install @pinia/nuxt

注册,修改 nuxt.config.ts

typescript 复制代码
export default defineNuxtConfig({
  modules: ['@pina/nuxt']
})

store/index.ts

typescript 复制代码
export const useAppStore = defineStore('app', {
    state: () => {
       return {
          theme: 'dark',
          userData: {
              name: 'Jack',
              age: 18
          }
       }
    },
    getters: {
        name: (state) => state.userData.name 
    },
    actions: {
        initHead() {
            useHead({
                title: '四叶草'
            })
        }
    }
})

在页面上导入 store ,则可以使用

typescript 复制代码
import { useAppStore } from '@/store/index'
const appStore = useAppStore()

除了上面那种方式外,也可以全局挂在到 nuxtApp 上。

在项目根目录上,创建 plugins 目录(注意,文件名需要一致)

typescript 复制代码
// store.ts (文件名随便)

import { useAppStore } from '@/store'
export default defineNuxtPlugin((nuxtApp) => {
    const appStore = useAppStore()
    
    // 挂载到 nuxtApp
    nuxtApp.provide('appStore', appStore)
})

使用

typescript 复制代码
import { useNuxtApp } from '#app'
const { $appStore } = useNuxtApp();

pina 在 nuxt 中的前期配置就是这些了,pina 具体使用可以查看我另外一遍文章

学完 Pinia 真香,不想用 vuex 了

三、如何在 Nuxt3 中使用第三方组件库

这里我使用的是 TDesgin 组件库。

安装 TDesign

json 复制代码
npm install tdesign-vue-next @tdesign-vue-next/nuxt

注册,修改 nuxt.config.ts

typescript 复制代码
export default defineNuxtConfig({
  modules: ['@tdesign-vue-next/nuxt']
})

使用

javascript 复制代码
<template>
  <t-button>Hello Nuxt + TDesgin</t-button>
</template>
  • Message 的使用:也可以直接挂载到 nuxtApp 上
typescript 复制代码
import { MessagePlugin } from "tdesign-vue-next";
export default defineNuxtPlugin((nuxtApp) => {
  const appStore = useAppStore();
  nuxtApp.provide("message", MessagePlugin);
});
javascript 复制代码
<template>
    <t-button @click="showMessage">显示提示信息</t-button>
</template>
<script setup>
import { useNuxtApp } from '#app';
    
const { $message } = useNuxtApp();
    
const showMessage = () => {
    $messsage.info({ content: "这里是 Message 信息" });
}    
</script>
  • 图标库的使用:也可以进行全局注册使用
typescript 复制代码
import * as Icons from "tdesign-icons-vue-next";
export default defineNuxtPlugin((nuxtApp) => {
  const icons = Icons;
  for (const i in icons) {
    // 全局注册
    nuxtApp.vueApp.component(i, icons[i]);
  }
});
vue 复制代码
<template>
  <div>
    <logo-adobe-illustrate-icon />
  </div>
</template>
<script setup lang="ts">
// import { LogoAdobeIllustrateIcon } from 'tdesign-icons-vue-next'  // 局部使用
</script>

四、如何在 Nuxt3 中实现自定义指令的注册

如:权限指令

typescript 复制代码
// authBtn.ts
import store from '@/store'
function checkPermission (el, binding) {
  // 获取绑定的值,此处为权限
  const { value } = binding
  // 获取所有的功能指令
  const authId = store.getters.userInfo.authId

  let btnAuth = []
  if (authId === '1') { // 管理员权限
    btnAuth = ['export', 'delete', 'change']
  }
  // 当传入的指令集为数组时
  if (value && value instanceof Array) {
    // 匹配对应的指令
    const hasPermission = btnAuth.some(point => {
      return value.includes(point)
    })
    if (!hasPermission) {
      el.parentNode && el.parentNode.removeChild(el)
    }
  } else {
    // eslint-disabled-next-line
    throw new Error('v-permission value is ["admin","editor"]')
  }
}
export default {
  // 在绑定元素的父组件被挂载后调用
  mounted (el, binding) {
    checkPermission(el, binding)
  },
  // 在包含组件的 VNode 及其子组件的 VNode 更新后调用
  update (el, binding) {
    checkPermission(el, binding)
  }
}
typescript 复制代码
// plugins/directives.ts
import authBtn from '@/directives/authBtn'
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.directive('authBtn', authBtn);
})

五、如何在 Nuxt3 中使用 Axios

在 Nuxt3 中也提供了 useFetch$FetchuseAsyncData 来发送请求。(后面找个时间研究一下这三个函数)

对于使用 axios 习惯的开发者,也可以在 nuxt3 中配置使用 axios

安装 Axios

json 复制代码
npm install axios

定义 Axios

typescript 复制代码
// request.ts
import axios from 'axios'
import { useNuxtApp } from '#app'

const axiosInstance = axios.create({
  baseURL: '/api', 
});

axiosInstance.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

axiosInstance.interceptors.request.use((config) => {
  let authorization = {};
  const authStr = localStorage.getItem('AUTHORIZATION'); // localStorage 也可以挂在到 nuxtApp 上
  if (authStr) {
    authorization = JSON.parse(authStr)
  }

  if (authorization && authorization['access_token']) {
    config.headers['Authorization'] = `${authorization['token_type']} ${authorization['access_token']}`;
  }
  return config; 
}, (error) => {
  return Promise.reject(error);
});

axiosInstance.interceptors.response.use((response) => {
  return response.data;
}, (error) => {
  if (process.client) { // 在客户端进行处理,不做判断会报错
      const { $message, $appStore } = useNuxtApp();
      if (error.response.status === 401) {
        if (error.response.data) {
          let data = error.response.data;
          let codes = ['disabled_credentials', 'invalid_credentials', 'invalid_verify_code'];
          if (!codes.includes(data.code)) {
            $appStore.logout();
            $appStore.isLogin = false;
          } else if (data.code !== 'invalid_token_expired') {
            $message.error(error.response.data.message);
          }
        }
      }
  }

  return Promise.reject(error);
});

export default axiosInstance;

通过 provide 挂载到 nuxtApp

typescript 复制代码
import axios from '@/utils/request.ts'

export default defineNuxtPlugin((nuxtApp) => {
    nuxtApp.provide("axios", axios);
})

页面上使用

typescript 复制代码
import { useNuxtApp } from '#app'
const { $axios } = useNuxtApp()

六、如何在 Nuxt3 中做好请求路由的转发

nuxt.config.ts

typescript 复制代码
import Config from "./config";
export default defineNuxtConfig({
  nitro: {
    devProxy: {
      "/api": {
        target: Config.baseUrl,
        changeOrigin: true,
        prependPath: true,
      },
    },
  },
})

可以通过 process 对象,来配置请求地址是开发环境地址,还是生成环境地址

七、如何在 Nuxt3 中做好路由拦截

Nuxt3 有自己的一套路由机制,会自动注册 pages 目录下的文件,并通过文件名规则进行路由注册,这里不做过多介绍。比较简单。

路由拦截主要是用在权限校验,在 vue项目中可以通过路由钩子进行拦截。

在 nuxt 也提供了 defineNuxtRouteMiddleware 来定义拦截器。

在项目根路径创建 middleware 目录,创建 router.global.ts 文件(global) 代表全局拦截

typescript 复制代码
// 全局路由守卫
import Config from '@/config';
export default defineNuxtRouteMiddleware((to, from) => {
  if (process.client) { // 需要在客户端进行拦截,否则会报错
    let authorization = {};
    const authStr = localStorage.getItem('AUTHORIZATION');

    if (authStr) {
      authorization = JSON.parse(authStr);
    }

    if (authorization && authorization['access_token']) {
      // 校验登录是否过期
      const expires_in = authorization['expires_in'];
      const login_time = Number(authorization['login_time']);
      const now = Date.now();
      if (now > login_time + expires_in * 1000) {
        clientStore.logout();
        window.location.href = '/';
        clientStore.isLogin = false;
      }
    }
    
    if (!Config.router.includes(to.name as string)) {
      // 不存在路由 
      // window.location.href = '/';
      // navigateTo('/') 重定向  
      // 抛出错误信息,也可以不抛出, Nuxt 会自动抛出
      throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
    }
  } 
});

去掉 .global,则非全局中间件,如果需要在某个页面中使用该中间件,则可以通过 definePageMeta 进行配置

typescript 复制代码
definePageMeta(
  { middleware: 'auth' } // middleware/auth.ts
)

除了使用 definePageMeta 配置路由拦截外,还能使用 addRouteMiddleware 来进行配置

typescript 复制代码
// plugins/middleware.ts
export default defineNuxtPlugin(() => {
  // 全局中间件:所有路由都会执行
  addRouteMiddleware('global-auth', () => {
    // 逻辑
  }, { global: true })

  // 命名中间件:需要在页面中显式使用 通过 definePageMeta
  addRouteMiddleware('named-auth', () => {
    // 逻辑
  })
})

addRouteMiddlewaredefinePageMeta 的区别

  • 使用位置不同 :

    • addRouteMiddleware :在插件(plugins)中使用,用于动态注册中间件
    • definePageMeta :在页面组件中使用,用于静态声明中间件
  • 作用范围不同 :

    • addRouteMiddleware :可以注册全局中间件( global: true )或命名中间件
    • definePageMeta :只能为当前页面配置中间件
  • 使用场景 :

    • addRouteMiddleware :适合需要动态注册或全局使用的中间件,比如全局的身份验证
    • definePageMeta :适合特定页面的中间件需求,比如某个管理页面的权限检查
  • 执行顺序 :

    • 全局中间件最先执行
    • 然后是布局中间件(如果有)
    • 最后是页面中间件
  • 灵活性 :

    • addRouteMiddleware 更灵活,可以在运行时动态添加中间件
    • definePageMeta 是静态配置,在编译时就确定了

八、如何在 Nuxt3 中 更友好的处理页面报错

比如:访问不存在的页面

体验不是很好,我们可以在 项目根目录下创建 error.vue,来自定义错误页面

vue 复制代码
<template>
  <div>错误页面</div>
</template>
<script></script>

九、如何在 Nuxt3 中做 SEO

Nuxt3 中 提供了 useHead

首先我们需要在服务端,拿到配置好的 SEO 参数。通过 useHead 渲染后,返回给到客户端。

可以通过 useAsyncData 实现在服务端发送请求。

vue 复制代码
<!-- layout.vue -->
<template></template>
<script setup>

const res = await useAsyncData('siteData', () => {
   return $fetch(`${url}`, {
       method: 'post',
       headers: {
           // ...
       },
       body: {
           // ... 参数
       }
   })
});
</script>

此时通过 useAsyncData 发送请求。 在页面显示时,我们是看不到发送请求的。在使用 useHead 初始化 SEO

typescript 复制代码
useHead({
    title: `${res.data.title}`,
    meta: [
      { name: 'description', content: `${res.data.desc}` },
      { name: 'keywords', content: `${res.data.keywords}` },
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: `${res.data.favicon}` },
    ]
})

举例:

typescript 复制代码
useHead({
    title: "我是标题",
    meta: [
      { name: "description", content: "我是 description" },
      { name: "keywords", content: "我是 keywords" },
    ],
    link: [
      {
        rel: "icon",
        type: "image/x-icon",
        href: "https://static.tdesign.tencent.com/vue/apple-touch-icon.png",
      },
    ],
});

上述是全局设置,单页面也是可以进行设置的,可以通过 definePageMeta 进行设置。

definePageMeta

十、如何在 Nuxt3 中全局加载资源

在 nuxt.config.ts 中进行配置

typescript 复制代码
export default defineNuxtConfig({
     css: ["~/assets/index.css"], // 加载 css
     vite: {
        css: {
          preprocessorOptions: {
            less: {
              additionalData: '@import "@/assets/mixin.less";' // 加载 less
            },
            scss: {
              additionalData: '@use "~/assets/func.scss" as *;', // 加载 scss
            },
          },
        },
     },
      app: {
        head: {
          script: [
            {
              src: `https://static.tdesign.tencent.com/vue/assets/index-bba43f31.js`, // 加载 javascript
            },
          ],
        },
      },
})
相关推荐
慧一居士1 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead3 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
伍哥的传说7 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409197 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app