百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

nuxt3 + ts + vue3 vue3服务端渲染,从构建到部署,兼容pc+移动端

myzbx 2025-03-26 14:23 5 浏览

简介:该教程兼容pc+移动端,兼容原理:同时封装了pc框架element-plus、和移动端框架vant,根据不同客户端类型渲染不同的组件,同一个组件需要根据客户端类型同时封装pc和移动端,如果觉得开发麻烦,可忽略兼容部分教程,根据需要分别构建pc、和移动端

nuxt3:https://www.nuxt.com.cn/docs/guide/directory-structure/pages

vue3:https://cn.vuejs.org/

nuxt.config:https://www.nuxt.com.cn/docs/api/configuration/nuxt-config

elementPlus:https://element-plus.gitee.io/zh-CN/

vant:https://vant-contrib.gitee.io/vant/#/zh-CN

axios:https://www.axios-http.cn/docs/api_intro

一、构建项目

访问
https://codeload.github.com/nuxt/starter/tar.gz/refs/heads/v3
,下载压缩报解压到项目下,或者运行npx nuxi@latest init vue_common,需把typescript更新为4.3.5版本

yarn install 安装依赖 
yarn dev -o 启动

二、路由,新建pages文件夹

  1. 普通路由 ,每个pages文件下的vue都是一个路由
  • 新建index.vue
  • 修改app.vue
  1. 嵌套路由
  • 新建pages>demo.vue

<script lang="ts" setup>
    definePageMeta({
        title: 'My home page'
    })
</script>
  • 新建pages>demo>test>index.vue、pages>demo>test1>index.vue
//demo>test>index.vue

<script lang="ts" setup>
    const route = useRoute()
    console.log(route.meta.title)
</script>

浏览器访问:http://localhost:3000/demo/test
//demo>test1>index.vue


浏览器访问:http://localhost:3000/demo/test1
  1. 动态路由,创建:pages>dynami[group]>[id].vue


浏览器访问:http://localhost:3000/dynamicGroup/123

三、根目录下创建静态资源文件 assets

  1. 创建assets>font字体文件,可下载阿里字体图标库 ,解压后文件放入font文件下
  1. 创建css文件
  • 集成sass
yarn add sass
  • 创建globalsIndex.scss
/**
* 涉及全局px需要在此,定义样式,并在globals引用,用来兼容pc和移动,不用兼容可以直接写在 globals,也可以全局引入@import "globalsIndex"
*/

body{
  font-size: $font_size
}
  • 创建globals.scss 全局样式
/**
* 涉及px单位的,需要做pc和移动端的兼容;不需要兼容,可去掉此代码;
*/

.pc{
  @import "globalsIndex";
}

.mobile{
  @import "globalsIndex";
}
  • 创建normalize.scss 统一浏览器样式,下载链接:http://nicolasgallagher.com/about-normalize-css/https://github.com/necolas/normalize.css
  • 创建variable.scss全局scss变量
$font_size: 14px;//基础字体大小
  • 创建iframe.scss 全局样式导入
@import "./variable";
@import "./globals";
@import "./normalize";
@import "../font/iconfont.css";
  • 引入全局scss文件,修改nuxt.config.ts
export default defineNuxtConfig({
    ...
    css: [
        "@/assets/css/iframe.scss"
    ],
    vite: {
        css: {
            preprocessorOptions: {
                scss: {
                    additionalData: '@import "assets/css/variable.scss";',
                }
            }
        }
    },
    ...
})
  1. 创建image文件
  • 配置别名
export default defineNuxtConfig({
    ...
    alias: {
        "@img": "~assets/image/",
    },
    ...
})
  • 使用

四、状态管理器 pinia
https://pinia.web3doc.top/introduction.html

  1. 安装依赖
yarn add @pinia/nuxt --dev
  1. 修改nuxt.config.ts
export default defineNuxtConfig({
    ...
   modules: ["@pinia/nuxt"],  //配置Nuxt3的扩展的库
    ...
})
  1. 创建第一个store:useMobile
  • 创建store文件夹,修改nuxt.config.ts(后面会把store文件放入公共hooks,就不需要改配置)
export default defineNuxtConfig({
    ...
   alias: {"@store":'store'},
    ...
})
  • 创建 useMobile.ts,用于判断是哪个客户端(pc,移动)
import { defineStore } from "pinia";

