nuxt3实战:完整的 nuxt3 + vue3 项目创建与useFetch请求封装

一. 安装

pnpm dlx nuxi@latest init <project-name>

// or

npx nuxi@latest init <project-name>
  • 如遇到报错
    在这里插入图片描述

手动安装:

  1. 浏览器访问报错https请求地址:
    在这里插入图片描述

  2. 点击tar(项目初始文件的下载地址)对应地址,下载starter-3.tar.gz 包到本地

  3. 本地创建项目文件,将压缩包解压到项目文件内

  4. 安装依赖pnpm i / npm install

  5. 启动项目pnpm dev

二. 服务端和客户端

1. 对比vite项目运行和nuxt项目运行`:
  1. vite
    在这里插入图片描述

  2. nuxt
    在这里插入图片描述

  3. nuxt运行在浏览器
    在这里插入图片描述

  4. 总结:
    - vite创建项目,浏览器访问,返回模板html
    - nuxt创建项目,浏览器访问,请求返回渲染后的html, 输出先是服务端渲染的1111,后是客户端的1111

2. 区分serverclient
const runtimeConfig = useRuntimeConfig()
if (runtimeConfig.apiSecret) {
  console.log('server');
} else {
  console.log('clint');
}

或者

if (import.meta.server) {
  console.log('server');
} else {
  // import.meta.client
  console.log('clint');
}

三. 基础配置nuxt.config.ts

1. 环境变量和私有令牌
export default defineNuxtConfig({
  runtimeConfig: {
    // 只在服务器端可用的私有键
    apiSecret: '123',
    // public中的键也可以在客户端使用
    public: {
      apiBase: '/api'
    }
  }
})

或者

# 这将覆盖apiSecret的值
NUXT_API_SECRET=api_secret_token

这些变量通过useRuntimeConfig()组合函数暴露给应用程序的其余部分。

<script setup lang="ts">
const runtimeConfig = useRuntimeConfig()
</script>
2. 全局样式导入

有一个 sass部分 文件,其中包含颜色变量,供你的Nuxt 页面 和 组件 使用。

$primary: #49240F;
$secondary: #E4A79D;
export default defineNuxtConfig({
  // css:['~/assets/css/base.scss'],
  // 或者
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@use "@/assets/_colors.scss" as *;'
        }
      }
    }
  }
})

3. 引入element-plus

安装

npm i element-plus @element-plus/nuxt -D
modules: [
    '@element-plus/nuxt'
  ],

四、路由

1. 创建pages文件夹,内部的.vue文件会自动被创建为路由
  // 路由入口 相当于 router-link
  <NuxtLink to="/">首页</NuxtLink>

  // 路由容器, 相当于 router-view
  <NuxtPage />
2. 命名路由
  • [id].vue一个中括号包裹的文件名,将匹配一个参数化的路由。
  <NuxtLink to="/product/123">产品</NuxtLink>

  <NuxtPage />
  • product/[id].vue
const route = useRoute()
console.log(route.params);
3. 可选路由
  • [[test]] / myTest.vue 两个中括号包裹文件夹名。内部.vue路由访问时test可省略
<NuxtLink to="/test/myTest">可选路由1</NuxtLink><br>
<NuxtLink to="/myTest">可选路由2</NuxtLink>
4. 全局路由
  • [...404].vue 一个中括号...加文件名
5. definePageMeta为你的页面组件定义元数据。
  • login.vue
definePageMeta({
  path: '/login1'
})
  • 访问login会跳转404login1则会跳转login
6. 嵌套路由
  • user.vue同级创建user文件夹,user文件夹内路由为user.vue的子路由

在这里插入图片描述

7. 编程路由

navigateTo在服务器端和客户端均可使用。

if (import.meta.server) {
  navigateTo('/login1')
}
// navigateTo('/login1')
8. 路由中间件
  1. 创建middleware文件夹,内部创建my-middleware.ts,
export default defineNuxtRouteMiddleware((to, from) => {
  console.log("my-middleware", to.path);
  // // 在实际应用中,你可能不会将每个路由重定向到 `/`
  // // 但是在重定向之前检查 `to.path` 是很重要的,否则可能会导致无限重定向循环
  if (to.path === "/about") {
    return navigateTo("/user");
  }
});
  1. 页面使用about.vue
definePageMeta({
  middleware: [
    'my-middleware'
  ]
})
  1. 全局中间件: test.global.ts,必须global结尾
export default defineNuxtRouteMiddleware((to, from) => {
  console.log("全局中间件", to.path);
});
9. 导航守卫
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: '请先登录'
         }
      });
    }
  }
});
onMounted(() => {
  const route = useRoute()
  if (route.query.code === '401') {
    ElMessage.error(route.query.message as string)
  }
})

五. 目录结构

  1. components目录是你放置所有 Vue 组件的地方。

Nuxt 会自动导入该目录中的所有组件

  1. 使用composables目录将你的Vue组合式函数自动导入到你的应用程序中
export const useFoo = () => {
  return useState('foo', () => 'bar')
}
<script setup lang="ts">
const foo = useFoo()
</script>

<template>
  <div>
    {{ foo }}
  </div>
</template>

默认导出可使用驼峰文件名进行访问

  1. 使用utils目录在整个应用程序中自动导入你的工具函数。使用当时同composables

六、请求

<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
/**
 * @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
<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>
  • 调用接口
// 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

  1. 安装pinia
pnpm install pinia @pinia/nuxt
  1. 配置nuxt.config.ts
modules: [
  '@pinia/nuxt',
],
  1. 持久化配置
  • 安装
pnpm i -D @pinia-plugin-persistedstate/nuxt
modules: [
  '@pinia-plugin-persistedstate/nuxt',
],

// 默认存在cookies
// piniaPersistedstate: {
//   storage: 'localStorage'
// },

八、 nuxt错误处理

  1. app.vue同级创建error.vue
<script setup lang="ts">

defineProps({
  error: Object
})

clearError({ redirect: '/login' })
</script>
  1. index.vue
throw createError({ statusCode: 404, message: '404 not found' })
  1. 错误只能从服务端触发

九、SEO优化

useHead函数用于自定义Nuxt应用中单个页面的头部属性。

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
<template>
  <div>
    <p>一些在所有页面之间共享的默认布局内容</p>
    <slot />
  </div>
</template>
  • app.vue页面使用layout
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>

  {/* <NuxtLayout :name="其他layout">
    <NuxtPage />
  </NuxtLayout> */}
</template>

  • login.vue页面不使用layout
definePageMeta({
  layout: false
})
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值