Nuxt学习日记流水账

127 阅读3分钟

背景

为了拓宽自己技术广度,尝试将自己的毕设迁移到nuxt中。

同时也将功能、接口完善一下

nuxt版本:3.17.6

day1

首先做了试验,想着沿用原先的axios去做接口请求,在网上找了nuxt使用axios的文章,结果发现人家nuxt3就不想支持axios了,于是改用nuxt自带的fetch。

创建useRequest

nuxt的composables中以use开头的文件都会被自动导出,在vue文件中并不需要引入即可使用。

useRequest内容,风格上还是跟axios差不多。

创建之后,我们就可以直接在vue文件中使用const request = useRequest()

 // composables/useRequest.ts
 ​
 interface RequestOptions {
     baseURL?: string
     headers?: Record<string, string>
     timeout?: number
 }
 ​
 interface ResponseData<T = any> {
     code: number
     message: string
     result: boolean
     data: T
 }
 ​
 const defaultOptions: RequestOptions = {
     baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
     timeout: 10000,
 }
 ​
 // 请求拦截器
 const requestInterceptor = (options: any) => {
     // 处理token等情况
     return options
 }
 ​
 // 响应拦截器
 const responseInterceptor = (response: any) => {
 ​
     // 处理响应数据
     const data: ResponseData = response._data
 ​
     // 根据业务逻辑处理不同状态码
     if (data.code !== 200) {
 ​
         // 处理未登录等特殊情况
         if (data.code === 401) {
             navigateTo('/login')
         }
 ​
         return Promise.reject(data)
     }
 ​
     return data.data
 }
 ​
 // 错误处理
 const errorHandler = (error: any) => {
 ​
     // 可以特殊处理一些情况
     return Promise.reject(error)
 }
 ​
 // 创建请求实例
 const createRequest = (options: RequestOptions = {}) => {
     const requestOptions = { ...defaultOptions, ...options }
 ​
     // 创建自定义fetch函数
     const fetchInstance = $fetch.create({
         baseURL: requestOptions.baseURL,
         headers: requestOptions.headers,
         timeout: requestOptions.timeout,
         onRequest: requestInterceptor,
         onResponse: responseInterceptor,
         onRequestError: errorHandler,
         onResponseError: errorHandler,
     })
 ​
     // 封装GET请求
     const get = async <T = any>(url: string, params?: any, config?: any) => {
         return fetchInstance<T>(url, {
             method: 'GET',
             params,
             ...config
         })
     }
 ​
     // 封装POST请求
     const post = async <T = any>(url: string, data?: any, config?: any) => {
         return fetchInstance<T>(url, {
             method: 'POST',
             body: data,
             ...config
         })
     }
 ​
     // 封装PUT请求
     const put = async <T = any>(url: string, data?: any, config?: any) => {
         return fetchInstance<T>(url, {
             method: 'PUT',
             body: data,
             ...config
         })
     }
 ​
     // 封装DELETE请求
     const del = async <T = any>(url: string, params?: any, config?: any) => {
         return fetchInstance<T>(url, {
             method: 'DELETE',
             params,
             ...config
         })
     }
 ​
     return {
         fetch: fetchInstance,
         get,
         post,
         put,
         del
     }
 }
 ​
 const request = createRequest()
 ​
 export default function useRequest() {
     return request
 }
 ​

跨域配置

 // nuxt.config.ts
 export default defineNuxtConfig({
   compatibilityDate: '2025-05-15',
   devtools: { enabled: true },
   devServer: {
     port: 8002,
   },
   nitro: {
     devProxy: {
       '/api': {
         target: 'http://127.0.0.1:3000', // Express API 地址
         changeOrigin: true,
         prependPath: true,
       }
     }
   }
 })

接口存放

这里我希望还是沿用原先的风格,然后在使用nuxt的挂载实例的方式来实现

所以这里就用到了nuxt的plugin,存放在plugin的实例都可以挂载到nuxt实例上

 // plugins\api\index.ts
 import Common from './module/common' // 存放接口
 export default defineNuxtPlugin((nuxtApp) => {
     const request = {
         Common
     }
     nuxtApp.provide('request', request)
 })

day2

接口存放

