Nuxt3项目搭建
Nuxt3项目搭建
初始化项目
安装项目依赖(node版本至少v16)
npx nuxi@latest init <project-name>
pnpm dlx nuxi@latest init <project-name>
目录结构
理论上一个完整的项目除了常用目录以外,还需要建立 husky、commitlint、prettier、stylelint、tsconfig等以便更好地规范项目。 正常情况下,我们希望项目配置文件比如以上说项目规范配置文件或者是build工程化构建相关配置文件、环境变量等放在根目录下,而项目内容(如页面、组件等)统一放在src文件夹内管理。 所以项目结构最终大致如下:
Nuxt3
├── .husky # Git hooks 工具配置
├── .vscode # vscode配置
├── doc # 项目文档
├── build # 工程化构建相关配置
├── src
│ ├── api # 接口请求服务管理
│ │ └── modules # 接口模块
│ ├── assets # 工程化处理的静态资源
│ ├── components # 项目组件
│ ├── composables # 响应式共享状态
│ ├── constants # 公共方法管理
│ ├── enums # 枚举管理
│ ├── layouts # 布局组件
│ ├── middleware # 路由中间件
│ ├── pages # 页面视图
│ ├── plugins # 项目公共插件
│ ├── public # 不需要工程化处理的静态资源
│ ├── store # 状态管理
│ ├── utils # 静态工具函数
│ └── app.vue # 入口页面
├── .commitlintrc.json # git提交规范检查配置
├── .editorconfig # 编辑器配置
├── env
│ ├── .env.dev # 本地环境
│ ├── .env.test # 测试环境
│ └── .env.prod # 生产环境
├── .eslintignore # eslint忽略文件检查的配置
├── .eslintrc.js # eslint代码规范检查配置
├── .gitignore # git仓库提交忽略配置
├── .lintstagedrc.js # git提交代码规范检查配置
├── nuxt.config.ts # Vite 构建配置入口
├── package.json # 项目包管理文件
├── pnpm-lock.yaml # pnpm包版本管理锁定
├── README.md # 项目说明
└── tsconfig.json # TS编译的配置
而 Nuxt 初始化,app.vue、pages文件等都建立在根目录下,所以我们先在根目录下建立 src 文件夹,并且把 app.vue 文件移入到 src 目录下,修改 nuxt.config.ts文件配置:
export default defineNuxtConfig({
srcDir: 'src/',
});
.nuxt
Nuxt 使用.nuxt/目录在开发中生成您的Vue应用程序。 你不应该碰里面的任何文件,因为整个目录将在运行 nuxt dev 时重新创建。 ## assets
assets/ 目录用于添加构建工具(webpack或Vite)将处理的所有网站资产。 该目录通常包含以下类型的文件: Stylesheets (CSS, SASS, etc.) Fonts Images Icons 如果你想从服务器上提供资产,可以将文件放入 public/ 目录。
public
public/目录直接服务于服务器根目录,包含必须保留其名称的公共文件(例如:robots.txt)或可能不会更改(例如:favicon.ico)。
utils
Nuxt 3 使用 utils/ 目录在整个应用程序中使用自动导入辅助函数和其他实用程序。
app.vue
app.vue 是应用程序的主要组件,可以在组件中定义全局的样式和行为,如路由全局守卫和错误处理等。当应用程序启动时,app.vue 会被渲染为根视图组件,并且在应用程序的整个生命周期内始终存在,可以说 app.vue 是 Nuxt.js 3 应用程序的视图层的入口文件。
layouts 布局
Nuxt提供了一个可定制的布局框架,可以在整个应用程序中使用,非常适合将常见的UI或代码模式提取到可重用的布局组件中。 布局放在layouts/目录中,使用时将通过异步导入自动加载。
默认布局
在layouts目录下添加default.vue 布局文件。 不像其他组件,布局组件必须有一个根元素,以允许 Nuxt 在布局变化之间应用过渡-这个根元素不能是slot。如果你的应用只有一个布局,建议使用app.vue。 在布局文件中,布局的内容将加载在slot中,~/layouts/default.vue
<template>
<div>
<slot />
</div>
</template>
如果你使用app.vue你还需要添加
<template>
<NuxtLayout>
// 在app.vue中没有NuxtLayout组件,内容将会不显示
<NuxtPage/>
</NuxtLayout>
</template>
如果业务组件不使用 <NuxtLayout>
组件包裹,配置布局是不会生效的
配置布局
-| layouts/
---| default.vue
---| custom.vue
可以在 NuxtLayout 中添加 name 属性来覆盖默认布局
<template>
<NuxtLayout :name="layout">
<NuxtPage />
</NuxtLayout>
</template>
<script setup>
// 您可以根据API调用或登录状态来选择此选项
const layout = "custom";
</script>
也可以通过 definePageMeta 设置
<template>
<NuxtLayout>
巴拉巴拉小魔仙
</NuxtLayout>
</template>
<script setup>
definePageMeta({
layout: "custom",
});
</script>
layouts配置
app.vue中代码
<template>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</template>
创建组件src/components/Header.vue
<template>
<div>
222
</div>
</template>
创建src/layouts/default.vue
<template>
<div>
<Header></Header>
<slot />
</div>
</template>
<script setup>
import Header from '~/components/Header.vue'
</script>
即可实现每个页面都有header组件内容
middleware 中间件
Nuxt提供了一个可定制的路由中间件框架,可以在整个应用程序中使用,可以认为路由中间件就是导航守卫,因为它接收当前路由和下一个路由作为参数。
路由中间件有三种
匿名(或内联)路由中间件
直接在使用它们的页面中定义
<script setup>
definePageMeta({
middleware: [
defineNuxtRouteMiddleware((to, from) => {
console.log('匿名路由中间件', to, from)
}),
],
});
</script>
命名路由中间件
放置在middleware/ 目录中,在页面上使用时会通过异步导入自动加载。(注意:路由中间件名称被规范化为串串形式,因此someMiddleware 变成 some-middleware。)
-| middleware/
---| auth.ts
<script setup>
definePageMeta({
middleware: ['auth'],
});
</script>
全局路由中间件
放置在 middleware/目录中(带有.global后缀),并将在每次路由更改时自动运行
-| middleware/
---| auth.global.ts
带 .global 后缀的全局路由中间件,执行顺序优先于app.vue Nuxt提供了两个全局可用的辅助函数,它们可以直接从中间件返回:
- navigateTo 在插件或中间件中重定向到给定的路由。也可以直接调用它来执行页面导航。
- abortNavigation 终止导航,并显示一条可选的错误消息。
不像 vue-router 中的导航守卫,第三个 next() 参数不会被传递,重定向或路由取消是通过从中间件返回值来处理的。可能的返回值有:
- nothing 不会阻塞导航,并且会移动到下一个中间件功能(如果有的话),或者完成路由导航
- return navigateTo('/') - 重定向到给定的路径,并将重定向代码设置为302 Found
- return navigateTo('/', { redirectCode: 301 }) - 重定向到给定的路径,并将重定向代码设置为301 Moved permanent
navigateTo(to: RouteLocationRaw | undefined | null,options?: NavigateToOptions) =>
Promise<void | NavigationFailure> | RouteLocationRaw
interface NavigateToOptions {
replace?: boolean
redirectCode?: number
external?: boolean
}
middleware 实现单点登录
-| middleware/
---| auth.global.ts
-| enums/
---| auth.ts
/**
* enums/auth.ts
**/
export enum WhitePageEnum {
BASE_HOME = 'index',
SERVER_ERROR_PAGE = 'error',
FETCH_TEST_PAGE = 'fetch',
}
/**
* middleware/auth.global.ts
**/
import { WhitePageEnum } from "~/enums/auth";
import { useUserStore } from "~/store/modules/user";
// 白名单
const whitePathList: WhitePageEnum[] = [
WhitePageEnum.BASE_HOME,
WhitePageEnum.SERVER_ERROR_PAGE,
WhitePageEnum.FETCH_TEST_PAGE,
];
export default defineNuxtRouteMiddleware(async (to) => {
const userStore = useUserStore();
// 从 store 获取用户信息
let { userId } = userStore.getUserInfo;
if (!userId) {
const res = await userStore.fetchUserInfo();
userId = res?.userId;
}
if (!whitePathList.includes(to.name as WhitePageEnum)) {
if (!userId) {
const nuxtApp = useNuxtApp();
const { _route, $login } = nuxtApp;
await $login(_route?.fullPath);
}
}
});
useUserStore 内容可以参考本文档状态管理-引入pinia部分
pages 页面
页面目录。Nuxt 提供了一个基于文件的路由,使用 Vue Router 在底层创建路由。pages/index.vue 文件将被映射到应用程序 / 路由。 如果你正在使用app.vue,确保在 app.vue 使用 <NuxtPage/>
组件来显示当前页面。
动态路由
建立页面文件时,如果命名时将任何内容放在方括号内,它将被转换为路由参数。在文件名或目录中混合和匹配多个参数。
-| pages/
---| index.vue
---| users-[group]/
-----| [id].vue
会生成路由:
{
"routes": [
{
"name": "users-group-id",
"path": "/users-:group()/:id()",
"component": "~/pages/users-[group]/[id].vue"
},
]
}
根据上面的例子,你可以通过 $route 对象中的 params 访问组件中的 group & idx
嵌套路由
可以使用 来显示嵌套路由。示例:
-| pages/
---| parent/
------| child.vue
---| parent.vue
这个文件树将生成这些路由:
[
{
path: '/parent',
component: '~/pages/parent.vue',
name: 'parent',
children: [
{
path: 'child',
component: '~/pages/parent/child.vue',
name: 'parent-child'
}
]
}
]
要显示 child.vue 组件,你必须在 pages/parent.vue 中插入 <NuxtPage>
组件:
<!-- parent.vue -->
<template>
<div>
<h1>I am the parent view</h1>
<NuxtPage/>
</div>
</template>
<!-- child.vue -->
<template>
<div>
<p class="text-base text-gray-600">
I am the child view
</p>
</div>
</template>
页面导航
在 setup 可以使用 useRouter、useRoute 来获取路由信息。
<script setup>
const route = useRoute();
const router = useRouter();
const { id } = route.params;
console.log(router.getRoutes());
const handlerToHome = () => {
router.push("/");
};
</script>
通过 router.getRoutes() 我们可以获取到当前 web 项目所有的路由,打印获取到上述动态路由的 name 和 path 如下
{
"name": "users-group-id"
"path": "/users-:group()/:id()"
}
metaData
如果想在应用程序中为每个路由定义元数据,可以使用definePageMeta宏来实现这一点
<script setup>
definePageMeta({
title: 'My home page'
})
const route = useRoute()
console.log(route.meta.title) // My home page
</script>
Element-plus安装
安装引入
1、先安装Element Plus
npm install element-plus --save
2、安装Nuxt官方专门针对引入Element Plus 开发的模块
npm i @element-plus/nuxt -D
3、在nuxt.config.ts中配置modules参数
export default defineNuxtConfig({
modules: [
'@element-plus/nuxt'
],
elementPlus: { /** Options */ }
})
至此,已经引入成功,且所有Element Plus 组件也都可以直接自动导入。
全局配置
所有的配置参数可以点击查看官方文档
如何全局引入element ui 图标 Element Plus UI 的图标并未在nuxt3中做自动导入,所以使用的时候,需要手动从@element-plus/icons-vue中导入, 如下:
<script lang="ts" setup>
import { Document } from '@element-plus/icons-vue'
</script>
<template>
<el-icon><Document /></el-icon>
</template>
因为使用到的图标的地方会非常多,所以这里创建plugins目录中创建一个global.ts文件,用于全局引入一些组件。
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
export default defineNuxtPlugin(async (NuxtApp) => {
// nuxtApp包含的属性可看文档 https://nuxt.com/docs/guide/going-further/internals
// 全局组件引入
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
NuxtApp.vueApp.component(key, component)
}
})
再次在页面中查看
<script lang="ts" setup>
</script>
<template>
<el-icon><Document /></el-icon>
</template>
pinia及数据持久化
安装
npm install pinia @pinia/nuxt
npm i -D @pinia-plugin-persistedstate/nuxt
项目中配置(nuxt.config.ts中)
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt',
],
数据持久化配置,注册插件(plugis/pinia.ts)
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.$pinia.use(piniaPluginPersistedstate)
})
新建文件(store/counter.ts)
// 登录信息持久化保存
import { defineStore } from 'pinia'
export const useLoginStore = defineStore('loginStore', {
state: () => {
return {
userInfo:""
}
},
getters: {
},
actions: {
setUser(e:any){
this.userInfo = e
}
},
persist: {
storage: persistedState.localStorage,
},
})
页面使用
<template>
<div class="cont">
131434
</div>
</template>
<script setup lang="ts">
import { useLoginStore } from '@/store/login'
let login = useLoginStore()
console.log(login.userInfo)
login.setUser('dsdfsf')
console.log(login.userInfo)
</script>
<style scoped lang="scss">
.cont{
height: 100%;
}
</style>
环境配置
获取环境变量可以通过 useRuntimeConfig()里面的public获取,不放在public里面话,客户端渲染是找不到变量的
安装@types/node 本地安装 Node 的 TypeScript 类型描述文件即可解决编译器报错
npm i --save-dev @types/node
//nuxt.config.ts
import { loadEnv } from 'vite'
interface VITE_ENV_CONFIG {
VITE_API_URL: string
}
const envScript = (process.env as any).npm_lifecycle_script.split(' ')
const envName = envScript[envScript.length - 1] // 通过启动命令区分环境
const envData = loadEnv(envName, 'env') as unknown as VITE_ENV_CONFIG
// ts文件中调用环境变量
// const config = useRuntimeConfig()
// console.log(config)
console.log('当前环境:', envData)
console.log('当前环境:', envData.VITE_API_URL)
export default defineNuxtConfig({
runtimeConfig: {
public:{
baseUrl: envData.VITE_API_URL // 把env放入这个里面,通过useRuntimeConfig获取
}
},
vite: {
envDir: '~/env', // 指定env文件夹
},
})
把拿到的环境变量放在runtimeConfig里面

