Appearance
创建一个项目
初始化项目
使用脚手架工具初始化项目,vue2 以及配套的 vue cli 均已不再维护。
vue 官网都推荐使用自家新品 vite。
shell
npm create vite根据提示,选择合适的配置项,即可创建一个项目,看一下 vue 项目的主流选项:vue3+ts。
使用 less
因为 vite 并没有提供默认的内置支持,所以我们需要自己安装
shell
npm install -D less less-loader要求项目中,书写样式必须 scope,防止样式污染
html
<!-- xxx.vue -->
<style scoped lang="less">
.logo {
color: red;
&:hover {
color: blue;
}
}
</style>使用短链接
当路径较为复杂的时候,导入起来就比较麻烦,可以通过一些配置来设置短路径
开启功能支持:在 vite 配置文件中增加如下配置
js
// vite.config.ts
export default defineConfig({
// ...
resolve: {
alias: {
"@": "/src",
},
},
});这样仅仅是功能可以使用了,
但若是 ts 项目,还需要在 ts 配置文件中做一些配置以使得支持 ts(如 ts 报错 智能路径提示)。
js
// tsconfig.json
{
"compilerOptions": {
/* Sort path */
"baseUrl": ".",
"paths": {"@/*": ["src/*"]}
}
}
提示
在新的 vite 脚手架创建的项目中,我发现项目中 ts 相关配置被分别提取到了 tsconfig.app.json和 tsconfig.node.json中,这可能是为了方便区分吧。建议以上代码放到tsconfig.app.json
添加路由
先安装路由插件 npm install vue-router,再在项目根路径下创建一个路由文件
js
// src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";
export default createRouter({
history: createWebHistory(),
routes: [
{
path: "/", // 使用动态导入来实现懒加载
component: () => import("@/pages/home.vue"),
},
],
});在程序入口文件中引入
js
// src/main.ts
...
import router from '@/router'
...
app.use(router);
...最后,在根组件中引入路由组件标签
html
<!-- src/App.vue -->
<template>
<router-view />
</template>封装 API
在支持 openApi 的情况下,优先推荐 API 自动生成工具 Pont(或者类似工具)。
在没有的情况下,可以自己封装,目录结构为
|--src
|--api
|--init.ts
|--index.ts
|--modules
|--student.ts
|--teacher.ts其中,将 init.ts 引入项目的入口文件 main.ts 即可。
简单描述下个文件的内容
js
// init.ts
import axios from "axios";
import store from "@/store";
import { Message } from "element-ui";
const getFileNameFromUrl = (url) => {
const match = url.match(/([^/]+)\.([^/]+)?$/); // 使用正则表达式匹配文件名(不包括扩展名)
if (match && match[1]) {
let fileName = match[1];
// 转换为小写,并用正则表达式替换每个分隔符后的字符为大写(除非它是字符串的第一个字符)
fileName = fileName
.toLowerCase() // 先转换为小写
.replace(/[-_\s]+(.)?/g, (match, p1) => (p1 ? p1.toUpperCase() : ""))
.replace(/^./, (str) => str.toLowerCase()); // 转换为小驼峰
return fileName;
}
return null; // 如果没有匹配到文件名,则返回null
};
// ---===全局默认axios配置===---
const whitePath = ["/login", "/sms-send"]; // 白名单
axios.defaults.baseURL = process.env.VUE_APP_PROXY;
axios.defaults.timeout = 10000;
axios.interceptors.request.use(
(config) => {
const { token } = store.state.app.loginInfo?.token || {};
if (token) {
config.headers["Authorization"] = token;
} else {
const requstUrl = config.url.replace("/api", "");
if (!whitePath.includes(requstUrl)) {
Message({
message: "登录失效,请重新登录!",
type: "error",
});
location.href = "#/login";
}
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
const res = response.data;
if (res.code !== 200) {
if (res.code == 100004) {
Message({
message: res.msg || "登录失效,请重新登录!",
type: "error",
});
location.href = "#/login";
return Promise.reject(new Error(res.msg || "登录失效,请重新登录!"));
} else {
Message({
message: res.msg || "接口错误,请重试!",
type: "error",
});
return Promise.reject(new Error(res.msg || "接口错误,请重试!"));
}
}
return res.data;
},
(error) => {
Message({
message: error.message,
type: "error",
duration: 5 * 1000,
});
return Promise.reject(error);
}
);
// ---===将api注入全局,只需将api定义放在modules中即可===---
// 参数1:其目录路径相对于此配置文件的位置;参数2:是否搜索其子目录;参数3:匹配基础组件文件名的正则表达式
const requireComponent = require.context("./modules", false, /[\w-]+\.js$/);
// 使用 `requireComponent.keys()` 获取匹配到的文件名数组
const api = {};
requireComponent.keys().forEach(async (filePath) => {
const fileName = getFileNameFromUrl(filePath);
api[fileName] = requireComponent(filePath);
});
window.api = api;js
// index.js
export * as student from "./modules/student";
export * as teacher from "./modules/teacher";js
// module/teacher.js
import axios from "axios";
export const queryTeachers = (params) => {
return axios.get("/teacher/list", { params });
};
export const queryTeacher = (params) => {
return axios.get("/teacher", { params });
};
export const inserTeacher = (params) => {
return axios.post("/teacher", { params });
};
export const deleteTeacher = (params) => {
return axios.delete("/teacher", { params });
};引入 Ui 库
虽然 antd 的 star 远比 element 多,但是在 vue 版本上,element 却比 antdv 多。
况且 antdv 是有社区维护的,而非蚂蚁团队官方出品。
综上选择 element ui(即便它被阿里收购了)!
现先下载npm install element-plus,后在入口文件中引入
js
// main.ts
...
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
...
app.use(ElementPlus)最后你就可以在组件内愉快的使用 element ui 啦
html
<template>
<el-button>按钮</el-button>
</template>引入状态管理
抛弃过时的 vuex,拥抱更好用的 pinia!
简单易用
Pinia 的 Api 设计非常接近 Vuex5 的 提案,管理数据简单,提供数据和修改数据的逻辑即可,不像之前的 Vuex 需要记忆太多。
先安装 npm install pinia,之后创建一个 pinia 实例 (根 store) 并将其传递给应用:
js
// main.ts
...
import { createPinia } from 'pinia'
...
app.use(createPinia())再创建一个自己的 store
js
import { defineStore } from "pinia";
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
export const useAlertsStore = defineStore("app", {
// 第一个参数是你的应用中 Store 的唯一 ID。
state: () => {
// 状态
return {
count: 0,
};
},
actions: {
// 修改状态的action
increment() {
this.count++;
},
},
});最后,在组件中使用即可
html
<script setup>
import { useAppStore } from "@/store";
const store = useAppStore();
const test = () => {
store.increment();
};
</script>
<template>
<div>{{ store.count }}</div>
<button @click="test()">自增</button>
</template>
另外,它还允许在组件之外使用,允许使用三方插件扩展自身能力 如持久化存储插件等等。
定义组件名称
<script setup> 语法糖里不支持声明 name 属性,但是又不想写两个<script>标签,我们可以采用一款插件 npm install -D vite-plugin-vue-setup-extend,然后在 vite 中配置好此插件。
js
// vite.config.ts
...
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [...,vueSetupExtend()],
...
})之后你就可以这样写了
html
<script setup lang="ts" name="My App">
...
</script>打开vue devtool,也能看到效果
自动导入
在开发 vue 项目的过程中,像 ref、react 等常用的 api 总是频繁导入,有点麻烦。 发现 github 上有一个不错的开源工具 unplugin-auto-import,可以借助它 让所需自动导入。
安装 npm install -D unplugin-auto-import 完成后,在 vite 配置文件中添加即可。
js
// vite.config.js
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
...
AutoImport({ imports: ['vue', 'vue-router'] }),
],
...
})之后,你变可以这么用了
html
<!-- xx.vue -->
<script setup>
const str = ref("你好,世界");
</script>
<template> {{ str }} </template>ts 支持
但是如果您的项目是 ts,那么还需要配置一下,否则 ts 可能会提示没有显示导入的错误。
我们可以在 ts 的配置文件中导入相关的类型声明文件 auto-imports.d.ts (此文件在配置好插件后会启动项目 会自动生成)。
确保正确配置完成后 重启下 VScode。
js
// tsconfig.app.json
"include": [
...
"auto-import.d.ts"
]eslisnt 支持
如果你还用了 eslint,也需要载额外为期配置一下,否则它也会提示了没有显示导入的错误。
在 vite 使用 auto import 插件的时候,开启 eslint 的支持 AutoImport({eslintrc: { enabled: true }}),之后你再次运行项目,会发现项目中自动生成了 .eslintrc-auto-import.json!
我们需要将其引入到 eslint 配置文件中,因为我们使用的是新版 v9 的 eslint
js
// eslint.config.js
...
import require from './node-helper/require.js'
import absoluteFilePath from "./node-helper/absolute-file-path.js";
// 获得 auto-imports 生成的eslint配置 并导入此
const autoImportsPath = absoluteFilePath('.eslintrc-auto-import.json')
const autoImports = require(autoImportsPath)
export default [
...
{languageOptions: autoImports },
];这样你再执行 npx eslint 就可以顺利的通过检测啦!
require.js 和 absolute-file-path.js 是我提取出去的两个帮助性文件,这里我贴出来
js
// require.js
import { createRequire } from "module";
const require = createRequire(import.meta.url);
export default require;js
// absolute-file-path.js
import path from "path";
import process from "node:process";
export default (...arg) => {
const rootPath = process.cwd();
const filePath = path.join(rootPath, ...arg);
return filePath;
};集成 Prettier
Prettier 前端代码格式化工具。
确保代码的缩进、括号、引号、换行等样式一致。
我们安装 npm install -D prettier ,然后在根目录创建配置文件。
js
// prettier.config.js
export default {
tabWidth: 2, // 缩进2个空格
useTabs: false, // 缩进单位是否使用tab替代空格
semi: true, // 句尾添加分号
singleQuote: true, // 使用单引号代替双引号
};至此,项目就已经支持 Prettier 了,
可以使用检查命令 npx prettier src 或者 自动修复错误命令 npx prettier --write src。
若想要编辑器也支持项目中 prettier 配置,可到商店安装 prettier 插件,然后右键格式化代码的时候就可以看到 使用prettier格式化代码的选项。
集成 Eslint
ESLint 是一个代码检查工具(默认只检查 js,不支持 ts 或 css),用来检查你的代码是否符合指定的规范。
安装 eslint npm install -D eslint ,选择合适的配置项, 即可自动生成 eslint.config.js
shell
npm create @eslint/config至此,项目就已经支持 eslint 了,
可以使用检查命令 npx eslint 或者 自动修复错误命令 npx eslint --fix。
若想要编辑器也支持项目中 eslint 配置,可到商店安装 eslint 插件,后重启编辑器就可以看到效果
注意
上诉演示提示为项目中 eslint 禁用 var 关键字,需要在 eslint 配置文件中增加此规则
js
// eslint.config.js
...
export default [
...
{rules:{'no-var': 'error'}}
];最后,贴出一个常用的自定义的规则
js
// eslint.config.js
export default [
// ...
{
rules: {
"no-var": "error", // 禁止使用var
// ---vue-eslint参考:https://eslint.vuejs.org/rules---
"vue/multi-word-component-names": "off",
"vue/attribute-hyphenation": ["error", "always"], // vue模板属性中划线
"vue/component-name-in-template-casing": ["error", "kebab-case"], // vue模板使用组件名规范
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "always",
component: "always",
},
},
], // 强制自闭合标签
// ---tslint 规则集参考:https://typescript-eslint.io/rules---
"@typescript-eslint/no-explicit-any": "warn", // 允许使用any类型,但是警告(默认即使警告,可以不用声明)
"@typescript-eslint/no-var-requires": "warn", // 允许使用require,但是警告(默认不允许)
"@typescript-eslint/no-empty-function": "off", // 允许空方法,因为可能是做单例限制构造or被注解修饰的空方法(默认为error)
"@typescript-eslint/ban-ts-comment": "off", // 允许@ts- 指令的使用,如@ts-nocheck(默认不允许使用)
"@typescript-eslint/no-non-null-assertion": "off", // 允许 非空断言操作符(默认为不允许)
// '@typescript-eslint/explicit-module-boundary-types': 'off' // 函数必须定义参数类型和返回类型,默认即是关闭校验
"no-unused-vars": "warn",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-unused-expressions": "off",
},
},
];Eslint 与 Prettier
一般情况下 我们不会单独运行 prettier,而是将 prettier 集成到 eslint 中,作为一项 rule 进行提示与修复。
我们通过两个包来做到这个能力
- eslint-config-prettier:一个 ESLint 配置规则的包,它将禁用与 Prettier 冲突的 ESLint 规则。
- eslint-plugin-prettier:一个 ESLint 插件,它将 Prettier 作为规则在 ESLint 内部运行。
npm install -D eslint-config-prettier eslint-plugin-prettier安装完成后,在 eslint 配置文件中,使用 eslint-plugin-prettier 插件即可(此插件会自动调用 eslint-config-prettier)
js
// eslint.config.js
...
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
export default [
...
eslintPluginPrettierRecommended,
];最后就可以看到效果了
注意
上诉提示为项目中 prettier 要求使用单引号,需要在 prettier 配置文件中增加此规则
js
// prettier.config.js
export default {
...
singleQuote: true, // 使用单引号代替双引号
};最后
在你的项目 pakage.json 中新增一个脚本
{
...
"scripts": {
...
"eslint": "npx eslint src --fix"
}
}这样,以后你每次提交代码前,执行一下这个命令,你犯的错误 它都帮你解决了。
集成 Husky
这是一个让开发人员头痛的插件,它会通过一些手段禁止不符合要求的 commit 被提交。
遇到被限制了提交了,先不要骂娘,出于团队规范的考虑,你应该先根据提示解决自己的问题!
Husky 在提交或推送时,自动检查提交信息、检查代码 和 运行测试。
首先安装它 npm install -D husky ,之后使用初始化命令 npx husky init,这将会 生成 .husky/pre-commit 脚本,并更新 package.json 中的 prepare 脚本。
最后,你可以在.husky/pre-commit 中编写 shell 脚本(如果你更喜欢 js 脚本,可以看这里),脚本将会在你执行 git commit 命令的时候被触发。
比如你可以如下这么写,浙江会在你提交之前检查代码,如果报错 则不予通过
js
// .husky/pre-commit
npx eslint src锁定 Node 版本
为了确保团队协作项目的稳定性和一致性,我们需要采取一些措施来保证项目中的 Node 版本 和 包管理工具一致。
在项目的 package.json 文件中,可以使用 engines 字段来指定所需的 Node 版本 和 包管理器。在该字段中,我们可以定义一个范围或者具体的版本号来限制 Node 的版本。
js
// package.json
{
...
"engines": {
"yarn": ">= 1.0.0",
"node": "=18.0.0"
},
}这样当项目成员运行 npm install 时,npm 会自动检查 Node 版本是否满足要求,并给出警告信息。
npm 下 engines 只是建议,默认不开启严格版本校验,只会给出提示,需要手动开启严格模式。 而 yarn 则默认开启严格模式。
yml
# .npmrc
engine-strict = true不过实测,并不好用(如 无论怎么配置 yarn 都不支持 node 版本的检测)。
优选方案
除此这个方式,我们还可以利用 preinstall 在安装依赖之前做一些限制,比如使用插件 use-yarn、please-use-yarn、only-allow。 它们的使用方式都一样 比如,在 package.json 文件的 scripts 中添加 preinstall:
js
// package.json
{
"scripts": {
"preinstall": "npx please-use-yarn"
}
}其原理简单,npx please-use-yarn 会执行 please-use-yarn/bin 的脚本文件做检测,知道此原理就好办了。
优化方案
js
// package.json
{
"scripts": {
"preinstall": "node ./node-helper/preinstall.js"
}
}js
// ./node-helper/preinstall.js
import chalk from "chalk";
import semver from "semver";
import process from "node:process";
const version = "22.0.0";
const pkgManager = "yarn";
const pkgManagerExecpath = process.env.npm_execpath || "";
const allowPkgManager = pkgManagerExecpath.indexOf(pkgManager) > -1;
if (!allowPkgManager) {
console.log(chalk.underline.bold.red("包管理器不符合要求"));
console.log(chalk.red("要求为:" + pkgManager));
process.exit(1);
}
if (!semver.satisfies(process.version, version)) {
console.log(chalk.underline.bold.red("Node版本不符合项目要求"));
console.log(chalk.red("要求版本:v" + version));
console.log(chalk.red("您的版本:" + process.version));
console.log(chalk.magentaBright("推荐使用n、nvm等管理node"));
process.exit(1);
}最终方案
但是考虑到在 preinstall 阶段的时候,你可能无法使用 chalk、 semver 三方包,随意我们改为如下
js
// version-compare.js
// 版本比较函数
export default (v1, v2, operator) => {
// 将版本号转换为数组,按.分割
v1 = v1.split(".");
v2 = v2.split(".");
const maxLen = Math.max(v1.length, v2.length);
// 补充短的版本号数组,使其长度等于最长的版本号
for (let i = 0; i < maxLen; i++) {
if (!v1[i]) {
v1[i] = "0";
}
if (!v2[i]) {
v2[i] = "0";
}
}
// 转换成数字数组进行比较
for (let i = 0; i < maxLen; i++) {
const num1 = parseInt(v1[i], 10);
const num2 = parseInt(v2[i], 10);
if (num1 > num2) {
return operator === ">" || operator === ">=" ? true : false;
} else if (num1 < num2) {
return operator === "<" || operator === "<=" ? true : false;
}
}
return true; // 版本号相等
};
// 使用示例
// console.log(versionCompare('1.2.3', '1.2.4', '<')); // true
// console.log(versionCompare('1.2.3', '1.2.4', '>')); // false
// console.log(versionCompare('1.2.3', '1.2.3', '=')); // true
// console.log(versionCompare('1.2.3', '1.2.4', '>=')); // false
// console.log(versionCompare('1.2.4', '1.2.3', '<=')); // truejs
// preinstall.js
import process from "node:process";
import versionCompare from "./version-compare.js";
const currentNodeVersion = process.version.replace("v", "");
const version = "20.16.0";
const pkgManager = "yarn";
const pkgManagerExecpath = process.env.npm_execpath || "";
console.log(pkgManagerExecpath);
const allowPkgManager = pkgManagerExecpath.indexOf(pkgManager) > -1;
if (!allowPkgManager) {
console.error(`\x1B[1;31m${"*".repeat(40)}\x1B[0;0m`);
console.error(
`\x1B[1;31m* 包管理器不符合要求,要求为 ${pkgManager}\x1B[0;0m`
);
console.error(`\x1B[1;31m${"*".repeat(40)}\x1B[0;0m`);
console.error(``);
process.exit(1);
}
const allowNodeVersion = versionCompare(currentNodeVersion, version, ">=");
if (!allowNodeVersion) {
console.error(`\x1B[1;31m${"*".repeat(50)}\x1B[0;0m`);
console.error(
`\x1B[1;31m* Node不符合项目要求v${version}, 您的为 ${process.version}\x1B[0;0m`
);
console.error(`\x1B[1;31m${"*".repeat(50)}\x1B[0;0m`);
console.error(``);
process.exit(1);
}ts 不识别 vue 文件
增加如下代码即可
js
// vite-env.d.ts
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<_, _, any>;
export default component;
}表单插件
一般中后台都会大量用到表单,如果自己纯手撸,效率很低。 这里推荐将 vue 开源表单生成插件(目前 start 最多,且较为完善) FormCreate