感觉之前的方式不是很方便,不如直接使用composables,所以做出下面的更改

 // useApi.ts
 import Common from './api/module/common'
 import Role from './api/module/role'
 import User from './api/module/user'
 export default function() {
     return {
         Common,
         Role,
         User
     }
 }

这样可以直接在组件内使用const $request = useApi()

引入element-plus

在这里用到了自动引入的工具

npm install -D unplugin-vue-components unplugin-auto-import

在nuxt.config.ts中配置样式和自动引入的vite插件

 css: [
     'element-plus/dist/index.css'
 ],
 vite: {
     plugins: [
         AutoImport({
             resolvers: [ElementPlusResolver()],
         }),
         Components({
             resolvers: [ElementPlusResolver()],
         }),
     ],
 }

配置token

这里遇到点问题,不知道fetch这个的请求头和响应头是一个专门的Headers对象?然后直接用axios的赋值方式拿不到值奇怪了半天,后来发现这里是得用get、set做取值和赋值的操作

 // 请求拦截器
 const requestInterceptor = ({ options }: { options: any}) => {
     if (!options.headers.get('Authorization')) {
         options.headers.set('Authorization', window.localStorage.getItem('token') || '')
     }
     return options
 }
 ​
 // 响应拦截器
 const responseInterceptor = ({ response }: { response: any }) => {
     if (response.headers.get('Authorization')) {
         window.localStorage.setItem('token', response.headers.get('Authorization'))
     }
     const data: ResponseData = response._data
 ​
     return data.data
 }

引入pinia

npm i pinia @pinia/nuxt

 // nuxt.config.ts
 modules: ['@pinia/nuxt']

day3

引入富文本编辑器

组件选型:quill富文本编辑器

一开始使用的vue-quill-editor来实现这个功能,新建针对其的插件,在nuxt.config.ts增加vue-quill-editorssr配置,但是试了很多次都是报document is not defined

后面才发现,这个版本是适用于vue2.x的,vue3.x需要使用@vueup/vue-quill

全局使用该组件

如果多处使用该组件可以通过plugins去引入

 // nuxt-quill-plugin.js
 import { defineNuxtPlugin } from "#app"
 import { QuillEditor } from '@vueup/vue-quill'
 export default defineNuxtPlugin(nuxtApp => {
     nuxtApp.vueApp.component('QuillEditor', QuillEditor)
 })

局部使用

 <template>
     <QuillEditor v-model:content="content" :options="editorOptions" class="quill-editor" />
 </template>
 ​
 <script setup>
 import editorOption from './editorOption'
 import { QuillEditor } from '@vueup/vue-quill'
 ​
 // 编辑器内容
 const content = ref('<p>初始内容</p>')
 ​
 // 编辑器配置
 const editorOptions = {
     theme: 'snow',
     ...editorOption
 }
 </script>
 ​
 <style>
 @import '@vueup/vue-quill/dist/vue-quill.snow.css';
 ​
 .quill-editor {
     min-height: 300px;
     margin-top: 1rem;
 }
 </style>

按照局部使用的方式去做,编译完之后出现如下错误

image.png

通过查询之后得知,vue-quill的使用需要在浏览器环境、vue环境中使用,nuxt在客户端渲染之前是没有这个环境的

所以需要添加nuxt自带的组件<ClientOnly>,使用这个标签表示,这个组件只有在客户端渲染的时候才会使用

image.png

最终的代码就是:

 <template>
     <ClientOnly>
         <QuillEditor v-model:content="content" :options="editorOptions" class="quill-editor" />
     </ClientOnly>
 </template>
 ​
 <script setup>
 import editorOption from './editorOption'
 const QuillEditor = defineAsyncComponent(() => import('@vueup/vue-quill').then(mod => mod.QuillEditor))
 ​
 // 编辑器内容
 const content = ref('<p>初始内容</p>')
 ​
 // 编辑器配置
 const editorOptions = {
     theme: 'snow',
     ...editorOption
 }
 </script>
 ​
 <style>
 @import '@vueup/vue-quill/dist/vue-quill.snow.css';
 ​
 .quill-editor {
     min-height: 300px;
     margin-top: 1rem;
 }
 </style>