interface IsMobileInterface {
    isMobile: boolean;
}

const useMobile = defineStore("mobile", {
    state: ():IsMobileInterface => {
        return {
            isMobile: false,
        };
    }
});

export default useMobile
  • 修改app.vue 判断是否是移动端
//app.vue


<script lang="ts" setup>
  import {onMounted} from "vue";
  import {storeToRefs} from "pinia";
  import useMobile from "@store/useMobile";

  const mobileStore = useMobile();
  let {isMobile} = storeToRefs(mobileStore);

  onMounted(()=>{
      //判断是哪个客户端(pc,mobile),主要用来兼容样式
      if (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {
          //增加全局class,用于设置全局样式
          document.getElementsByTagName('html')[0].className = 'mobile';
          isMobile.value = true;
      }else{
          //增加全局class,用于设置全局样式
          document.getElementsByTagName('html')[0].className = 'pc';
          isMobile.value = false;
      }
  });
</script>
  • 持久化储存 pinia-plugin-persistedstate,只有store变化时,才会储存,app.vue中不会生效
yarn add @pinia-plugin-persistedstate/nuxt --dev
export default defineNuxtConfig({
    ...
   modules: [  //配置Nuxt3的扩展的库
    "@pinia/nuxt",
    '@pinia-plugin-persistedstate/nuxt',
   ],
    ...
})
//localStorage
import { defineStore } from "pinia";
interface IsMobileInterface {
    isMobile: boolean;
}

const useMobile = defineStore("mobile", {
    state: ():IsMobileInterface => {
        return {
            isMobile: false,
        };
    },
    persist: {
        storage: persistedState.localStorage,
    }
});
export default useMobile

//sessionStorage
import { defineStore } from "pinia";
interface IsMobileInterface {
    isMobile: boolean;
}

const useMobile = defineStore("mobile", {
    state: ():IsMobileInterface => {
        return {
            isMobile: false,
        };
    },
    persist: {
        storage: persistedState.sessionStorage,
    }
});
export default useMobile

//cookie
import { defineStore } from "pinia";
interface IsMobileInterface {
    isMobile: boolean;
}
const useMobile = defineStore("mobile", {
    state: ():IsMobileInterface => {
        return {
            isMobile: false,
        };
    },
    persist: {
        storage: persistedState.cookiesWithOptions({
            sameSite: false,
        }),
    }
});
export default useMobile

export default defineNuxtConfig({
  ...
  modules: [
    "@pinia/nuxt",
    "@pinia-plugin-persistedstate/nuxt"
  ],
  piniaPersistedstate: {
    cookieOptions: {
      sameSite: "strict",
    },
    storage: "localStorage"//默认储存方式
  },
   ...
})

五、集成postcss

  1. 安装依赖
yarn add postcss-px-to-viewport-8-plugin --dev
  1. 修改nuxt.config.ts
export default defineNuxtConfig({
    ...
    postcss: {
        plugins: {
            "postcss-px-to-viewport-8-plugin": {
                viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度
                viewportHeight: 912, // 视窗的高度,对应的是我们设计稿的高度
                unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
                viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
                propList: ['*'],
                selectorBlackList: [/^.pc/],
                minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
                mediaQuery: false, // 允许在媒体查询中转换`px`,
                exclude: [/pc[\s\S]*.vue/,/element-plus/,/elementTheme.scss/], //设置忽略文件,用正则做目录名匹配
            }
        }
    }
    ...
})

参数

说明

类型

默认值

unitToConvert

需要转换的单位,默认为 px

string

px

viewportWidth

设计稿的视口宽度,如传入函数,函数的参数为当前处理的文件路径,函数返回

undefind

跳过转换

number | Function

320

unitPrecision

单位转换后保留的精度

number

5

propList

能转化为 vw 的属性列表

string[]

['*']

viewportUnit

希望使用的视口单位

string

vw

fontViewportUnit

字体使用的视口单位

string

vw

selectorBlackList

需要忽略的 CSS 选择器,不会转为视口单位,使用原有的 px 等单位

string[]

[]

minPixelValue

设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换

number

1

mediaQuery

媒体查询里的单位是否需要转换单位

boolean

false

replace

是否直接更换属性值,而不添加备用属性

boolean

true

landscape

是否添加根据

landscapeWidth

生成的媒体查询条件

@media (orientation: landscape)

boolean

false

landscapeUnit

横屏时使用的单位

string

vw

landscapeWidth

横屏时使用的视口宽度,,如传入函数,函数的参数为当前处理的文件路径,函数返回

undefind

跳过转换

number

568

exclude

忽略某些文件夹下的文件或特定文件,例如 node_modules 下的文件,如果值是一个正则表达式,那么匹配这个正则的文件会被忽略,如果传入的值是一个数组,那么数组里的值必须为正则

Regexp

undefined

include

需要转换的文件,例如只转换 'src/mobile' 下的文件 (

include: /\/src\/mobile\//

),如果值是一个正则表达式,将包含匹配的文件,否则将排除该文件, 如果传入的值是一个数组,那么数组里的值必须为正则

Regexp

undefined

六、配置页面标题、icon、meta等

  1. 修改nuxt.config.ts
export default defineNuxtConfig({
    ...
    app: {
        head: {
            title: "项目标题",
            meta: [
                {name: "description", content: "项目的重点信息描述--" },
                {name: "keywords", content: "项目关键词" },
                {name:"applicable-device", content:"pc,mobile"}, // 移动pc适配
            ],
            link: [{
                rel:"icon",
                type: "image/x-icon",
                href:"/new_favicon.ico"
            }],
        }
    }
    ...
})

七、集成ui框架

  1. 移动端vant,设计尺寸375
  • 安装依赖
yarn add vant
yarn add @vant/nuxt --dev
  • 修改nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    "@vant/nuxt"
  ],
  vant: { /** Options */ }
})
  • Vant组件已经可以被自动导入,而且也包括自动导入showDialog、showToast等方法
