【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)

前言

H5 项目基于 Web 技术,可以在智能手机、平板电脑等移动设备上的浏览器中运行,无需下载和安装任何应用程序,且H5 项目的代码和资源可以集中在服务器端进行管理,只需更新服务器上的代码,即可让所有顾客访问到最新的系统版本。

本系列将以肯德基自助点餐页面为模板,搭建一款自助点餐系统,第一次开发移动端h5项目,免不了有所差错和不足,欢迎各位大佬指正。

项目代码正在gitee同步更新中,项目地址:https://gitee.com/airheaven/kfg-vue,学习前请大家给个star哦

技术栈

Vue3.2 + Vite + TS + Vant + Pinia + Node.js

一、起始准备

1.1、安装nvm

nvm 全英文也叫 node.js version management,是一个 nodejs 的版本管理工具,用于管理nodejs。
首先进入github链接:https://github.com/coreybutler/nvm-windows/releases 下载 nvm-setup.zip,随后安装。

安装完成后输入nvm version显示版本号就是安装成功了,我这里用的是1.1.10版本。

PS E:\MyVueWorkspace> nvm version
1.1.10

1.2、利用nvm安装node和npm

nvm install 版本号 安装指定的版本的 nodejs,我这里输入:nvm install 16.16.0 安装16.16.0版本
nvm会自动帮你安装好node和npm,显示如下信息就是成功了:

Downloading node.js version 16.16.0 (64-bit)...
Extracting node and npm...
Complete
npm v8.11.0 installed successfully.

Installation complete. If you want to use this version, type
nvm use 16.16.0

如果之前安装过,可以使用nvm ls查看已经安装过的node版本。

安装好node和npm后,需要使用nvm use 16.16.0启用该版本。

1.3、利用npm安装Vite

既然是新项目,且用的是Vue3.2,那么我们必须用上现在嘎嘎香 嘎嘎快的Vite,Vite是新一代的前端开发与构建工具,具有开箱即用、高度的可扩展性和完整的类型支持。

使用npm install -g vite安装最新版的Vite,然后使用vite -v查看版本(我的是4.1.4)

added 14 packages, and audited 16 packages in 13s

found 0 vulnerabilities
npm notice
npm notice New major version of npm available! 8.11.0 -> 9.5.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.5.1
npm notice Run npm install -g [email protected] to update!
npm notice
PS E:\MyVueWorkspace> vite -v
vite/4.1.4 win32-x64 node-v16.16.0

1.4、VsCode插件安装

在VsCode中找到扩展,需要安装的扩展插件有:

ESlint:开源的JavaScript验证工具,可以让代码更加规范:

【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第1张图片

Prettier:前端代码格式工具,可以让代码保持风格一致:

【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第2张图片
Vue Language Features(Volar):针对Vue3的vscode插件:

【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第3张图片

二、项目初始化

输入npm create vite@latest使用Vite初始化项目,填写项目名称(KFG-vue),选择Vue作为框架,语言选择TypeScript,如下所示:

Need to install the following packages:
  create-vite@latest
Ok to proceed? (y) y
√ Project name: ... KFG-vue
√ Package name: ... kfg-vue
√ Select a framework: » Vue
√ Select a variant: » TypeScript

然后cd进入KFG-vue项目目录,输入:npm install安装项目所需的基本依赖:

added 46 packages, and audited 47 packages in 37s
5 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities

输入npm run dev运行项目,控制台会输出网址,如http://127.0.0.1:5173/即可以查看初始化好的项目:

> [email protected] dev
> vite
  VITE v4.1.4  ready in 261 ms
  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose

然后我们需要删除一些初始化项目中不需要的东西,删除public里的vite.svg,删除assets里面的vue.svg,删除components里面的HelloWorld.vue,清空style.css,清空App.vue里的内容(仅仅保留最基本的vue3模板):
【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第4张图片
由于vue中ts无法识别引入的vue文件,引入模块后会提示打不到module,但是编译可能成功,运行也不报错,为了我的强迫症我选择在src文件夹下新增一个env.d.ts文件解决这个问题:

