零、项目预览
代码仓库地址: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 };