然后在项目根目录新建一个env文件夹

每个env文件设置不同的变量,例如env.dev文件的内容如下:
# 开发环境请求接口地址
VITE_API_URL=https://nuxtjs.org/dev
这里随便添加什么变,需要以VITE_开头,然后通过useRuntimeConfig都能获取到这些变量

然后随便写个axios请求后,请求的地址就改变了

安装scss及配置全局scss文件
npm install sass sass-loader -d
在assets目录下创建scss/_variables.scss文件,这个文件可编写一些公共的scss代码
//_variables.scss
// 定义全局scss
// 通用背景颜色
$backgroundColor: #575AEC;
$textColorUser: rgba(0, 0, 0, 0.90);
// 全局复选框样式
:deep(.el-checkbox__input.is-checked+.el-checkbox__label){
color: $textColorUser;
}
:deep(.el-checkbox){
color: $textColorUser;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner){
background-color: $backgroundColor;
border-color: $backgroundColor;
}
// 全局单选样式
:deep(.el-radio__input.is-checked+.el-radio__label){
color: $textColorUser;
}
:deep(.el-radio){
color: $textColorUser;
}
:deep(.el-radio__input.is-checked .el-radio__inner){
background-color: $backgroundColor;
border-color: $backgroundColor;
}
// 下拉菜单移入效果
:deep(.el-dropdown-menu__item:not(.is-disabled):hover) {
background-color: #F3F3F3;
color: $backgroundColor;
border: none;
}
nuxt.config.ts中配置
//nuxt.config.ts
vite: {
envDir: '~/env', // 指定env文件夹
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "~/assets/scss/_variables.scss";',
},
},
},
},
404页面配置
在这里我采用的是middleware中间件方式来做的404页面,新建全局路由中间件文件,middleware/auth.global.ts
import { useLoginStore } from '@/store/login'
// 路由中间件
export default defineNuxtRouteMiddleware((to, from) => {
// 不存在的页面路由拦截
if ((to.name ?? '') == '' && to.fullPath != '/') {
return navigateTo('/404/404')
}
let path = ['/login/login', '/404/404']
// 未登录跳转到登录页面
// if (useLoginStore().userInfo == '' && !path.includes(to.fullPath) && !to.fullPath.includes('/agreement/agreement')) {
// return navigateTo('/login/login')
// }
// 有登录信息跳转到后台系统
// if (useLoginStore().userInfo != '' && to.fullPath == '/') {
// return navigateTo('/home/home')
// }
})
components 组件
组件名策略
默认情况下,Nuxt自动导入components目录中的任何组件,组件名将基于它的路径、目录和文件名。
| components/
--| Base
----| Footer.vue
如上,该组件名为:BaseFooter
如果只想根据组件的名称而不是路径自动导入组件,那么需要在 nuxt.config.ts 文件中将 pathPrefix 选项设置为false,此时与Nuxt2的命名策略相同:
//nuxt.config.ts
export default defineNuxtConfig({
components: [
{
path: '~/components/',
pathPrefix: false,
},
],
});
| components/
--| Base
----| Footer.vue
绕过自动导入
可以在 nuxt.config.ts 文件中在 components 下配置 path;只有 path 配置路径下的组件才会被自动导入
| components/
--| business/
----| Count.vue
--| public/
----| MyImg.vue
//nuxt.config.ts
export default defineNuxtConfig({
components: [
{
path: '~/components/public', // 默认为 '~/components'
},
],
});
如上只有 public 目录下的组件将被注册,且自动注册的组件名为 MyImg (非 PublicMyImg);business 目录下的组件将被忽略。
可以配置 components 为 false,此时 components 下任何组件都不会被自动导入。
//nuxt.config.ts
export default defineNuxtConfig({
components: false,
});
也可以显式地从 #components 导入组件
<template>
<div>
<LazyMyImg />
</div>
</template>
<script setup>
import { LazyMyImg } from '#components'
</script>
惰性加载组件
要动态导入一个组件(也称为惰性加载组件),则在组件名称前添加 Lazy 前缀。通过使用 Lazy 前缀,你可以延迟加载组件代码,直到合适的时刻
<template>
<div>
<MyImg />
<LazyMyImg />
</div>
</template>
composables 状态共享
composables 目录下的内容也将自动将 Vue 组合导入到应用中,Nuxt 只扫描 composables/ 目录的顶层文件。 Composables 的主要作用是将常用逻辑和逻辑相关的代码抽象出来,以提高代码可复用性和可维护性,如:跨组件创建响应性的、对ssr友好的共享状态—— useState
composables/counter.ts 内容
export const userCounter = () => {
return useState('counter', () => 0);
};
业务组件
<script setup>
import Count from "~/components/business/Count.vue";
const counter = userCounter();
</script>
<template>
<div>
业务组件内容: {{ counter }}
<a-button type="primary" @click="counter--"> - </a-button>
<a-button type="primary" @click="counter++"> + </a-button>
</div>
</template>
business/Count 组件内容
<template>
<div>Count组件内容:{{ counter }}</div>
</template>
<script setup lang="ts">
const counter = userCounter();
</script>