// src/env.d.ts
declare module "*.vue" {
  import type { DefineComponent } from "vue";

  // eslint-disable-next-line @typescript-eslint/ban-types
  const vueComponent: DefineComponent<{}, {}, any>;

  export default vueComponent;
}

三、代码规范配置(可选)

插件拿来不一定能够完全适用,需要提供了一些额外的适用于 ts 和vue语法的规则,配置以下项:

ESlint配置:输入npx eslint --init,选择如下:

√ How would you like to use ESLint? · problems(第二个)
√ What type of modules does your project use? · esm(第一个)
√ Which framework does your project use? · vue
√ Does your project use TypeScript? · No / Yes
√ Where does your code run? · browser, node(两个都勾选)
√ What format do you want your config file to be in? · JavaScript

Prettier配置:输入npm i prettier eslint-config-prettier eslint-plugin-prettier -D
创建prettier.cjs文件,文件中输入:

// prettier.cjs
module.exports = {
  printWidth: 100,
  tabWidth: 2,
  useTabs: false, // 是否使用tab进行缩进,默认为false
  singleQuote: true, // 是否使用单引号代替双引号,默认为false
  semi: true, // 行尾是否使用分号,默认为true
  arrowParens: 'always',
  endOfLine: 'auto',
  vueIndentScriptAndStyle: true,
  htmlWhitespaceSensitivity: 'strict',
};

配置eslintrc文件,将文件.eslintrc.cjs改为:

// eslintrc.cjs

module.exports = {
  root: true, // 停止向上查找父级目录中的配置文件
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier', // eslint-config-prettier 的缩写
  ],
  parser: 'vue-eslint-parser', // 指定要使用的解析器
  // 给解析器传入一些其他的配置参数
  parserOptions: {
    ecmaVersion: 'latest', // 支持的es版本
    parser: '@typescript-eslint/parser',
    sourceType: 'module', // 模块类型,默认为script,我们设置为module
  },
  plugins: ['vue', '@typescript-eslint', 'prettier'], // eslint-plugin- 可以省略
  rules: {
    'vue/multi-word-component-names': 'off',
    '@typescript-eslint/no-var-requires': 'off',
  },
};

配置保存文件自动格式化:在项目的.vscode中新建一个setting.json文件,

【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第5张图片
文件中配置如下,使程序能够保存时自动使用eslint格式化(懒人福音),setting.json配置 如下所示:

// .vscode/setting.json
  {
  	"editor.codeActionsOnSave": {
  		"source.fixAll.eslint": true
    },
  // #每次保存的时候自动格式化
  	"editor.formatOnSave": true,
  	"editor.formatOnType": true,
  // #每次保存的时候将代码按eslint格式进行修复
  	"eslint.autoFixOnSave": true,
  	"eslint.format.enable": true,
  } 

添加lint命令(可选):package.json中进行配置,可以运行npm run lint检查代码:

// package.json

// 可以运行`npm run lint`检查代码
"lint": "eslint --ext .js,.vue,.ts src --fix"

四、项目搭建

4.1、清除默认样式

在网上找一个reset.css文件,放入到src/styles文件夹(可能需要新建)中,从而清除默认样式:

/* src/styles */
/* 清除内外边距 */
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote,
dl, dt, dd, ul, ol, li,
pre,
fieldset, lengend, button, input, textarea,
th, td {
    margin: 0;
    padding: 0;
}

/* 设置默认字体 */
body,
button, input, select, textarea { /* for ie */
    /*font: 12px/1 Tahoma, Helvetica, Arial, "宋体", sans-serif;*/
    font: 12px/1.3 "Microsoft YaHei",Tahoma, Helvetica, Arial, "\5b8b\4f53", sans-serif; /* 用 ascii 字符表示,使得在任何编码下都无问题 */
    color: #333;
}