button
  • 自定义主题 新建 vanTheme.scss,主题配置相关变量https://vant-contrib.gitee.io/vant/#/zh-CN/config-provider
:root:root {
  --van-primary-color: red;
}
//iframe.scss
@import "./vanTheme";
@import "./globals";
@import "./normalize";
@import "../font/iconfont.css";
  1. 集成element plus
  • 安装依赖
yarn add element-plus
yarn add @element-plus/nuxt --dev


  • 修改nuxt.config.ts
export default defineNuxtConfig({
  ...
  modules: [
    "@element-plus/nuxt"
  ],
  elementPlus: { /** Options */ }
  ...
})
  • element-plus组件已经可以被自动导入
button
  • 自定义主题 新建 elementTheme.scss,主题相关变量配置 node_modules/element-plus/theme-chalk/src/common/var.scss
:root:root{
  --el-color-primary: green;
}
//iframe.scss
@import "./elementTheme";
@import "./globals";
@import "./normalize";
@import "../font/iconfont.css";
  1. 编写兼容pc、移动端组件
  • 依次创建components>platform>button
  • button文件夹下创建 pcButton.vue、pc.scss



//pc.scss
@import "index";
  • button文件夹下创建 mobileButton.vue、mobile.scss


//mobile.scss
@import "index";
  • button文件夹下创建 button.vue、index.scss


<script lang="ts" setup>
const mobileStore = useMobile();
</script>
.button{
  font-size: 14px;
  height: 32px;
  padding: 4px 15px;
  border-radius: 6px;
}
  • 修改nuxt.config.ts
export default defineNuxtConfig({
    ...
    components: [{
        path: '~/components/',
        pathPrefix: false, //只根据名称使用组件
    }],
    ...
})
  • 直接使用文件名

八、构建布局页面Layouts

  1. 创建layouts 文件夹

布局放在layouts目录中,使用时将通过异步导入自动加载。布局是通过添加到的app.vue,或者手动指定它作为的一个prop,或者在pages页改变它

  1. 创建layouts>default.vue
//app.vue
  1. 创建layouts>custom.vue
//app.vue
//index.vue


<script lang="ts" setup>
    definePageMeta({
        layout: "custom",
    });
</script>
  1. 动态改变布局
//index.vue


<script lang="ts" setup>
    function enableCustomLayout () {
      setPageLayout('custom')
    }
    definePageMeta({
      layout: false,
    });
</script>
  1. 在每页的基础上重写布局
//index.vue

<script lang="ts" setup>
    definePageMeta({
        layout: false,
    });
</script>

九、服务端目录service

  1. server 路由,创建server目录,创建.ts文件,并非.tsx

