nuxt3 + ts + vue3 + axios + vant + element-plus + sass + pinia vue3服务端渲染,从构建到部署,兼容pc+移动端

8 篇文章 0 订阅

简介:该教程兼容pc+移动端,如只需一端,可忽略兼容部分教程,根据需要运行的客户端构建项目

  1. nuxt3:https://www.nuxt.com.cn/docs/guide/directory-structure/pages
  2. vue3:web开发框架 https://cn.vuejs.org/
  3. vant官网:ui组件库,https://vant-contrib.gitee.io/vant/#/zh-CN
  4. element-plus官网:https://element-plus.gitee.io/zh-CN/
一、构建项目

访问https://codeload.github.com/nuxt/starter/tar.gz/refs/heads/v3,下载压缩报解压到项目下,或者运行npx nuxi@latest init vue_common,之前配置过vue环境的,最好更新下,开发工具webstrom建议用2023

yarn install 安装依赖 
yarn dev -o 启动
二、路由,新建pages文件夹
  1. 普通路由 ,每个pages文件下的vue都是一个路由
  • 新建index.vue
     <template>2131</template>
    
  • 修改app.vue
    <template>
     	<NuxtPage/>
    </template>
    
  1. 嵌套路由
  • 新建pages>demo.vue
    <template>
    	demo父级页面
    	<NuxtPage/>
    </template>
    <script lang="ts" setup>
       definePageMeta({
      	 title: 'My home page'
        })
    </script>
    
  • 新建pages>demo>test>index.vue、pages>demo>test1>index.vue
    //demo>test>index.vue
    <template>
       demo测试页
    </template>
    <script lang="ts" setup>
       const route = useRoute()
       console.log(route.meta.title)
    </script>
    
    浏览器访问:http://localhost:3000/demo/test
    
    //demo>test1>index.vue
    <template>
    	demo测试页1
    </template>
    
    浏览器访问:http://localhost:3000/demo/test1
    
  • 动态路由,创建:pages>dynami[group]>[id].vue
    <template>
       动态路由1:{{ $route.params.group }} - {{ $route.params.id }}
    </template>
    
    浏览器访问:http://localhost:3000/dynamicGroup/123
    
三、根目录下创建静态资源文件 assets
  1. 创建font字体文件,可下载阿里字体图标库 ,解压后文件放入font文件下
    <i class="iconfont icon-usename"></i>
    
  2. 创建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/",
        },
        ...
    })
    
  • 使用
    <img src="@img/idCard.png"/>
    
四、状态管理器 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,用于判断是哪个客户端(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
    <template>
      <NuxtPage/>
    </template>
    
    <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
    
  2. 修改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/], //设置忽略文件,用正则做目录名匹配
               }
           }
       }
       ...
    })
    
六、配置页面标题、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等方法
    <van-button type="primary" @click="showToast('toast')">button</van-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组件已经可以被自动导入
    <el-button type="primary">button</el-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";
    

3.编写兼容pc、移动端组件

  • 依次创建components>platform>button
  • button文件夹下创建 pcButton.vue、pc.scss
    <template>
        <el-button class="button">
            <slot/>
        </el-button>
    </template>
    
    <style lang="scss" scoped>
        @import "pc";
    </style>
    
    //pc.scss
    @import "index";
    
  • button文件夹下创建 mobileButton.vue、mobile.scss
    <template>
        <van-button class="button">
            <slot/>
        </van-button>
    </template>
    
    <style lang="scss" scoped>
        @import "mobile";
    </style>
    
    //mobile.scss
    @import "index";
    
  • button文件夹下创建 button.vue、index.scss
    <template>
        <ClientOnly>
            <PcButton v-if="!mobileStore.isMobile">
                <slot/>
            </PcButton>
            <MobileButton v-else>
                <slot/>
            </MobileButton>
        </ClientOnly>
    </template>
    
    <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, //只根据名称使用组件
        }],
        ...
    })
    
  • 直接使用文件名
    <Button>button</Button>
    