h1 { font-size: 18px; /* 18px / 12px = 1.5 */ }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
h4, h5, h6 { font-size: 100%; }

address, cite, dfn, em, var, i{ font-style: normal; } /* 将斜体扶正 */
b, strong{ font-weight: normal; } /* 将粗体扶细 */
code, kbd, pre, samp, tt { font-family: "Courier New", Courier, monospace; } /* 统一等宽字体 */
small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */

/* 重置列表元素 */
ul, ol { list-style: none; }

/* 重置文本格式元素 */
a { text-decoration: none; color: #666;}


/* 重置表单元素 */
legend { color: #000; } /* for ie6 */
fieldset, img { border: none; }
button, input, select, textarea {
    font-size: 100%; /* 使得表单元素在 ie 下能继承字体大小 */
}

/* 重置表格元素 */
table {
    border-collapse: collapse;
    border-spacing: 0;
}

/* 重置 hr */
hr {
    border: none;
    height: 1px;
}
.clearFix::after{
	content:"";
	display: block;
	clear:both;
}
/* 让非ie浏览器默认也显示垂直滚动条,防止因滚动条引起的闪烁 */
html { overflow-y: scroll; }

a:link:hover{
    color : rgb(79, 76, 212) !important;
    text-decoration: underline;
}

/* 清除浮动 */
.clearfix::after {
    display: block;
    height: 0;
    content: "";
    clear: both;
    visibility: hidden;
}

在index.html中引入:

<link rel="stylesheet" href="./styles/reset.css">

4.2、路径别名配置

路径别名配置:在vite.config.ts, 加入以下内容(resolve的alias),设置别名@:

// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// 设置路径
import { resolve } from "path";

// Vant声明和按需引入
import Component from "unplugin-vue-components/vite";
import { VantResolver } from "unplugin-vue-components/resolvers";

// vw方案,将px转为vw
import postcsspxtoviewport from "postcss-px-to-viewport";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    Component({
      resolvers: [VantResolver()],
    }),
  ],
  css: {
    postcss: {
      plugins: [
        postcsspxtoviewport({
          unitToConvert: "px", // 要转化的单位
          viewportWidth: 750, // UI设计稿的宽度,一般写 320

          // 下面的不常用,上面的常用
          unitPrecision: 6, // 转换后的精度,即小数点位数
          propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
          viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
          fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
          selectorBlackList: ["ignore-"], // 指定不转换为视窗单位的类名,
          minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
          mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
          replace: true, // 是否转换后直接更换属性值
          landscape: false, // 是否处理横屏情况
        }),
      ],
    },
  },
  resolve: {
    alias: {
      "@": resolve(__dirname, "./src"),
      "*": resolve(""),
    },
  },
});

然后在tsconfig.json,加入以下内容(baseUrl和paths):

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true,
    "noEmit": true,
    "baseUrl": ".",
    // 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

4.3、项目搭建

1️⃣ Less: CSS预处理器

我选用了less作为CSS预处理器,需要安装相应的预处理器依赖:npm i less -D
如果需要规定全局样式的话,可以配置一下在src/asserts/less文件夹下新建一个global.less,然后在vite.config.ts的css中写入:

// vite.config.ts
css: {
preprocessorOptions: {
      less: {
        modifyVars: {
          hack: `true; @import (reference) "${resolve(
            "src/assets/less/global.less"
          )}";`,
        },
        javascriptEnabled: true,
      },
    },
    //....
  }

2️⃣ Vant: 轻量可定制的移动端组件库

选用移动端最常用的Vant作为组件库,输入npm i vant 安装Vant。

然后安装按需引入插件,输入npm i unplugin-vue-components -D安装插件,它可以自动引入组件,并按需引入组件的样式。相比于常规用法,这种方式可以按需引入组件的 CSS 样式,从而减少一部分代码体积,但使用起来会变得繁琐一些。