表格插件
中后台,表格多用于展示数据,
比如用户列表、角色权限列表等等,也是必不可少,
这里推荐 vxetable,推荐原因:star 多、简单易用!
mock 数据
MSW 全称 Mock Service Worker 是一个用于浏览器或 Node.js 的 API 模拟库。借助它,您可以拦截传出的请求、观察它们,并使用模拟响应来响应它们。
MSW 的与众不同之处在于,它极力主张使用独立的 API 模拟层,为您的网络行为创建单一事实来源,并将其集成到您使用的任何工具中。 这带来了更具弹性的设置,并与其他库功能相结合,创造了真正无缝的 API 模拟体验。
MSW是目前相当优秀的方案,但是这需要一定的难度,这里有简单和快熟开始的的使用文档
包管理器
推荐项目包管理工具位 pnpm,即便不是 您的选择也应该按照优先级为 pnpm > yarn > npm
npm、yarn、pnpm 比较
都是 js 包管理工具,用于管理和下载 js 依赖项。它们的主要区别在于以下几个方面:
⚡️ 性能:yarn 和 pnpm 相对于 npm 来说更快。yarn 使用并行下载和缓存机制来提高性能,而 pnpm 则使用硬链接和符号链接来减少磁盘空间的使用。
⬇️ 安装依赖项的方式:npm 和 yarn 都会将依赖项安装在本地 node_modules 中,而 pnpm 会将依赖项安装在全局缓存中,并使用符号链接将其连接到项目中。