【🍀新鲜出炉 】十个 “如何”从零搭建 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
            },
          ],
        },
      },
})
相关推荐
黄智勇几秒前
xlsx-handlebars 一个用于处理 XLSX 文件 Handlebars 模板的 Rust 库,支持多平台使
前端
brzhang1 小时前
为什么 OpenAI 不让 LLM 生成 UI?深度解析 OpenAI Apps SDK 背后的新一代交互范式
前端·后端·架构
brzhang1 小时前
OpenAI Apps SDK ,一个好的 App,不是让用户知道它该怎么用,而是让用户自然地知道自己在做什么。
前端·后端·架构
程序员王天2 小时前
【开发AGIC】Vue3+NestJS+DeepSeek AI作业批改系统(已开源)
vue.js·ai编程·nestjs
井柏然2 小时前
前端工程化—实战npm包深入理解 external 及实例唯一性
前端·javascript·前端工程化
昔冰_G3 小时前
Vue内置组件KeepAlive——缓存组件实例
vue.js·缓存·vue3·vue2·keep-alive·vue组件缓存·vue内置组件
IT_陈寒3 小时前
Redis 高性能缓存设计:7个核心优化策略让你的QPS提升300%
前端·人工智能·后端
aklry3 小时前
elpis之动态组件机制
javascript·vue.js·架构
井柏然3 小时前
从 npm 包实战深入理解 external 及实例唯一性
前端·javascript·前端工程化
羊锦磊4 小时前
[ vue 前端框架 ] 基本用法和vue.cli脚手架搭建
前端·vue.js·前端框架