配置Vant的按需引入,在 vite.config.js 文件中配置插件:

// vite.config.ts
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';

export default {
  plugins: [
    vue(),
    Components({
      resolvers: [VantResolver()],
    }),
  ],
};

完成以上两步,就可以直接在模板中使用 Vant 组件了,unplugin-vue-components 会解析模板并自动注册对应的组件。
可以试试在App.vue中写入:

<script setup lang="ts">script>

<template>
  <van-button type="primary">Hello Worldvan-button>
template>

<style scoped>style>

显示如下则成功引入Vant并自动注册了组件:
【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第6张图片

3️⃣ vw/vh方案:移动端适配

移动端适配通常使用的有rem方案和vw/vh方案,我选用的是vw方案,首先输入npm install postcss-px-to-viewport -D安装插件,将 px 转化成 vw,安装完成后,修改 vite.config.ts声明插件(由于 vite 中已经内联了 postcss,所以无需创建 postcss.config.js文件来声明插件):

// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// 设置路径
import { resolve } from "path";
// Vant声明和按需引入
import Component from "unplugin-vue-components/vite";
import { VantResolver } from "unplugin-vue-components/resolvers";
// vw方案,将px转为vw
import postcsspxtoviewport from "postcss-px-to-viewport";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    Component({
      resolvers: [VantResolver()],
    }),
  ],
  css: {
    postcss: {
      plugins: [
        postcsspxtoviewport({
          unitToConvert: "px", // 要转化的单位
          viewportWidth: 750, // UI设计稿的宽度,一般写 320

          // 下面的不常用,上面的常用
          unitPrecision: 6, // 转换后的精度,即小数点位数
          propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
          viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
          fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
          selectorBlackList: ["ignore-"], // 指定不转换为视窗单位的类名,
          minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
          mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
          replace: true, // 是否转换后直接更换属性值
          landscape: false, // 是否处理横屏情况
        }),
      ],
    },
  },
  resolve: {
    alias: {
      "@": resolve(__dirname, "./src"),
      "*": resolve(""),
    },
  },
});


然后运行项目,即可发现支持了移动端适配,且自适应的调整高度,成功配置
【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第7张图片

4️⃣ Vue Router:路由配置

部分参考自:https://blog.csdn.net/jason_renyu/article/details/123261823

Vue Router是Vue.js 的官方路由,非常方便好用,在控制台输入:npm i vue-router@4安装Vue Router,新建router文件夹,新建router/index.ts,配置如下:

/**
 * createRouter 这个为创建路由的方法
 * createWebHashHistory 这个就是vue2中路由的模式,
 *                      这里的是hash模式,这个还可以是createWebHistory等
 * RouteRecordRaw 这个为要添加的路由记录,也可以说是routes的ts类型
 */
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
// 路由记录,这个跟vue2中用法一致,就不做过多解释了
const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "todolist",
    component: () => import("@/components/TodoList.vue"),
    alias: "/todolist",
    meta: {
      title: "todolist页面",
    },
  },
  {
    path: "/father",
    name: "father",
    component: () => import("@/components/Father.vue"),
    meta: {
      title: "father页面",
    },
  },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});
export default router;

测试一下,在compnents文件下下新建Father.vue和TodoList.vue,内容如下:
Father.vue:


<template>
  <div>
    <h2>这是Father组件h2>
    <h3>路由传入的参数为:{{ route.query.msg }}h3>
  div>
template>

<script setup lang="ts">
import { ref } from "vue";
// 路由接收参数
import { useRoute } from "vue-router";
const route = useRoute();
// 接收路由传入的参数
let routeMsg = ref("");
if (route.query.msg) {
  routeMsg.value = route.query.msg as string;
}
script>

<style scoped>style>

TodoList.vue:


<template>
  <div>
    <h2>这是TodoList组件h2>
  div>
template>

<script setup lang="ts">script>

<style scoped>style>

在App.vue中测试调用:


<script setup lang="ts">
// useRouter的使用
import { useRouter } from "vue-router";
const router = useRouter();

const jumpFather = () => {
  // 编程式跳转和传参
  router.push({
    path: "/father",
    query: {
      msg: "hello Vue-Router",
    },
  });
};
script>

<template>
  <div>
    <router-link to="/">todolistrouter-link>
    |
    <router-link to="/father">fatherrouter-link>
  div>
  <div>
    <van-button type="primary" @click="jumpFather">跳转到fathervan-button>
  div>
  <router-view>router-view>
template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
style>

然后输入npm run dev测试看看,显示如下,todolist和father点击后有所变化,配置成功啦!
【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第8张图片

5️⃣ Pinia:状态管理器

部分参考自:https://blog.csdn.net/qq1195566313/category_11672479.html

Pinia 是 Vue 的存储库,允许跨组件/页面共享状态。同样我们输入npm i pinia安装Pinia,在项目src下创建store文件夹,以后项目中所有的状态管理部分文件都将放到store文件夹下。然后新建index.ts创建store

// src/store/index.ts
import { createPinia } from 'pinia';

const pinia = createPinia();

export default pinia;

挂载store,打开main.ts,以跟router差不多的方式,挂载进去:

import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
// 挂载router
import router from "./router/index";
// 挂载store
import store from "@/store";

const app = createApp(App);
app.use(router);
app.use(store);
app.mount("#app");

同样测试一下,在store文件夹下新建types文件夹,types文件夹为状态模块类型管理文件夹,创建模块类型文件home.ts,写入代码如下:

export type storeHome = {
    count: number,
    status: boolean
}

在store文件夹下新建modules文件夹,modules文件夹为状态模块管理文件夹,创建模块文件home.ts,写入代码如下:

import { defineStore } from "pinia";
import { storeHome } from "../types/home";

export const useHomeStore = defineStore("index", {
  state: (): storeHome => {
    return {
      count: 0,
      status: false,
    };
  },
  getters: {
    curCount(): number {
      return this.count;
    },
    curStatus(): boolean {
      return this.status;
    },
  },
  actions: {
    updatecount(val: number) {
      this.count = val;
    },
    changeStatus(val: boolean) {
      this.status = val;
    },
  },
});

然后在TodoList.vue文件中测试store是否成功:

<template>
  <div>
    <h2>这是TodoList组件h2>
  div>
  <div class="main-box">
    <h1>状态管理测试界面h1>
    <h1>状态count:{{ homeStore.count }}h1>
    
    <van-button type="success" @click="changeCount">计数van-button>
    <van-button type="danger" @click="randomCount">随机van-button>
  div>
template>

<script setup lang="ts">
import { useHomeStore } from "@/store/modules/home";
const homeStore = useHomeStore();

const changeCount = () => {
  homeStore.count++;
};

const randomCount = () => {
  const num: number = Math.random() * 100;
  homeStore.updatecount(Math.floor(num));
};
script>

<style scoped>style>

输入npm run dev 进入测试,点击todolist,然后点击随机或者计数,可以看到状态count值的变化。
【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第9张图片
状态的持久化配置等内容暂时不展开,后续需要用到会说明。

至此项目的整体结构为:

【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第10张图片

6️⃣ Axios:网络请求封装

Axios 是一个基于 promise 的网络请求库,其使用简单,包尺寸小且提供了易于扩展的接口,首先输入npm i axios安装Axios。
新建 src/utils/http 文件夹,新建 axios.tstypes.ts
axios.ts封装axios请求方法:

// src/utils/http/axios.ts
import axios, {
  AxiosInstance,
  AxiosError,
  AxiosRequestConfig,
  AxiosResponse,
} from "axios";
import { showToast } from "vant";
import "vant/es/toast/style";
// response interface { code, msg, success }
// 不含 data
interface Result {
  code: number;
  success: boolean;
  msg: string;
}

// request interface,包含 data
interface ResultData<T = any> extends Result {
  data?: T;
}

