Skip to content

零、项目预览

代码仓库地址:https://github.com/Topeceen/vue3-ts

项目预览项目预览项目预览项目预览项目预览项目预览项目预览项目预览项目预览

一. 项目的介绍

1.1. 后台管理系统预览

  • 账号/密码:admin/admin123

1.2. 后台管理系统项目技术栈

  • 技术栈介绍:
    • 开发工具 :Visual Studio Code
    • 编程语言 :TypeScript 4.x + JavaScript
    • 构建工具 :Vite 3.x / Webpack5.x
    • 前端框架 :Vue 3.x + setup
    • 路由工具 :Vue Router 4.x
    • 状态管理 :Vuex 4.x / Pinia
    • UI 框架 :Element Plus
    • 可视化 :Echart 5.x
    • 工具库 :@vueuse/core + dayjs + countup.js 等等
    • CSS 预编译 :Sass / Less
    • HTTP 工具 : Axios
    • Git Hook 工具 :husky
    • 代码规范 :EditorConfig + Prettier + ESLint
    • 提交规范 :Commitizen + Commitlint
    • 自动部署 :Centos + Jenkins + Nginx

1.3. 创建项目-npm init vue@latest

  • 方式一:Vue CLI
    • 基于 webpack 工具;
    • 命令:vue create
  • 方式二:create-vue
    • 基于 vite 工具;
    • 命令:npm init vue@latest

1.5. tsconfig 文件的作用和解析

  • extends 默认继承配置
js
{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["src/**/*", "src/**/*.vue", "env.d.ts"],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },

  "references": [
    {
      "path": "./tsconfig.config.json"
    }
  ]
}
  • 配置@表示 src
js
 "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },

1.6 tsconfig 文件的配置

js
"compilerOptions": {
    //开启合并
    "composite": true,
    //ts类型的隐式any
    "noImplicitAny": false
  }

1.7 env.d.ts 添加类型声明

js
declare module '*.vue' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent
  export default component
}

二. 项目代码规范

2.1. editorconfig 文件

js
root = true