八、构建布局页面Layouts
  1. 创建layouts 文件夹
    布局放在layouts目录中,使用时将通过异步导入自动加载。布局是通过添加到的app.vue,或者手动指定它作为的一个prop,或者在pages页改变它
  2. 创建layouts>default.vue
    <template>
        一些在所有页面共享的默认布局
        <slot />
    </template>
    
    //app.vue
    <template>
      <NuxtLayout>
        <NuxtPage/>
      </NuxtLayout>
    </template>
    
  3. 创建layouts>custom.vue
    <template>
        <slot name="header"/>
        自定义布局
        <slot />
    </template>
    
    //app.vue
    <template>
      <NuxtLayout name="custom">
        <NuxtPage/>
      </NuxtLayout>
    </template>
    
    //index.vue
    <template>
        <Button>button</Button>
    </template>
    
    <script lang="ts" setup>
        definePageMeta({
            layout: "custom",
        });
    </script>
    
  4. 动态改变布局
    //index.vue
    <template>
        <Button @click="enableCustomLayout">button</Button>
    </template>
    
    <script lang="ts" setup>
    	function enableCustomLayout () {
    	  setPageLayout('custom')
    	}
    	definePageMeta({
    	  layout: false,
    	});
    </script>
    
  5. 在每页的基础上重写布局
    //index.vue
    <template>
        <NuxtLayout name="custom">
            <template #header>重写布局</template>
            当前页面
        </NuxtLayout>
    </template>
    <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()发送响应
  2. 创建server>routes>创建routes.ts
    export default defineEventHandler(() => 'service demo')
    
    浏览器:http://localhost:3000/routes
    页面:await useFetch('/routes')
    
  3. 创建server>api>demo.ts
    export default defineEventHandler(() => 'service api demo')
    
    浏览器:http://localhost:3000/api/demo
    页面:await useFetch('/api/demo')
    
  4. 指定请求方式可以用.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'})
    
  5. 路由参数,新建: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'});
    
  6. 嵌套路由,新建: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'});
    
  7. 处理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' }
    });
    
  8. 创建server/middleware,中间件
    中间件处理程序将在每个请求上运行,再运行任何其他服务器路由,以添加或检查标头、记录请求或扩展事件的请求对象,相当于api拦截器
    export default defineEventHandler((event) => {
        console.log('New request: ' + event.node.req.url)
    })
    
  9. 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' })
    
  2. 封装axios,创建composables>request>useAxiosRequest.ts
    /**
     * @description axios公共请求封装
     * */
    import axios, {AxiosResponse, InternalAxiosRequestConfig} from "axios";
    
    /**
     * @description 定义相关接口或者枚举
     * */
    
    //请求枚举
    export enum MethodEnum {
        Get='GET',
        Post='POST'
    }
    
    //返回结果
    export interface ResponseResultInterface<Body> {
        Header:{},
        Body: Body
    }
    
    //请求参数
    export interface RequestInterface<params> {
        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 <params,responseData>(requestPar:RequestInterface<params>):Promise<responseData> => {
        const requestResult:AxiosResponse = await axios({
            method: requestPar.method || MethodEnum.Post,
            url: requestPar.url,
            data: requestPar.params
        });
        return requestResult.data as responseData;
    };
    
    
    export default useAxiosRequest;
    
    
  3. 封装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<Body> {
        Header:{},
        Body: Body
    }
    
    export interface RequestInterface<params>{
        url:string,
        method?: MethodEnum,
        params?: params
    }
    
    /**
     * @method useRequest useFetch请求封装
     * @param requestPar {RequestInterface} 请求参数
     * */
    
    const useRequest = async <params,responseData>(requestPar:RequestInterface<params>):Promise<responseData>=>{
        const requestResult:_AsyncData<any, any> = 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;
    
  4. 根据自己喜好选择axios、useFetch,axios属于客户端发送请求受跨域影响,useFetch属于第一次渲染页面为服务端请求无跨域影响、再次返回页面为客户端请求受跨域影响。如果需要同时使用两种,建议把共同的枚举统一放到同一个文件
十、插件plugins
  1. 创建plugins目录,创建.ts文件,并非.tsx
    自动plugins目录中的文件,并在创建Vue应用程序时加载它们。可以在文件名中使用.server或.client后缀来只在服务器端或客户端加载插件。
  2. 创建plugins>demo.vue
    export default defineNuxtPlugin(nuxtApp => {
        return {
            provide: {
                demo: (msg: string) => `demo ${msg}`
            }
        }
    });
    
    页面:<template>
        <div>插件:{{ $demo('插件') }}</div>
    </template>
    
    <script lang="ts" setup>
        const { $demo } = useNuxtApp()
    </script>
    
    
  3. 控制插件注册顺序,在文件名上加前加上数字
  • 创建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'
               }
           }
       })
    });
    
    页面:<input v-focus/>
    
十一、创建公共hooks
  1. 创建composables目录
    Nuxt 3使用composables/目录,将hooks自动导入vue程序
  2. 把之前创建的store文件放进去
    在这里插入图片描述
  3. 修改nuxt.config.ts
    export default defineNuxtConfig({
        ...
        imports: {
            dirs: 
                "composables/**"
            ]
        },
        ...
    });
    
    页面使用:const mobileStore = useMobile();//相比之前不需要在import导入
    
十二、其他
  1. 创建utils目录:存放一些公共函数、或者枚举,会自动导入vue程序;
  2. 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<any> => {
        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,下载合适的版本,解压到任意位置
    • 启动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.conf
       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/;
            }
      
        }
      
  • 安装 pm2,node进程管理工具:npm install -g pm2
  • 安装npm install cross-env -g
  • 把打包后得.output上传服务器
  • package.json
    {
      "name": "nuxt-app",
      "private": true,
      "scripts": {
        "start": "cross-env PORT=7000 HOST=127.0.0.1 node .output/server/index.mjs"
      }
    }
    
  • 安装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});
    
  • 运行pm2 start start.js --name projectName,浏览访问:localhost:9001,需要把env 代理部分改成9001端口,否则请求会出现跨域问题,本地搭建;服务器搭建env直接配置服务器地址
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值