enum RequestEnums {
  TIMEOUT = 10000, // 请求超时 request timeout
  FAIL = 500, // 服务器异常 server error
  LOGINTIMEOUT = 401, // 登录超时 login timeout
  SUCCESS = 200, // 请求成功 request successfully
}

// axios 基础配置
const config = {
  // 默认地址,可以使用 process Node内置的,
  // 这里后续要配置到env.development里
  baseURL: "/api",
  timeout: RequestEnums.TIMEOUT as number, // 请求超时时间
  withCredentials: true, // 跨越的时候允许携带凭证
};

class Request {
  request(config: AxiosRequestConfig<any>) {
    throw new Error("Method not implemented.");
  }
  service: AxiosInstance;

  constructor(config: AxiosRequestConfig) {
    // 实例化 serice
    this.service = axios.create(config);

    /**
     * 请求拦截器
     * request -> { 请求拦截器 } -> server
     */
    this.service.interceptors.request.use(
      (config: AxiosRequestConfig): any => {
        const token = localStorage.getItem("token") ?? "";
        return {
          ...config,
          headers: {
            customToken: "customBearer " + token,
          },
        };
      },
      (error: AxiosError) => {
        // 请求报错
        Promise.reject(error);
      }
    );

    /**
     * 响应拦截器
     * response -> { 响应拦截器 } -> client
     */
    this.service.interceptors.response.use(
      (response: AxiosResponse) => {
        const { data, config } = response;
        if (data.code === RequestEnums.LOGINTIMEOUT) {
          // 登录过期,需要重定向至登录页面
          localStorage.setItem("token", "");
          location.href = "/";
        }
        if (data.code && data.code !== RequestEnums.SUCCESS) {
          showToast({ message: data });
          return Promise.reject(data);
        }
        return data;
      },
      (error: AxiosError) => {
        const { response } = error;
        if (response) {
          this.handleCode(response.status);
        }
        if (!window.navigator.onLine) {
          showToast({
            message: "网络连接失败,请检查网络",
          });
          // 可以重定向至404页面
        }
      }
    );
  }

  public handleCode = (code: number): void => {
    switch (code) {
      case 401:
        showToast({
          message: "登陆失败,请重新登录",
        });
        break;
      case 500:
        showToast({
          message: "请求异常,请联系管理员",
        });
        break;
      case 404:
        showToast({
          message: "404错误,请联系管理员",
        });
        break;
      default:
        showToast({
          message: "请求失败,请联系管理员",
        });
        break;
    }
  };

  // 通用方法封装
  get<T>(url: string, params?: object): Promise<ResultData<T>> {
    return this.service.get(url, { params });
  }

  post<T>(url: string, params?: object): Promise<ResultData<T>> {
    return this.service.post(url, params);
  }
  put<T>(url: string, params?: object): Promise<ResultData<T>> {
    return this.service.put(url, params);
  }
  delete<T>(url: string, params?: object): Promise<ResultData<T>> {
    return this.service.delete(url, { params });
  }
}

export default new Request(config);

types.ts和后端约定好接口返回的数据结构,如:

// src/utils/http/types.ts		

// 和后端约定好接口返回的数据结构
export interface Response<T = any> {
  code: number | string;
  message: string;
  result: T;
}

封装axios的方式多种多样,网上有很多种选择,本项目Axios封装主要参考自:https://blog.csdn.net/weixin_56650035/article/details/127467646
【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第11张图片
测试使用,src目录下新增 api/user/index.ts,内容如下:

// src/api/user/index.ts
import request from "@/utils/http/axios";
import { Response } from "@/utils/http/types";

export interface LoginParams {
  username: string;
  password: string;
}

export interface UserInfo {
  id: number;
  username: string;
  mobile: number;
  email: string;
}

export default {
  async login(params: LoginParams) {
    return await request.post<Response<UserInfo>>("/user/login", params);
  },
};