server/api中的文件在它们的路由中会自动以/api作为前缀。 对于添加没有/api前缀的服务器路由,可以将它们放到 server/routes目录中,每个文件都应该导出一个用defineEventHandler()定义的默认函数,处理程序可以直接返回JSON数据,一个Promise或使用event.node.res.end()发送响应

  1. 创建server>routes>创建routes.ts
export default defineEventHandler(() => 'service demo')

浏览器:http://localhost:3000/routes
页面:await useFetch('/routes')
  1. 创建server>api>demo.ts
export default defineEventHandler(() => 'service api demo')

浏览器:http://localhost:3000/api/demo
页面:await useFetch('/api/demo')
  1. 指定请求方式可以用.get, .post, .put, .delete作为后缀,新建server>routes>routes.post.ts、server>api>demo.post.ts
//routes>routes.post.ts
export default defineEventHandler(() => 'service router post")
//api>demo.post.ts
export default defineEventHandler(() => 'service api post")

页面:await useFetch('/routes',{ method: 'post'})
页面:await useFetch('/api/demo',{ method: 'post'})
  1. 路由参数,新建server>routes>par>[name].post.ts、server>api>[name].post.ts,不要在routes根目录下创建带参的server
//routes>par>[name].post.ts
import {H3Event,H3EventContext} from "h3";
export default defineEventHandler((event:H3Event) => {
    let params = event.context.params as H3EventContext;
    return `service router post ${params.name}`
});
//api>[name].post.ts
import {H3Event,H3EventContext} from "h3";
export default defineEventHandler((event:H3Event) => {
    let params = event.context.params as H3EventContext;
    return `service api post ${params.name}`
});

页面:await useFetch('par/routerName',{ method: 'post'});
页面:await useFetch('/routerName',{ method: 'post'});
  1. 嵌套路由,新建server>api>slug>[...slug].ts
import { createRouter, defineEventHandler, useBase } from "h3";

const router = createRouter()

router.get('/slugGet', defineEventHandler(() => 'slugGet'))
router.post('/slugPost', defineEventHandler(() => 'slugPos'))

export default useBase('/api/slug', router.handler);

浏览器:http://localhost:3000/api/slug/slugGet
页面:await useFetch('/api/slug/slugGet');
页面:await useFetch('/api/slug/slugPost',{ method: 'post'});
  1. 处理body、query、error、设置状态码、runtimeConfig、cookie,以api>[name].post.ts为例子
import {H3Event, H3EventContext} from "h3";

export default defineEventHandler(async (event:H3Event) => {
    const body = await readBody(event);
    const query = getQuery(event);
    let params = event.context.params as H3EventContext;
    const config = useRuntimeConfig();
    const cookies = parseCookies(event)
    const id = parseInt(params.id) as number;
    // if (!Number.isInteger(id)) {
    //     throw createError({
    //         statusCode: 400,
    //         statusMessage: "错误处理",
    //     })
    // }
    // setResponseStatus(200, '21321')
    return {
        text:`service router post ${params.name}`,
        params,
        body,
        query,
        config,
        cookies
    }
});

页面:await useFetch('api/apiName',{
    method: 'post',
    body: { test: 'body' },
    query:{ test: 'query' }
});
  1. 创建server/middleware,中间件

中间件处理程序将在每个请求上运行,再运行任何其他服务器路由,以添加或检查标头、记录请求或扩展事件的请求对象,相当于api拦截器

export default defineEventHandler((event) => {
    console.log('New request: ' + event.node.req.url)
})
  1. Nitro:https://nitro.unjs.io/config,服务配置,配置server 存储示例
  • 配置server,修改nuxt.config.ts
//nuxt.config.ts

export default defineNuxtConfig(
    ...   
    nitro: {
        storage: {
            redis: {
                driver: "redis",
                port: 3000,
                host: "localhost",
                username: "",
                password: "",
                db: 0,
                tls: {}
        }
    }
}
    ...
});
  • 修改api>[name].post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  let params = event.context.params as H3EventContext;
  await useStorage().setItem('redis:Storage', body)
  return `service router post ${params.name}`
})
  • 修改api>demo.ts
export default defineEventHandler(async (event) => {
  const data = await useStorage().getItem('redis:Storage')
  return data
});

 页面:
 const { data: resDataSuccess } = await useFetch('/api/apiName', {
     method: 'post',
     body: { text: 'storage' }
 })
 const { data: resData } = await useFetch('/api/demo')

  1. 集成axios