[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf

2.2. prettier 配置

js
{
  "$schema": "https://json.schemastore.org/prettierrc",
  "semi": false,
  "singleQuote": true,
  "printWidth": 100,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all"
}

2.4. eslint 配置

js
import pluginVue from "eslint-plugin-vue";
import vueTsEslintConfig from "@vue/eslint-config-typescript";
import skipFormatting from "@vue/eslint-config-prettier/skip-formatting";

export default [
  {
    name: "app/files-to-lint",
    files: ["**/*.{ts,mts,tsx,vue}"],
  },

  {
    name: "app/files-to-ignore",
    ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
  },

  ...pluginVue.configs["flat/essential"],
  ...vueTsEslintConfig(),
  skipFormatting,
];

2.5. .eslintrc.cjst 配置

  • 出现 ts 的警告的时候,想消除警告
js
  rules: {
    'vue/multi-word-component-names': 'off'
  },

三. 项目内容搭建

3.1. 目录结构的创建

3.2. css 样式的重置

js
//下载样式重置
pnpm install normalize.css
pnpm i less
  • 声明 common.less
  • 声明 reset.less
less
/* reset.css样式重置文件 */
/* margin/padding重置 */
body,
h1,
h2,
h3,
ul,
ol,
li,
p,
dl,
dt,
dd {
  padding: 0;
  margin: 0;
}

/* a元素重置 */
a {
  text-decoration: none;
  color: #333;
}

/* img的vertical-align重置 */
img {
  vertical-align: top;
}

/* ul, ol, li重置 */
ul,
ol,
li {
  list-style: none;
}

/* 对斜体元素重置 */
i,
em {
  font-style: normal;
}
  • 声明 index.less
index.less
@import "./reset.less"
@import "./common.less";
  • 导入 import

3.3. vue-router 路由

  • pnpm i vue-router
js
import { createRouter, createWebHashHistory } from "vue-router";

const router = createRouter({
  history: createWebHashHistory(),
  // 映射关系: path => component
  routes: [
    {
      //路由重定向
      path: "/",
      redirect: "/main",
    },
    {
      path: "/login",
      //路由懒加载
      component: () => import("../views/login/Login.vue"),
    },
    {
      path: "/main",
      component: () => import("../views/main/Main.vue"),
    },
    {
      path: "/:pathMatch(.*)",
      component: () => import("../views/not-found/NotFound.vue"),
    },
  ],
});
ts
app.use(router);

3.4. pinia 状态管理

  • 初始化 pinia
ts
import { createPinia } from "pinia";

const pinia = createPinia();

export default pinia;
  • 挂载 pinia
js
app.use(pinia);
  • 创建 store(例子)
js
import { defineStore } from "pinia";

const useCounterStore = defineStore("counter", {
  state: () => ({
    counter: 100,
  }),
  getters: {
    doubleCounter(state) {
      return state.counter * 2;
    },
  },
  actions: {
    changeCounterAction(newCounter: number) {
      this.counter = newCounter;
    },
  },
});

export default useCounterStore;
  • 使用 store(例子)
ts
<template>
  <div class="main">
    <h2>main: {{ counterStore.counter }}-{{ counterStore.doubleCounter }}</h2>
    <button @click="changeCounter">修改counter</button>
  </div>
</template>

<script setup lang="ts">
import useCounterStore from '@/store/counter'

const counterStore = useCounterStore()

function changeCounter() {
  counterStore.changeCounterAction(999)
}
</script>

<style lang="less" scoped>
.main {
  color: red;
}
</style>

3.5. axios 网络请求

  • 基础网络请求(先安装)
ts
// 1.区分开发环境和生产环境
// export const BASE_URL = ''
// export const BASE_URL = ''

// 2.代码逻辑判断, 判断当前环境
// vite默认提供的环境变量
// console.log(import.meta.env.MODE)
console.log(import.meta.env.DEV); // 是否开发环境
console.log(import.meta.env.PROD); // 是否生产环境
console.log(import.meta.env.SSR); // 是否是服务器端渲染(server side render)

let BASE_URL = "";
if (import.meta.env.PROD) {
  BASE_URL = "";
} else {
  BASE_URL = "";
}

console.log(BASE_URL);

// 3.通过创建.env文件直接创建变量
console.log(import.meta.env.VITE_URL);

export const TIME_OUT = 10000;
export { BASE_URL };
js
import axios from "axios";
import type { AxiosInstance } from "axios";
import type { HYRequestConfig } from "./type";

// 拦截器: 蒙版Loading/token/修改配置

/**
 * 两个难点:
 *  1.拦截器进行精细控制
 *    > 全局拦截器
 *    > 实例拦截器
 *    > 单次请求拦截器
 *
 *  2.响应结果的类型处理(泛型)
 */

class HYRequest {
  instance: AxiosInstance;

  // request实例 => axios的实例
  constructor(config: HYRequestConfig) {
    this.instance = axios.create(config);

    // 每个instance实例都添加拦截器
    this.instance.interceptors.request.use(
      (config) => {
        // loading/token
        return config;
      },
      (err) => {
        return err;
      }
    );
    this.instance.interceptors.response.use(
      (res) => {
        return res.data;
      },
      (err) => {
        return err;
      }
    );

    // 针对特定的hyRequest实例添加拦截器
    this.instance.interceptors.request.use(
      config.interceptors?.requestSuccessFn,
      config.interceptors?.requestFailureFn
    );
    this.instance.interceptors.response.use(
      config.interceptors?.responseSuccessFn,
      config.interceptors?.responseFailureFn
    );
  }

  // 封装网络请求的方法
  // T => IHomeData
  request<T = any>(config: HYRequestConfig<T>) {
    // 单次请求的成功拦截处理
    if (config.interceptors?.requestSuccessFn) {
      config = config.interceptors.requestSuccessFn(config);
    }

    // 返回Promise
    return (
      new Promise() <
      T >
      ((resolve, reject) => {
        this.instance.request < any,
          T >
            config
              .then((res) => {
                // 单词响应的成功拦截处理
                if (config.interceptors?.responseSuccessFn) {
                  res = config.interceptors.responseSuccessFn(res);
                }
                resolve(res);
              })
              .catch((err) => {
                reject(err);
              });
      })
    );
  }

  get<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "GET" });
  }
  post<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "POST" });
  }
  delete<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "DELETE" });
  }
  patch<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "PATCH" });
  }
}

export default HYRequest;
js
import type { AxiosRequestConfig, AxiosResponse } from 'axios'

// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {
  requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestFailureFn?: (err: any) => any
  responseSuccessFn?: (res: T) => T
  responseFailureFn?: (err: any) => any
}

export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: HYInterceptors<T>
}
js
import { BASE_URL, TIME_OUT } from "./config";
import HYRequest from "./request";

const hyRequest = new HYRequest({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
});

export default hyRequest;
  • 测试
js
hyRequest
  .get({
    url: "/home/multidata",
  })
  .then((res) => {
    console.log(res);
  });

3.6. 区分开发和生产环境

js
// 1.区分开发环境和生产环境
// export const BASE_URL = ''
// export const BASE_URL = ''

// 2.代码逻辑判断, 判断当前环境
// vite默认提供的环境变量
// console.log(import.meta.env.MODE)
console.log(import.meta.env.DEV); // 是否开发环境
console.log(import.meta.env.PROD); // 是否生产环境
console.log(import.meta.env.SSR); // 是否是服务器端渲染(server side render)

let BASE_URL = "";
if (import.meta.env.PROD) {
  BASE_URL = "";
} else {
  BASE_URL = "";
}

console.log(BASE_URL);

// 3.通过创建.env文件直接创建变量
console.log(import.meta.env.VITE_URL);

export const TIME_OUT = 10000;
export { BASE_URL };