最后,在某一个vue文件中调用api试试:

// 测试axios的封装
import Api, { LoginParams } from "@/api/user";
const loginInfo: LoginParams = {
  username: "name",
  password: "string",
};
const login = async () => {
  const result = await Api.login(loginInfo);
  // do something
  console.log(result);
};

五、git commit规范辅助和git版本管理

5.1、设置commit规范辅助

这里暂不使用git Husky和eslint,需要的同学可以配置

使用到Commitizen帮助编写规范的commit message ,首先需要输入npm install commitizen -D安装Commitizen,然后输入npm i -D cz-customizable安装cz-customizable自定义的 Commitizen 插件,
配置 根目录创建 .cz-config.js,内容如下:

module.exports = {
    types: [
      {
        value: ':sparkles: feat',
        name: '✨ feat:     新功能'
      },
      {
        value: ':bug: fix',
        name: ' fix:      修复bug'
      },
      {
        value: ':tada: init',
        name: ' init:     初始化'
      },
      {
        value: ':pencil2: docs',
        name: '✏️  docs:     文档变更'
      },
      {
        value: ':lipstick: style',
        name: ' style:    代码的样式美化'
      },
      {
        value: ':recycle: refactor',
        name: '♻️  refactor: 重构'
      },
      {
        value: ':zap: perf',
        name: '⚡️ perf:     性能优化'
      },
      {
        value: ':white_check_mark: test',
        name: '✅ test:     测试'
      },
      {
        value: ':rewind: revert',
        name: '⏪️ revert:   回退'
      },
      {
        value: ':package: build',
        name: '️ build:    打包'
      },
      {
        value: ':rocket: chore',
        name: ' chore:    构建/工程依赖/工具'
      },
      {
        value: ':construction_worker: ci',
        name: ' ci:       CI related changes'
      }
    ],
    messages: {
      type: '请选择提交类型(必填)',
      customScope: '请输入文件修改范围(可选)',
      subject: '请简要描述提交(必填)',
      body: '请输入详细描述(可选)',
      breaking: '列出任何BREAKING CHANGES(可选)',
      footer: '请输入要关闭的issue(可选)',
      confirmCommit: '确定提交此说明吗?'
    },
    allowCustomScopes: true,
    allowBreakingChanges: [':sparkles: feat', ':bug: fix'],
    subjectLimit: 72
  }

最后要把package.json的脚本中的"type": "module"改为"type": "commonjs"
在scrpits中添加一行:"commit": "git add . && cz-customizable",如下

"scripts" : {
  ...
  "commit": "git add . && cz-customizable"
}

以后就使用 npm run commit 代替 git commit

5.2、git版本管理

首先在自己的github或者gitee上新建一个空白项目,我命名为KFG-vue,然后复制刚刚创建好的项目地址如:https://gitee.com/airheaven/kfg-vue.git,然后:

git init   # 把项目初始化,相当于在项目的跟目录生成一个 .git 目录
git add .    # 把项目的所有文件加入暂存区

随后使用npm run commit 进行commit,可以看到规范配置成功,选择辅助信息:
【Vue H5项目实战】从0到1的自助点餐系统—— 搭建脚手架(Vue3.2 + Vite + TS + Vant + Pinia + Node.js)_第12张图片

链接远程仓库:git remote add origin https://gitee.com/airheaven/kfg-vue.git(这里输入你刚刚创建好的仓库),然后git push origin master将项目推送到master分支

5.3、git日常使用

npm run commit:对代码进行commit
git push origin master:将项目推送到master分支

资源下载与学习

本部分的代码已上传至CSDN:https://download.csdn.net/download/air__Heaven/87530349

项目代码正在gitee同步更新中,请大家给个star,项目地址:https://gitee.com/airheaven/kfg-vue

支持我:点赞+收藏⭐️+留言

你可能感兴趣的:(Vue,TypeScript项目实战,vue.js,前端,typescript,vant,vite,Pinia)