一. 安装
bash
pnpm dlx nuxi@latest init <project-name>
// or
npx nuxi@latest init <project-name>
- 如遇到报错
手动安装:
-
浏览器访问报错
https
请求地址:
-
点击
tar
(项目初始文件的下载地址)对应地址,下载starter-3.tar.gz
包到本地 -
本地创建项目文件,将压缩包解压到项目文件内
-
安装依赖
pnpm i / npm install
-
启动项目
pnpm dev
二. 服务端和客户端
1. 对比vite
项目运行和nuxt
项目运行`:
-
vite
-
nuxt
-
nuxt
运行在浏览器
-
总结:
-
vite
创建项目,浏览器访问,返回模板html
-
nuxt
创建项目,浏览器访问,请求返回渲染后的html
, 输出先是服务端渲染的1111,后是客户端的1111
-
2. 区分server
和client
js
const runtimeConfig = useRuntimeConfig()
if (runtimeConfig.apiSecret) {
console.log('server');
} else {
console.log('clint');
}
或者
js
if (import.meta.server) {
console.log('server');
} else {
// import.meta.client
console.log('clint');
}
三. 基础配置nuxt.config.ts
1. 环境变量和私有令牌
ts
export default defineNuxtConfig({
runtimeConfig: {
// 只在服务器端可用的私有键
apiSecret: '123',
// public中的键也可以在客户端使用
public: {
apiBase: '/api'
}
}
})
或者
bash
# 这将覆盖apiSecret的值
NUXT_API_SECRET=api_secret_token
这些变量通过useRuntimeConfig()组合函数暴露给应用程序的其余部分。
js
<script setup lang="ts">
const runtimeConfig = useRuntimeConfig()
</script>
2. 全局样式导入
有一个 sass部分
文件,其中包含颜色变量,供你的Nuxt
页面 和 组件 使用。
scss
$primary: #49240F;
$secondary: #E4A79D;
js
export default defineNuxtConfig({
// css:['~/assets/css/base.scss'],
// 或者
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "@/assets/_colors.scss" as *;'
}
}
}
}
})
3. 引入element-plus
安装
js
npm i element-plus @element-plus/nuxt -D
js
modules: [
'@element-plus/nuxt'
],
四、路由
1. 创建pages
文件夹,内部的.vue
文件会自动被创建为路由
js
// 路由入口 相当于 router-link
<NuxtLink to="/">首页</NuxtLink>
// 路由容器, 相当于 router-view
<NuxtPage />
2. 命名路由
[id].vue
一个中括号包裹的文件名,将匹配一个参数化的路由。
js
<NuxtLink to="/product/123">产品</NuxtLink>
<NuxtPage />
product/[id].vue
js
const route = useRoute()
console.log(route.params);
3. 可选路由
[[test]] / myTest.vue
两个中括号包裹文件夹名。内部.vue
路由访问时test
可省略
js
<NuxtLink to="/test/myTest">可选路由1</NuxtLink><br>
<NuxtLink to="/myTest">可选路由2</NuxtLink>
4. 全局路由
[...404].vue
一个中括号...
加文件名
5. definePageMeta
为你的页面组件定义元数据。
login.vue
js
definePageMeta({
path: '/login1'
})
- 访问
login
会跳转404
,login1
则会跳转login
6. 嵌套路由
user.vue
同级创建user
文件夹,user
文件夹内路由为user.vue
的子路由
7. 编程路由
navigateTo
在服务器端和客户端均可使用。
js
if (import.meta.server) {
navigateTo('/login1')
}
// navigateTo('/login1')
8. 路由中间件
- 创建
middleware
文件夹,内部创建my-middleware.ts
,
js
export default defineNuxtRouteMiddleware((to, from) => {
console.log("my-middleware", to.path);
// // 在实际应用中,你可能不会将每个路由重定向到 `/`
// // 但是在重定向之前检查 `to.path` 是很重要的,否则可能会导致无限重定向循环
if (to.path === "/about") {
return navigateTo("/user");
}
});
- 页面使用
about.vue
js
definePageMeta({
middleware: [
'my-middleware'
]
})
- 全局中间件:
test.global.ts
,必须global
结尾
js
export default defineNuxtRouteMiddleware((to, from) => {
console.log("全局中间件", to.path);
});
9. 导航守卫
js
export default defineNuxtRouteMiddleware((to, from) => {
// console.log("全局中间件", to.path);
const whiteList = ['/index', '/login', '/404', '/']
if(!whiteList.includes(to.path)){
let token = ''
if(import.meta.client){
token = localStorage.getItem('token') || ''
}
if(!token){
return navigateTo({
path: '/login',
query: {
code: 401,
message: '请先登录'
}
});
}
}
});
js
onMounted(() => {
const route = useRoute()
if (route.query.code === '401') {
ElMessage.error(route.query.message as string)
}
})
五. 目录结构
components
目录是你放置所有Vue
组件的地方。
Nuxt
会自动导入该目录中的所有组件
- 使用
composables
目录将你的Vue
组合式函数自动导入到你的应用程序中
js
export const useFoo = () => {
return useState('foo', () => 'bar')
}
js
<script setup lang="ts">
const foo = useFoo()
</script>
<template>
<div>
{{ foo }}
</div>
</template>
默认导出可使用驼峰文件名进行访问
- 使用
utils
目录在整个应用程序中自动导入你的工具函数。使用当时同composables
六、请求
js
<script setup lang="ts">
// 在SSR中数据将被获取两次,一次在服务器端,一次在客户端。
const dataTwice = await $fetch('/api/item')
// 在SSR中,数据仅在服务器端获取并传递到客户端。
const { data } = await useAsyncData('item', () => $fetch('/api/item'))
// 你也可以使用useFetch作为useAsyncData + $fetch的快捷方式
const { data } = await useFetch('/api/item')
</script>
- 完整
request.ts
js
/**
* @description useFetch
* */
import type { NitroFetchRequest } from 'nitropack';
const apiRequest = <T>(url:NitroFetchRequest, options: any): Promise<ResultData<T>> => {
const config = useRuntimeConfig();
const nuxtApp = useNuxtApp()
const contentType = options.contentType || 'application/json'
return new Promise((resolve, reject) => {
useFetch<ResultData<T>>(url, {
baseURL: config.public.baseURL,
onRequest({ options }) {
let token = "";
if (import.meta.client) {
token = useStore().getToken();
}
options.headers = {
'Content-Type': contentType,
'Cookies': `token=${token}`,
...options.headers,
};
},
onResponse({ response }) {
if(response.status >= 200 && response.status < 300){
if(response._data.code === 200){
resolve(response._data)
} else {
if(import.meta.client){
ElMessage.error(response._data.msg)
} else {
nuxtApp.runWithContext(()=>{
navigateTo({
path: '/Error',
query:{
code: response._data.code,
message: response._data.msg
}
})
})
}
}
}
},
onResponseError({ response }) {
if(import.meta.client){
ElMessage.error(response._data.msg)
} else {
nuxtApp.runWithContext(()=>{
navigateTo({
path: '/Error',
query:{
code: response._data.code,
message: response._data.msg
}
})
})
}
},
...options
});
});
};
interface Result {
code: string;
msg: string;
}
interface ResultData<T = any> extends Result {
data: T;
}
export const getApi = <T>(url:NitroFetchRequest, options: any = {}): Promise<ResultData<T>> => {
return apiRequest(url, {
method: 'GET',
...options
})
}
export const postApi = <T>(url:NitroFetchRequest, options: any = {}): Promise<ResultData<T>> => {
return apiRequest(url, {
method: 'POST',
...options
})
}
[[Error]] / index.vue
js
<script setup lang="ts">
onMounted(() => {
const route = useRoute();
switch (route.query.code) {
case '401':
ElMessage.error('no login')
localStorage.removeItem('token')
break;
case '501':
ElMessage.error('Unknown error' + route.query.message)
break;
default:
ElMessage.error(route.query.code + '' + route.query.message)
}
})
</script>
- 调用接口
js
// template
<p v-for="item in list" :key="item.id">{{ item.name }}</p>
// script
const list = ref<Info[]>()
interface Info {
id: number;
name: string
}
interface List {
list: Info[],
page: number;
total: number;
}
const handleClick = async () => {
const { data } = await getApi<List>('/list')
list.value = data.list
}
七、pinia
- 安装
pinia
js
pnpm install pinia @pinia/nuxt
- 配置
nuxt.config.ts
js
modules: [
'@pinia/nuxt',
],
- 持久化配置
- 安装
js
pnpm i -D @pinia-plugin-persistedstate/nuxt
js
modules: [
'@pinia-plugin-persistedstate/nuxt',
],
// 默认存在cookies
// piniaPersistedstate: {
// storage: 'localStorage'
// },
八、 nuxt
错误处理
app.vue
同级创建error.vue
js
<script setup lang="ts">
defineProps({
error: Object
})
clearError({ redirect: '/login' })
</script>
index.vue
js
throw createError({ statusCode: 404, message: '404 not found' })
- 错误只能从服务端触发
九、SEO
优化
useHead
函数用于自定义Nuxt
应用中单个页面的头部属性。
js
useHead(
{
// title: 'my login',
meta: [
{
name: 'description',
content: 'my login description'
},
{
name: 'keywords',
content: 'my login keywords'
},
],
// titleTemplate: (titleChunk) => {
// return titleChunk ? `${titleChunk} - my login` : 'my login'
// }
titleTemplate: `%s ${name.value}`
}
)
十、layout
布局
layout
目录下创建default.vue
js
<template>
<div>
<p>一些在所有页面之间共享的默认布局内容</p>
<slot />
</div>
</template>
app.vue
页面使用layout
js
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
{/* <NuxtLayout :name="其他layout">
<NuxtPage />
</NuxtLayout> */}
</template>
login.vue
页面不使用layout
js
definePageMeta({
layout: false
})