Nuxt3 基础总结
前言
Nuxt3 的对比之前的2 和 1 ,只能感叹前端发展的越来越快了,不学无术
(PS :好好学,我还年轻 我还可以 每天进步一小点)
开发更快
打包更小
支持 vite
支持 vue3
支持自动引入
支持文件路由
支持布局系统
支持多种渲染模式
支持 typescript
支持 composition-api
安装NUXT3 需要node 大于16的版本
brew 更新 node
brew update
brew upgrade node
还有就是 node 安装
npm install -g
npm install -g
sudo n stable
最后查看node版本

然后 安装官网的来
npx nuxi@latest init <project-name>
因为家里的网 没有那么快,需要在hosts 配置
sudo vi /etc/hosts

把上面的代码输入上去 按 : wq 退出保存
然后在运行

最后 安装 cd my-nuxt. npm run dev
打开目录 vscode

vetur 把插件卸载 然后装上 Volar. vue3 的插件 这个是有好
在开发项目中我们经常会使用到一些第三方库/插件、组件来丰富项目,这里列举一些开发后台项目用到的第三方库/插件、组件。
naive-ui
安装 naive-ui 和 @css-render/vue3-ssr
yarn add -D naive-ui @css-render/vue3-ssr
成功之后再配置
在 nuxt.config.ts 增添下列配置
import { defineNuxtConfig } from 'nuxt'
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
build: {
transpile:
process.env.NODE_ENV === 'production'
? [
'naive-ui',
'vueuc',
'@css-render/vue3-ssr',
'@juggle/resize-observer'
]
: ['@juggle/resize-observer']
},
vite: {
optimizeDeps: {
include:
process.env.NODE_ENV === 'development'
? ['naive-ui', 'vueuc', 'date-fns-tz/formatInTimeZone']
: []
}
}
})
然后再添加一个插件
新建目录: plugins - naive-ui.ts
import { setup } from '@css-render/vue3-ssr'
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin((nuxtApp) => {
if (process.server) {
const { collect } = setup(nuxtApp.vueApp)
const originalRenderMeta = nuxtApp.ssrContext?.renderMeta
nuxtApp.ssrContext = nuxtApp.ssrContext || {}
nuxtApp.ssrContext.renderMeta = () => {
if (!originalRenderMeta) {
return {
headTags: collect()
}
}
const originalMeta = originalRenderMeta()
if ('then' in originalMeta) {
return originalMeta.then((resolvedOriginalMeta) => {
return {
...resolvedOriginalMeta,
headTags: resolvedOriginalMeta['headTags'] + collect()
}
})
} else {
return {
...originalMeta,
headTags: originalMeta['headTags'] + collect()
}
}
}
}
})

自定义错误页面
根目录下新建 error.vue
<template>
<n-result status="500" title="错误提示" :description="error.message">
<template #footer>
<n-button @click="handleError">回到首页</n-button>
</template>
</n-result>
</template>
<script setup>
import {
NResult,
NButton
} from "naive-ui"
const prop = defineProps({
error : Object
})
const handleError = () => clearError({ redirect: '/' })
</script>
<style lang="">
</style>
在app.vue
<script setup> import { NButton } from "naive-ui" throwError('故意报错') </script>
就看到了这个页面

自定义全局loading 实现

上面那个不适合服务端渲染
然后要用到其他的UI的其他