yarn add axios

页面:import axios from "axios";
await axios.post("http://localhost:3000/api/apiName",{ test: 'axios' })
  1. 封装axios,创建composables>request>useAxiosRequest.ts
/**
 * @description axios公共请求封装
 * */
import axios, {AxiosResponse, InternalAxiosRequestConfig} from "axios";

/**
 * @description 定义相关接口或者枚举
 * */

//请求枚举
export enum MethodEnum {
    Get='GET',
    Post='POST'
}

//返回结果
export interface ResponseResultInterface {
    Header:{},
    Body: Body
}

//请求参数
export interface RequestInterface {
    url:string,
    params?:params,
    method?:MethodEnum
}

/**
 * 封装axios
 * */

// 添加请求拦截器
axios.interceptors.request.use( (config:InternalAxiosRequestConfig)=>{
    return config;
}, (error)=>{
    return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use( (response:AxiosResponse) => {
    return response;
}, (error) => {
    return Promise.reject(error);
});

/**
 * @method useAxiosRequest axios请求封装
 * @param requestPar { RequestInterface } 请求url
 * @return Promise
 * */
const useAxiosRequest= async (requestPar:RequestInterface):Promise => {
    const requestResult:AxiosResponse = await axios({
        method: requestPar.method || MethodEnum.Post,
        url: requestPar.url,
        data: requestPar.params
    });
    return requestResult.data as responseData;
};


export default useAxiosRequest;
  1. 封装useFetch,创建composables>request>useRequest.ts
/**
 * @description  useFetch请求封装
 * */
import {FetchContext} from "ofetch";
import {_AsyncData} from "#app/composables/asyncData";

/**
 * @description 定义相关接口或者枚举
 * */

//请求枚举
export enum MethodEnum {
    Get='GET',
    Post='POST'
}

//返回结果
export interface ResponseResultInterface {
    Header:{},
    Body: Body
}

export interface RequestInterface{
    url:string,
    method?: MethodEnum,
    params?: params
}

/**
 * @method useRequest useFetch请求封装
 * @param requestPar {RequestInterface} 请求参数
 * */

const useRequest = async (requestPar:RequestInterface):Promise=>{
    const requestResult:_AsyncData = await useFetch(requestPar.url,{
        method:requestPar.method || MethodEnum.Post,
        params:requestPar.params || {},
        //请求拦截
        onRequest(requestOption:FetchContext):void {
            // options.headers = options.headers || {}
        },
        //错误请求拦截
        onRequestError(RequestErrorOption:FetchContext):void {
            // throw createError({
            //     statusCode: 500,
            //     statusMessage: reqUrl,
            //     message: '自己后端接口的报错信息',
            // })
        },
        //响应拦截
        onResponse(ResponseOption:FetchContext):void {
        },
        //响应错误拦截
        onResponseError(responseErrorOption:FetchContext):void {
            // throw createError({
            //     statusCode: 500,
            //     statusMessage: reqUrl,
            //     message: '自己后端接口的报错信息',
            // })
        }
    });

    return requestResult.data.value as responseData;
}

export default useRequest;
  1. 根据自己喜好选择axios、useFetch,axios属于客户端发送请求受跨域影响,useFetch属于第一次渲染页面为服务端请求无跨域影响、再次返回页面为客户端请求受跨域影响。如果需要同时使用两种,建议把共同的枚举统一放到同一个文件

十、插件plugins

  1. 创建plugins目录,创建.ts文件,并非.tsx

自动plugins目录中的文件,并在创建Vue应用程序时加载它们。可以在文件名中使用.server.client后缀来只在服务器端或客户端加载插件。

  1. 创建plugins>demo.vue
export default defineNuxtPlugin(nuxtApp => {
    return {
        provide: {
            demo: (msg: string) => `demo ${msg}`
        }
    }
});

页面:

<script lang="ts" setup>
    const { $demo } = useNuxtApp()
</script>
  1. 控制插件注册顺序,在文件名上加前加上数字
  • 创建plugins>1.demos.ts
export default defineNuxtPlugin(nuxtApp => {
    return {
        provide: {
            demo1: (msg: string) => `demo1 ${msg}`
        }
    }
});
  • 创建plugins>2.demo.ts,2.demo.ts可以使用1.demos.ts
export default defineNuxtPlugin(nuxtAp => {
    return {
        provide: {
            demo2: (msg: string) => `${nuxtApp.$demo1('demo2')} ${msg}`
        }
    }
});
  1. 创建监听hook插件,plugins>watchHook.ts
export default defineNuxtPlugin((nuxtApp) => {
  // 客户端 & 服务端
  nuxtApp.hook("app:created", (vueApp) => {
    console.log("app:created");
  });
  // 服务端
  nuxtApp.hook("app:beforeMount", (vueApp) => {
    console.log("app:beforeMount");
  });
  // 客户端 & 服务端
  nuxtApp.hook("vue:setup", () => {
    console.log("vue:setup");
  });
  // 服务端
  nuxtApp.hook("app:rendered", (renderContext) => {
    console.log("app:rendered");
  });
  // 客户端
  nuxtApp.hook("app:mounted", (vueApp) => {
    console.log("app:mounted");
  });
});
  1. 创建全局vue自定义指令,plugins>directive.ts
import {DirectiveBinding, VNode} from "@vue/runtime-core";
export default defineNuxtPlugin(nuxtApp => {
    nuxtApp.vueApp.directive('focus', {
        // 在绑定元素的 attribute 或事件监听器被应用之前调用
        created:(el)=>{},
       // 在绑定元素的父组件挂载之前调用
        beforeMount:(el)=>{},
        // 在包含组件的 VNode 更新之前调用
        beforeUpdate:(el)=>{},
        // 在包含组件的 VNode 及其子组件的 VNode 更新之后调用
        updated:(el)=>{},
        // 在绑定元素的父组件卸载之前调用
        beforeUnmount:(el)=>{},
        // 卸载绑定元素的父组件时调用
        unmounted:(el)=>{},
        // 绑定元素的父组件被挂载时调用
        mounted:(el)=>{
            el.focus()
        },
        //渲染时向元素添加属性
        getSSRProps:(binding:DirectiveBinding, vnode:VNode) =>{
            return {
                id:'test'
            }
        }
    })
});

页面:

十一、创建公共hooks

  1. 创建composables目录

Nuxt 3使用composables/目录,将hooks自动导入vue程序

  1. 把之前创建的store文件放进去

  1. 修改nuxt.config.ts,去除@store配置
export default defineNuxtConfig({
    ...
    imports: {
        dirs: 
            "composables/**"
        ]
    },
    ...
});

页面使用:const mobileStore = useMobile();//相比之前不需要在import导入

十二、其他

  1. 创建utils目录:存放一些公共函数、或者枚举,会自动导入vue程序;
  2. nuxt api
  3. nuxt常用命令:https://nuxt.com/docs/api/commands/add
  • nuxt dev -o -p 3002启动一个热加载的 Web 服务器(开发模式) localhost:3002,默认端口3000
  • nuxt build 利用 webpack 编译应用,压缩 JS 和 CSS 资源(发布用)。
  • nuxt start 以生产模式启动一个 Web 服务器 (需要先执行nuxt build)。
  • nuxt generate 编译应用,并依据路由配置生成对应的 HTML 文件 (用于静态站点的部署)。
  • nuxt preview 在运行build命令后,preview 命令启动一个服务器来预览你的Nuxt应用程序
  • nuxt prepare 命令在应用程序中创建一个.nuxt 目录并生成类型
  • nuxi cleanup 用来删除 Nuxt 自动产生的文件和缓存
  • nuxi upgrade 将目前项目的 Nuxt 3 升级至最新的版本
  1. 多环境配置
  • 根目录创建env>.env.dev文件
#开发环境配置
VITE_HOST=http://127.0.0.1:3000/  #本地请求ip+端口
VITE_PROXY=http://127.0.0.1  #需要代理的ip
VITE_PROXY_3002=3002  #需要代理的端口
  • 修改nuxt.config.ts
import{loadEnv}from"vite";
 
/**
*@description定义env接口*/
interfaceENV_CONFIG{
    host:string
    host1:string
}
const envScript=(process.envasany).npm_lifecycle_script.split('');
const envName=envScript[envScript.length-1];//通过启动命令区分环境
const envData=loadEnv(envName,'env') as ENV_CONFIG|unknown;

export default defineNuxtConfig({
    ...
    runtimeConfig: {    
        public: envData // 把env放入这个里面,通过useRuntimeConfig获取
    },
    vite: {    
        envDir: '~/env', // 指定env文件夹    
    },
    ...
});

页面:const envConfig = useRuntimeConfig();
package.json配置:"dev": "nuxt dev --mode dev",
  • 服务端代理客户端请求,解决跨域,如:axios、useFetch返回页面后的请求

import{getRequestHeaders, getRequestURL, H3Event, HTTPMethod} from "h3";
 
const {public: {
    VITE_PROXY,
    VITE_PROXY_3002
}} =useRuntimeConfig();
 
interfaceApiInterface{
    [propName:string]:string
}
 
//把已有的ip和端口。组成请求的url
constapiUrlOption:ApiInterface ={
    3002:`${VITE_PROXY}:${VITE_PROXY_3002}`
}
 
//请求路由配置,getArticles是后端提供的api
constapiRouter:ApiInterface ={
   "/getArticles":`${apiUrlOption["3002"]}/getArticles`
};
 
export defaultdefineEventHandler(async (event:H3Event):Promise => {
    const urlInfo:URL = getRequestURL(event);
    const requestUrl:string =apiRouter[urlInfo.pathname];
    if(requestUrl){
        const method:HTTPMethod =getMethod(event);
        // const query:QueryObject =getQuery(event);
        const headers =getRequestHeaders(event);
        const options: any  = {
            responseType: 'json',
        }
        options.headers = headers;
        options.method = method;
        if (method != 'GET') {
            options.body = awaitreadBody(event);
        }
        return await $fetch(requestUrl,options);
    }
});

页面:const envConfig:RuntimeConfig = useRuntimeConfig();
      await useFetch(`${VITE_HOST}getArticles`)


十三、项目部署

  1. 安装nginx:https://nginx.org/en/download.html,下载合适的版本,解压到任意位置 nginx 配置
  • 启动cd到nginx目录运行 nginx.exe
  • 启动cd到nginx目录运行start nginx,访问http://localhost:80 正常访问nginx运行成功
  • 启动cd到nginx目录运行nginx -s reload 重新加载配置
  • 启动cd到nginx目录运行nginx -s stop 停止
  • 启动cd到nginx目录运行nginx -s quit 正常关闭
  • 启动cd到nginx目录运行nginx -s reopen 重新打开
  • 配置nginx.confg

 server {
      listen       9001;
      server_name  localhost;
      # server_name  btyhub.site, www.btyhub.site;
      # ssl两个文件,放在 nginx的conf目录中
      # ssl_certificate      btyhub.site_bundle.pem;
      # ssl_certificate_key  btyhub.site.key;

      # ssl_session_cache    shared:SSL:1m;
      # ssl_session_timeout  5m;

      # ssl_ciphers  HIGH:!aNULL:!MD5;
      # ssl_prefer_server_ciphers  on;
      # 代理到Next的服务,默认3000端口,也可以在start的时候指定
      location / {
          proxy_pass    http://127.0.0.1:7000/;
      }

  }


  1. 安装 pm2,node进程管理工具:npm install -g pm2
  2. 安装npm install cross-env -g
  3. 把打包后得.output上传服务器
  4. package.json
{
  "name": "nuxt-app",
  "private": true,
  "scripts": {
    "start": "cross-env PORT=7000 HOST=127.0.0.1 node .output/server/index.mjs"
  }
}
  1. 安装child_process,运行npm install child_process -g 或 yarn add child_process --dev,创建start.js
let exec = require("child_process").exec;
exec("yarn start", {windowsHide: true});
  1. 运行pm2 start start.js --name projectName pm2:node进程管理工具

相关推荐

06Ni9DR容器板,06Ni9DR钢板切割,06Ni9DR钢板规格尺寸

06Ni9DR容器板,06Ni9DR钢板切割,06Ni9DR钢板规格尺寸06Ni9DR是低温容器钢板,含有9%Ni的钢板,06Ni9DR因为钢板Ni含量在百分之0.9左右又被叫作9镍钢或9Ni钢,06...

TOS5系统命令行部署Docker版网心云

  Docker版网心云也就官主叫做容器魔方,由网心云推出的一款docker容器镜像软件,目前支持:铁威马TOS、koolshare、群晖、树莓派等支持docker的设备(arm32、aarch64、...

诞生比航母还早,能造的国家屈指可数

蒸汽弹射器是航空母舰上的飞机起飞装置,用于舰载机蒸汽弹射起飞,使用一个平的甲板作为飞机跑道。起飞时一个蒸汽驱动的弹射装置带动飞机在两秒钟内达到起飞速度。蒸气弹射器是一个非常复杂的系统工程,是由起飞系统...

S550QL high strength steel plate、S550QL相当于国内什么材质

S550QLhighstrengthsteelplate、S550QL相当于国内什么材质S550QLhighstrengthsteelplate调质高屈服强度结构钢。S550QL相当于...

特别关注|中垂变形对VLCC油轮载货量的影响

VLCC油轮营运过程中,在船抵达装运港之前,船长需要计算和申报本航次的最大装货量,影响货物最大装货量的各相关因素主要有:●该轮在装货港所允许的最大吃水;●货舱舱容和油品密度;●VLCC航线通常...

南海车改装音响奥迪S5升级弗莱德FP-6A功放—永日汽车音响

奥迪S5的原车音响效果也比较普通的,其实跟其他日产国产车好不了多少,整体表现含糊不清;所以很难满足今天来的奥迪车主聆听需求,为了享受到更好的音乐,为爱车进行音响升级。奥迪S5音响升级配置:前声场:弗莱...

奥迪S5轿跑:354马力+3.0T奥迪进口车,落地近70万,选它还是A7?

前几天我们介绍了奥迪S5,今天我们拿到的是奥迪的S5,可谓是A5的性能版本,往上还有RS5,S5落地近70万,和4缸的奥迪A7同一价位,相当于2台进口A5低配的价格,而这个价位,可供选择的余地太多了...

S5定时器与IEC定时器差异巨大,资料分散难入门,你真的懂了吗?

你有没有想过,小小的定时器,在工业自动化里竟然扮演着如此重要的角色?它就像一位精准的指挥家,掌控着生产线的节奏。今天,我们就来聊聊PLC编程里两种常见的定时器:S5定时器和IEC定时器。它们看起来差不...

LOL英雄联盟S5新赛季季前赛野区入门手册

本文摘要:随着S5季前赛的到来,LOL各个方面都做了不小的改动,而其中改动最明显的莫过于野区,现在从各方面来带大家认识一下改版后的野区。首先是野怪的伤害有了非常大的提升并且惩戒的CD时间被提高到了一分...

六个重点 搞懂全新Audi A5 Coupe改在哪

由于Audi正式进入新一世代后的车款各个都有著令人印象深刻的亮眼表现,品牌旗下的双门轿跑车系A5Coupe的改款同样让人期待不已,也就在2016年6月3日,全新一代AudiA5以及S5终于正式的与...

入门级视频拍摄设备该如何选?(拍视频设备推荐性价比)

如今的年轻人都很热衷于视频制作,B站各种领域的up主层出不穷,而短视频领域,大家也开始倾向于使用专业的设备去拍摄。对于很多想要开启自己视频拍摄生涯的小伙伴来说,如何选择一台入门级的视频拍摄设备很关键,...

pb管规格及用途这么多,你知道多少?

导语:谈到PB管,估计大家都只知其一不知其二,pb管在生活中到底扮演着什么样的角色呢?它给我们的生活带来了那些便利呢?下面让小编带领大家来一块了解pb管的奥秘!pb管的材料是聚丁烯(PB)树脂,开始工...

Bcup S5今日开赛,Life又复出了(b05赛制)

由知名电竞解说BBC(直播间抖音搜索:BBC张宏圣)主办的Bcup魔兽争霸赛S5将于2024年1月3日20点正式开战。目前,S5分组已经全部出炉,下面让我们一起看下。S5最大的亮点莫过于前职业选手Li...

无主之地2:仍然可用的SHiFT代码(2023年1月15日)

兑换方式需要首先注册SHiFT账号,可以在SHiFT网站注册,也可以在游戏中注册。SHiFT网址:https://shift.gearboxsoftware.com游戏中注册:主菜单>...

三年LOL资深玩家,教你在S5玩转VN

做为一个资深的LOL玩家,我已经玩了有几年了,从12年那会就已经开始玩了。在LOL当中我最喜欢的adc就是薇恩没有之一。每次玩LOL,最后一盘必定是薇恩。在这说说个人玩VN的经验跟大家一起分享下,不认...