然后再。plugins 下新建一个 globalloading.ts 文件
import { createDiscreteApi, ConfigProviderProps, // darkTheme, // lightTheme } from 'naive-ui' export default defineNuxtPlugin((nuxtApp) => { const bar = ref(null); nuxtApp.hook("app:mounted", (e) => { if (!bar.value) { const { loadingBar } = createDiscreteApi(['loadingBar']) bar.value = loadingBar; } console.log('app:mounted') }) nuxtApp.hook("page:start", (e) => { bar.value?.start() //bar.value ? bar.value.start() : ''; console.log('age:start') }) nuxtApp.hook("page:finish", (e) => { console.log('page:finish') // bar.value?.finish() setTimeout(() => { bar.value?.finish() }, 1000) }) nuxtApp.hook("app:error", (e) => { console.log('app;error') }) })
最后就看到了加载的页面进度条

安装 windicss
yarn add nuxt-windicss -D
关于NUXT目录的
Nuxt 使用.nuxt/目录在开发中生成您的Vue应用程序。
你不应该碰里面的任何文件,因为整个目录将在运行 nuxt dev 时重新创建。
就是最上面的nuxt 目录
components 组件
组件名策略
默认情况下,Nuxt自动导入components目录中的任何组件,组件名将基于它的路径、目录和文件名。
惰性加载组件
要动态导入一个组件(也称为惰性加载组件),则在组件名称前添加 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>
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组件,内容将会不显示
</NuxtLayout>
</template>
配置布局
-| layouts/ ---| default.vue ---| custom.vue
可以在 NuxtLayout 中添加 name 属性来覆盖默认布局:
<template>
<NuxtLayout :name="layout">
<NuxtPage />
</NuxtLayout>
</template>
<script setup>
// 您可以根据API调用或登录状态来选择此选项
const layout = "custom";
</script>
也可以通过 definePageMeta 设置
<template>
<NuxtLayout>
2023,12.23
</NuxtLayout>
</template>
<script setup>
definePageMeta({
layout: "custom",
});
</script>
如果业务组件不使用
<NuxtLayout>组件包裹,配置布局是不会生效的
方法
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
<template>
<ul class="text-base text-gray-600">
<li>
获取到的 group 是 <span class="text-green-500 text-xl">{{ group }}</span>
</li>
<li>
获取到的 id 是 <span class="text-green-500 text-xl">{{ id }}</span>
</li>
</ul>
</template>
nuxt3 中内置了四种请求的方法
useFetch
useLazyFetch
useAsyncData
useLazyAsyncData
推荐的话还是 useFetch
const param1 = ref('value1')
const { data, pending, error, refresh } = await useFetch('/api/modules', {
query: { param1, param2: 'value2' }
})
Request 的封装与使用
没有用 nuxt3 自带的接口返回格式才进行这样的简易封装;
- ~enums/interface.ts // 定义接口返回 code 值的枚举
export enum ResultEnum { SUCCESS = 0, TOKEN_OVERDUE = 404, // 用户登录失败 INTERNAL_SERVER_ERROR = 500, // 服务异常 }
- ~/composables/useDefaultRequest.ts // 定义接口统一拦截函数
import { UseFetchOptions } from "nuxt/app";
import { RouteLocationRaw } from "vue-router";
import { ResultEnum } from "~/enums/interface";
interface DefaultResult<T = any> {
code: number;
data: T;
msg: string;
success: boolean;
}
type UrlType = string | Request | Ref<string | Request> | (() => string | Request);
type HttpOption<T> = UseFetchOptions<DefaultResult<T>>;
interface RequestConfig<T = any> extends HttpOption<T> {
// 忽略拦截,不走拦截,拥有 responseData,且 code !== 0 的情况下,直接返回 responseData,
// 但是若无 responseData, 不设置 ignoreGlobalErrorMessage 也会报错
ignoreCatch?: boolean;
// 忽略全局错误提示,走拦截,但是任何情况下都不会提示错误信息
ignoreGlobalErrorMessage?: boolean;
}
const request = async <T>(
url: UrlType,
params: any,
options: RequestConfig<T>
): Promise<DefaultResult<T> | T> => {
const headers = useRequestHeaders(["cookie"]);
const method = ((options?.method || "GET") as string).toUpperCase();
const runtimeConfig = useRuntimeConfig();
const nuxtApp = useNuxtApp();
const { $message, $login } = nuxtApp;
const { apiBaseUrl } = runtimeConfig.public;
const baseURL = `${apiBaseUrl}/mall/api`;
// 处理用户信息过期
const hanlerTokenOverdue = async () => {
const { _route } = nuxtApp;
await $login(_route?.fullPath);
};
// 处理报错异常
const handlerError = (msg = "服务异常") => {
if (process.server) {
showError({ message: msg, statusCode: 500 });
} else {
$message.error(msg);
}
};
const { data, error } = await useFetch(url, {
baseURL,
headers,
credentials: "include",
params: method === "GET" ? params : undefined,
body: method === "POST" ? JSON.stringify(params) : undefined,
...options,
});
const responseData = data.value as DefaultResult<T>;
const { ignoreCatch, ignoreGlobalErrorMessage } = options; // 忽略全局
if (error.value || !responseData) {
if (!ignoreGlobalErrorMessage) handlerError();
return Promise.reject(error.value || "服务响应失败,请稍后重试");
} else {
const { code, data: result, msg } = responseData;
// 接口请求成功,直接返回结果
if (code === ResultEnum.SUCCESS || !code) {
return result;
}
if (!ignoreCatch) {
// 接口请求错误,统一处理
switch (code) {
case ResultEnum.TOKEN_OVERDUE: // 登录信息过期,去登录
// 用户信息过期
await hanlerTokenOverdue();
default:
if (!ignoreGlobalErrorMessage) handlerError(msg);
return Promise.reject(msg || "服务响应失败,请稍后重试");
}
}
}
return responseData;
};
