说明:
之前因为做一个项目从零到一搭建了Vue3的后台管理系统,写本文的时候本想采用现行版本,结果遇到了很多插件版本兼容问题。想起之前踩得那些坑,望而却步,时间、水平有限,故放弃了这个想法。
本文使用 node 14.16.1 版本,如果您的 node 版本与该版本相近,可以尝试能否兼容,也可以使用nvm安装该版本。
1. Vite创建项目
说到 Vue3 就不得不说一下 Vite,如果你受够了 webpack 启动服务40s+,热更新6s+的话,一定要来看看 Vite,真的很香!
这里,我也是用 Vite 进行项目创建,文档提供了示例,可以使用
yarn create vite my-vue-app --template vue
进行Vite+Vue项目创建,下面还提供了几种模板预设,因为要使用 ts,所以这里我使用的命令是
yarn create vite my-vue-app --template vue-ts
然后进入目录,yarn 安装依赖,yarn dev 启动服务就好了
说明:因为 Vite 一直更新版本问题,所以如果您按上述命令创建项目,与该分支上代码并不一致。《项目地址》
2. 集成JEST单元测试
2-1. 安装jest
yarn add jest@26.6.3 -D
2-2. 创建第一个测试内容
在根目录创建 tests 文件夹,再在内部创建 unit 文件夹存放单元测试文件,unit 文件夹内编写我们的第一个测试文件 index.spec.js
test('1+1=2',() => {
expect(1+1).toBe(2)
})
在 package.json 配置命令
"scripts": {
"test-unit":"jest"
}
运行 yarn test-unit 发现第一个测试用例就通过了
2-3. 添加自动提示插件
上面在编写测试用例的时候,是没有代码提示的,这导致效率很低,这里安装@types/jest实现代码提示
yarn add @types/jest@26.0.24 -D
2-4. 支持import
接下来我们在unit文件夹下编写一个测试用文件 foo.js
export default function (){
return 'this is foo'
}
修改 index.spec.js
import foo from './foo.js'
test('1+1=2',() => {
expect(1+1).toBe(2);
})
test('foo',() => {
expect(foo()).toBe('this is foo')
})
再次执行 yarn test-unit 发现报错了
会发现 jest 无法识别 import 语法,这是因为 jest 是基于 node 环境的,所以要将 import 这种语法转化为 nodejs 可以识别的语法
在根目录创建 jest.config.js 进行 jest 配置
module.exports = {
// 转换器
transform: {
// jest解析js的时候通过babel-jest解析
"^.+\\.jsx?$": "babel-jest"
}
};
安装 babel-jest
yarn add babel-jest@26.6.3 -D
因为使用了 babel,所以还需要创建在根目录 babel.config.js 配置babel
module.exports = {
presets: [
[
// 安装官方预设插件
"@babel/preset-env",
// 指定解析的目标是本机node版本
{ targets: { node: "current" } }
],
],
};
安装 @babel/preset-env
yarn add @babel/preset-env@7.14.9 -D
再次运行 yarn test-unit 发现测试通过了
2-5. 支持 .vue文件
在src/compontents下创建 Foo.vue
<template>
<div>
Foo
</div>
</template>
修改 index.spec.js
import foo from './foo.js'
import Foo from '../../src/components/Foo.vue
'test('1+1=2',() => {
expect(1+1).toBe(2);
})
test('foo',() => {
expect(foo()).toBe('this is foo')
})
test('Foo',() => {
console.log('Foo',Foo)
})
执行 yarn test-unit 发现报错无法解析vue的语法
这里需要在 jest.config.js 中新增解析 .vue 文件的规则
module.exports = {
// 转换器
transform: {
// jest解析js的时候通过babel-jest解析
"^.+\\.jsx?$": "babel-jest",
// jest解析vue的时候通过vue-jest解析
"^.+\\.vue$": "vue-jest"
}
};
安装 vue-jest
yarn add vue-jest@next -D
再次执行 yarn test-unit 发现测试失败,缺少 ts-jest 依赖
安装 ts-jest
yarn add ts-jest@26.5.6 -D
再次执行 yarn test-unit 测试通过
2-6. 安装 Vue Test Utils
Vue Test Utils 是Vue官方推荐的Vue单元测试库,提供对vue文件测试的支持
yarn add @vue/test-utils@next -D
修改 index.spec.js
import foo from './foo.js'
import {mount} from '@vue/test-utils'
import Foo from '../../src/components/Foo.vue
'test('1+1=2',() => {
expect(1+1).toBe(2);
})
test('foo',() => {
expect(foo()).toBe('this is foo')
})
test('Foo',() => {
console.log('Foo',Foo)
console.log('mount',mount(Foo))
})
执行 yarn test-unit 测试通过
2-7. 支持ts
将之前的 js 测试文件直接改成 .ts 文件再 yarn test-unit 肯定是不通过的,这里和之前一样,需要在jest.config.js 中新增转换器配置
module.exports = {
// 转换器
transform: {
// jest解析js的时候通过babel-jest解析
"^.+\\.jsx?$": "babel-jest",
// jest解析vue的时候通过vue-jest解析
"^.+\\.vue$": "vue-jest",
// jest解析ts的时候通过ts-jest解析
"^.+\\.tsx?$": "ts-jest"
}
};
ts-jest 之前已经安装过了,这里不需要重复安装
修改 babel.config.js 配置
module.exports = {
presets: [
[
// 安装官方预设插件
"@babel/preset-env",
// 指定解析的目标是本机node版本
{ targets: { node: "current" } }
],
"@babel/preset-typescript"
]
};
安装 @babel/preset-typescript
yarn add @babel/preset-typescript@7.14.5 -D
执行 yarn test-unit 测试通过 《项目地址》
3. 集成Cypress e2e测试
3-1. 安装Cypress
yarn add cypress@8.2.0 -D
package.json 配置命令 "test-e2e":"cypress open“ 并执行
第一次执行的时候会进行初始化,帮我们在根目录创建一个 cypress 文件夹,里边会有 cypress 相关的文件,初始化完成后会自动打开一个窗口
这里会有一个弹框供我们选择会在什么 CI 环境下使用 cypress,这里我们用不到,直接关闭即可
右上角可以切换运行测试环境的浏览器 相关文档
下面是所有的测试用例,点击会在浏览器中打开根据脚本进行测试
3-2. 调整目录
在我们的项目中,是希望所有测试相关的东西都放在 tests 文件夹下的,这里我们在 tests 下创建 e2e 文件夹,并将根目录 cypress 下的所有文件拷贝过来,这个时候如果我们再次执行
yarn test-e2e,会发现 cypress 每次都会检测根目录是否有 cypress 文件夹,没有会再次创建,所以我们需要在 cypress.json 中修改配置
{
"pluginsFile":"tests/e2e/plugins/index.js"
}
然后修改 plugins/index.js 中的配置 相关文档
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
return Object.assign({}, config, {
// fixtures路径
fixturesFolder: "tests/e2e/fixtures",
// 测试脚本文件夹
integrationFolder: "tests/e2e/specs",
// 从 cy.screenshot() 命令或在 cypress 运行期间测试失败后保存屏幕截图的文件夹路径
screenshotsFolder: "tests/e2e/screenshots",
// cypress 运行期间保存视频的文件夹路径
videosFolder: "tests/e2e/videos",
// 在加载测试文件之前加载的文件路径。 这个文件被编译和捆绑。 (通过 false 禁用)
supportFile: "tests/e2e/support/index.js"
});
}
需要注意的是这里将之前的 integration 文件夹重命名为 specs
执行 yarn test-e2e
出现如下弹窗
并且没有在根目录创建 cypress 文件夹
3-3. 支持 ts
首先把e2e文件夹下所有 .js 文件修改为 .ts
然后修改 e2e/plugins/index.ts 中的配置
module.exports = (on: any, config: Cypress.PluginConfig) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
return Object.assign({}, config, {
// fixtures路径
fixturesFolder: "tests/e2e/fixtures",
// 测试脚本文件夹
integrationFolder: "tests/e2e/specs",
// 从 cy.screenshot() 命令或在 cypress 运行期间测试失败后保存屏幕截图的文件夹路径
screenshotsFolder: "tests/e2e/screenshots",
// cypress 运行期间保存视频的文件夹路径
videosFolder: "tests/e2e/videos",
// 在加载测试文件之前加载的文件路径。 这个文件被编译和捆绑。 (通过 false 禁用)
supportFile: "tests/e2e/support/index.ts",
});
};
修改 cypress.json 中的配置
{
"pluginsFile":"tests/e2e/plugins//index.ts"
}
清除 tests/e2e/specs 下的测试示例文件,创建 index.spec.ts
describe("index", () => {
it("button click", () => {
cy.visit("http://localhost:3000/");
cy.get("button").click();
});
});
执行 yarn test-e2e,弹窗如下:
点击 index.spec.js,cypress 会帮我们打开配置的浏览器并执行测试脚本
可以发现已经触发了按钮点击,因为上方红色数值由0变为了1,而且左侧提示 index.spec.ts 中的button click 测试用例通过
3-4. 解决 jest 测试覆盖 e2e 问题
此时执行 yarn test-unit 会发现报错了
此时我们发现执行 jest 单元测试把 cyress 的测试文件也执行了,修改 jest.config.js 配置,指定测试脚本匹配规则
module.exports = {
// 转换器
transform: {
// jest解析js的时候通过babel-jest解析
"^.+\\.jsx?$": "babel-jest",
// jest解析vue的时候通过vue-jest解析
"^.+\\.vue$": "vue-jest",
// jest解析ts的时候通过ts-jest解析
"^.+\\.tsx?$": "ts-jest" },
// 配置测试脚本文件匹配规则
testMatch: ["**/tests/unit/?(*.)+(spec).[jt]s?(x)"],
};
再依次执行 yarn test-unit yarn test-e2e 发现都可以通过了
3-5. 整合 jest & cypress 测试
package.json 配置命令"test":"npm run test-unit && npm run test-e2e"并执行
可以在终端中看到jest测试通过,并打开了cypress窗口,那此时我们其实希望 cypress 的测试也在终端中进行。
cypress提供了另一种执行测试的命令 cypress run,我们在 package.json 中添加命令
"test-e2e-ci":"cypress run" 并执行,发现此时 cypress 测试可以在终端中进行了,修改 test 命令如下
"test":"npm run test-unit && npm run test-e2e-ci"
执行 yarn test,此时 jest 和 cypress 都可以在终端中完成了
3-6. 关闭 cypress 生成视频行为
当我们在终端中进行 cypress 测试的时候,会发现在 tests/e2e 文件夹下多了一个 video 文件夹,里边有一个 index.sepc.ts.mp4 文件,打开会发现该视频就是对应测试文件测试过程的录屏,这里我们其实是不需要的,每次生成.mp4文件会占用内存和增加测试时间,可以在 cypress.json 中进行配置关闭
{
"pluginsFile":"tests/e2e/plugins/index.ts",
"video":false
}
至此,集成 cypress e2e 测试完成。《项目地址》
4. 集成eslint
4-1. 安装eslint及相关依赖
yarn add eslint@7.20.0 eslint-plugin-vue@7.6.0 @vue/eslint-config-typescript@7.0.0 @typescript-eslint/parser@4.15.2 @typescript-eslint/eslint-plugin@4.15.2 -D
就不多说了,我们的目标,代码检查工具
Vue.js 的官方 ESLint 插件,提供了,以及 .js 文件中的 Vue 代码的支持
为在 Vue 组件中编写 ts 代码提供支持
针对 eslint 的一个 ts 解析器
@typescript-eslint/eslint-plugin
针对 ts 的 eslint plugin
接下来根目录创建 .eslintrc 文件进行 eslint 配置
{
// 指定当前目录为根目录
"root": true,
// 环境配置项
"env": {
// 是否浏览器环境
"browser": true,
// 是否node环境
"node": true,
// es2021 支持
"es2021": true
},
// 引入配置项
"extends": [
// vue3
"plugin:vue/vue3-recommended",
// eslint
"eslint:recommended",
// vue typescript
"@vue/typescript/recommended"
],
// 解析器配置
"parserOptions": {
// 要使用的 ECMAScript 语法版本
"ecmaVersion": 2021
}
}
在 package.json 中 scripts 配置 eslint 命令
"lint":"eslint --ext .ts,vue src/**"
检测 src 目录下的所有 .ts,.vue 文件
此时执行 yarn lint
会发现有一些警告和报错
这里我们再添加一个命令
"lint:fix":"eslint --ext .ts,vue src/** --fix"
fix 会帮助我们自动修复一些警告
执行 yarn lint:fix 后发现,所有警告消失,只剩下一个报错
针对图片没有配置解析器问题,我们可以在根目录配置 .eslintignore 文件,使 eslint 忽略某些文件或目录
node_modules
dist
src/assets
index.html
再次执行 yarn lint:fix 发现可以通过了
4-2. 集成 lint-staged
这个时候我们想到每次 lint 检查都是检查 src 目录下所有的 .ts,.vue 文件是没有必要的,实际上每次我们只需要检查那些进行了修改的文件,也就是git暂存区的文件即可,这要怎么做呢?
这里需要用到 lint-staged 和 yorkie,使用
yarn add lint-staged@11.1.2 yorkie@2.0.0 -D
进行安装,接下来需要在 package.json 进行一些配置
"gitHooks": {
"pre-commit": "lint-staged"},
"lint-staged": {
"*.{ts,vue}": "eslint --fix"
}
以上配置会在 commit 之前调用 lint-staged,该命令会对所有的 .ts,.vue 文件进行 eslint --fix
这里我们将 App.vue 文件中的 HelloWorld 引入注释,然后提交代码,会发现在报错如下:
说明我们配置的校验生效了。《项目地址》
. 集成Prettier
Prettier 可以帮我们美化及统一代码格式,所以这里我们也集成进来。
安装prettier及相关依赖
yarn add prettier@2.2.1 eslint-plugin-prettier@3.3.1 @vue/eslint-config-prettier@6.0.0 -D
为 prettier 在 eslint 中工作提供支持
为 eslint 代码校验规则与 prettier 代码校验规则部分冲突提供支持
.eslintrc 文件中新增配置
{
// 指定当前目录为根目录
"root": true,
// 环境配置项
"env": {
// 是否浏览器环境
"browser": true,
// 是否node环境
"node": true,
// es2021 支持
"es2021": true
},
// 引入配置项
"extends": [
// vue3
"plugin:vue/vue3-recommended",
// eslint
"eslint:recommended",
// vue typescript
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint"
],
// 解析器配置
"parserOptions": {
// 要使用的 ECMAScript 语法版本
"ecmaVersion": 2021
}
}
终端中执行 npx prettier -w -u .
-w: Edit files in-place. (Beware!) 就地编辑文件。 (谨防!)
-u: Ignore unknown files. 忽略未知文件。
. 指定路径为当前路径
会发现 prettier 帮我们格式化了一些文件,打开 App.vue 的变更
会发现 prettier 自动帮我们根据它的规则进行了一些修改,比如换行,空格,单双引号等
然后可以在根目录创建 .prettierrc 文件对 prettier 进行配置 相关文档
{
是否结尾分号
"semi": true,
是否单引号
"singleQuote": false
}
这里只是进行示例测试,你可以根据团队规范或者自己需要根据文档进行配置,此时如果把某个文件的结尾分号删除,执行npx prettier -w -u .,会发现prettier会帮我们把行尾分号加上
最后,为了方便使用,我们把 prettier 集成到 lint-staged 中,修改 package.json
"lint-staged": {
"*.{ts,vue}": "eslint --fix",
"*": "prettier -w -u"
}
至此,项目集成prettier完成。《项目地址》
. 集成commit message校验
对于团队来说,commit message 的规范也很重要,一个规范的 commit message 会让我们回顾之前的 git 历史时十分清晰明了,这里我们使用尤大在 vue 中的方法
该方法依赖 yorkie 库,我们在集成lint-staged 的时候已经进行了安装,这里不再重复安装。
配置 package.json gitHooks
"gitHooks": {
"commit-msg":"node scripts/verify-commit-msg.js",
"pre-commit": "lint-staged"
}
这里的作用是在 commit-msg 钩子中,执行 scripts/verify-commit-msg.js 文件,
verify-commit-msg.js 文件我们直接在尤大的vue项目中拷贝过来即可。
该文件的大致逻辑为通过 fs 模块读取 git 文件中的 commit message,并通过正则进行校验,
校验不通过的话,报错提示并阻断 git commit
这里可能会有一个问题就是拷贝 verify-commit-msg.js 到文件后,eslint会报一个警告
Require statement not part of import statement.
可以通过在 .eslintrc 中配置如下规则取消该校验
"rules":{
"@typescript-eslint/no-var-requires": 0
}
verify-commit-msg.js文件中依赖 chalk 库,chalk 提供了终端中console设置颜色的功能
安装chalk yarn add chalk -D
然后我们通过 git commit -m "test" 测试发现可以拦截不符合要求的commit mesage了
我本地是win10系统,发现 chalk 并没有生效,这里需要在 verify-commit-msg.js 添加下图第二行代码
再次执行 git commit -m "test" 测试发现 chalk 生效了
至此,集成commit-msg校验完成。《项目地址》
7. gitHooks 添加 test 校验
上面我们添加了 jest 单元测试和 cypress e2e 测试,并进行了整合,但是通常我们不希望每次手动执行测试命令,而是希望在提交代码的时候自动执行测试保证推送到 git上的代码的正确性即可。这一点,我们通过package.json 中添加 git hooks 钩子来实现
"gitHooks": {
"commit-msg": "node scripts/verify-commit-msg.js",
"pre-commit": "lint-staged",
"pre-push": "npm run test"
}
这里设置在git push 之前执行 test 命令,上面我们在该命令下配置了 jest 和 cypress 测试,这样每次git push 之前,就会跑一遍测试脚本了
这一步比较简单,代码合并到了集成commit-msg这一步的分支中。《项目地址》
8. 配置 alias 路径别名
路径别名的作用就是通过制定符号简化到指定路径的操作。
8-1. 配置 vite.config.js
例如:如果在 views/Home.vue 中引入 HelloWorld.vue 的时候是这样的
import HelloWorld from "../components/HelloWorld.vue";
如果层级再深一些,例如在 views/Goods/List/index.vue 中,就需要这样
import HelloWorld from "../../../components/HelloWorld.vue";
可以看到很麻烦,而且如果 HelloWorld.vue 文件的位置发生了变化,所有 HelloWorld.vue 的引入代码修改起来也比较麻烦,为了方便我们解决这样的问题,vite 也提供了配置 alias 别名的方法,修改 vite.config.js 如下:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
"@": resolve("./src")
},
},
plugins: [
vue()
]
})
这里我们设置了将 @ 符号指向到 ./src 目录下,以后无论在哪个文件,统一使用如下代码引入
HelloWorld.vue 即可,文件位置发生了变化,也可进行统一替换
import HelloWorld from "@/components/HelloWorld.vue";
文件位置发生了变化,这里也不需要进行调整,为我们提供了很大的便利。
但是到了这里还没有结束,在引入代码的时候可以发现当我们写 ./ 的时候,vscode 会有路径提示,但是当我们写 @/ 的时候,并没有路径提示,这里就需要对 ts 进行一些配置。
8-2. 配置 ts 支持 alias
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client", "jest", "node"],
"baseUrl":"./",
"paths":{
"@/*":["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
baseUrl: 指定当前目录
paths 中是我们的别名配置,这里的含义和 vite.config.js 中的配置一样,再次回到 .vue 文件中,使用 @ 别名引入 HelloWorld.vue 就会有路径提示了,如果无效,可以尝试下重启 vscode
8-3. 配置 jest 支持 alias
接下来我们尝试在 jest 单元测试中使用 alias,修改 tests/unit/index.spec.ts
import foo from "./foo";
import { mount } from "@vue/test-utils";
import Foo from "@/components/Foo.vue";
test("1+1=2", () => {
expect(1 + 1).toBe(2);
});
test("foo", () => {
expect(foo()).toBe("this is foo");
});
test("Foo", () => {
console.log("Foo", Foo);
console.log("mount", mount(Foo));
});
修改之后 vscode 就给我们报错提示了
然后我们尝试执行 yarn test-unit
发现对于 @ 路径别名,jest 是识别不了的,接下来需要修改 jest.config.js
module.exports = {
// 转换器
transform: {
// jest解析js的时候通过babel-jest解析
"^.+\\.jsx?$": "babel-jest",
// jest解析vue的时候通过vue-jest解析
"^.+\\.vue$": "vue-jest",
// jest解析ts的时候通过ts-jest解析
"^.+\\.tsx?$": "ts-jest"
},
// 配置测试脚本文件匹配规则
testMatch: [
"**/tests/unit/?(*.)+(spec).[jt]s?(x)"
],
// 配置路径别名
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1"
}
};
接下来再次执行 yarn test-unit,发现可以测试通过了。《项目地址》
9. 集成Vue Router
Vue Router 是 Vue.js 的官方路由,这里不必多说,直接安装
yarn add vue-router@next
src 目录下创建 router 文件夹,编写 index.ts
import { createRouter, createWebHashHistory } from "vue-router";
export const routes = [
{
path: "/",
redirect: "/home"
},{
path: "/home",
name: "Home",
component: () => import("@/views/Home.vue")
},{
path: "/login",
name: "Login",
component: () => import("@/views/Login.vue")
},{
path: "/404",
name: "404",
hidden: true,
meta: { notNeedAuth: true },
component: () => import("@/views/404.vue")
},
// 匹配所有路径 vue2使用* vue3使用/:pathMatch(.*)或/:catchAll(.*)
{
path: "/:catchAll(.*)",
redirect: "/404"
}
];
// 路由实例
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;
这里都是基本用法,不做赘述,看文档即可。
修改 App.vue
<template>
<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>
创建 views/Home.vue
<template>
<div>
<p>This is Home</p>
<img alt="Vue logo" src="@/assets/logo.png" />
<HelloWorld msg="Hello Vite + Vue 3 + TypeScript" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({
name: "Home",
components: { HelloWorld },
setup() {
return {};
}
});
</script>
创建 views/Login.vue
<template>
<div>This is Login</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "Login",
setup() {
return {};
}
});
</script>
main.ts 引入 vue router
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";
createApp(App).use(router).mount("#app");
至此,vue router 集成完成。 《项目地址》
10. 集成 Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化,直接安装。
yarn add vuex@next
src 目录下创建 store 文件夹,这里我们一步到位,直接把模块划分开,创建一个 user 模块作为示例:
src/store/index.ts
import { createStore } from "vuex";
import getters from "./getters";
import user from "./modules/user";
const modules = { user,};
const store = createStore({ modules, getters,});
export default store;
src/store/modules/user.ts
export default {
namespaced: true,
state: {
userInfo: {
userId:"001",
name: "wzy"
}
},
mutations: {
},
actions: {
}
};
src/store/getters.ts
type state = {
user: {
userInfo: {
name: string;
token: string;
avatar: string;
roles: string[];
};
}
};
export default {
userInfo: (state: state) => state.user.userInfo
};
main.ts 引入vuex
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";
import store from "./store/index";
createApp(App)
.use(store)
.use(router)
.mount("#app");
src/views/Home.vue 使用
<template>
<div>
<p>This is Home</p>
<img alt="Vue logo" src="@/assets/logo.png" />
<HelloWorld msg="Hello Vite + Vue 3 + TypeScript" />
<p>Name: {{ userInfo.name }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useStore } from "vuex";
import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({
name: "Home",
components: { HelloWorld },
setup() {
// 获取类型化的 store
const store = useStore();
// 获取 userInfo
const userInfo = store.getters.userInfo;
return {
userInfo
};
}
})
</script>
至此,vuex 集成完成。 《项目地址》
11. 集成 Element3
Element3,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库。安装
yarn add element3
main.ts 中引入 element3
这里采用按需引入的方式引入部分组件
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";
import store from "./store/index";
// Element3样式文件
import "element3/lib/theme-chalk/index.css";
import {
ElIcon,
ElButton,
ElForm,
ElFormItem,
ElInput
} from "element3";
createApp(App)
.use(store)
.use(router)
.use(ElIcon)
.use(ElButton)
.use(ElForm)
.use(ElFormItem)
.use(ElInput)
.mount("#app");
修改 src/views/Login.vue
<template>
<div class="login_main" @keyup.enter="login">
<!-- 中间盒子 -->
<div class="content">
<h3 class="title">登录</h3>
<el-form ref="form" :model="param" :rules="rules">
<el-form-item prop="name">
<el-input
v-model="param.name"
prefix-icon="el-icon-user"
placeholder="账号"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="param.password"
prefix-icon="el-icon-lock"
placeholder="密码"
show-password
autocomplete
></el-input>
</el-form-item>
<el-button class="w_100" type="primary" @click="login">登录</el-button>
</el-form>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from "vue";
export default defineComponent({
name: "Login",
setup() {
// 表单
const form = ref(null);
// 请求参数
const param = reactive({
name: "",
password: "",
});
// 表单校验规则
const rules = reactive({
name: [
{ required: true, message: "请输入账号", trigger: "blur" },
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" }
],
});
// 密码表单类型
const passInputType = ref("password");
// 修改密码表单类型
const changeInputType = (val: string) => {
passInputType.value = val;
};
// 登录
const login = () => {
form.value.validate((valid: boolean) => {
if (valid) {
console.log("login 校验通过!");
} else {
return false;
}
});
};
return {
form,
param,
rules,
passInputType,
changeInputType,
login,
};
}
});
</script>
<style scoped>
.login_main {
display: flex;
align-items: center;
justify-content: center;
}
.content {
width: 500px;
}
</style>
修改后的登录页如下:
至此,element3 集成完成。 《项目地址》
12. 集成 axios + mockjs + sass
12-1. 集成axios
项目中涉及到前后端交互就肯定需要一个HTTP库,这里我们集成最常用的 axios
yarn add axios
src 目录下创建 utils 文件夹存放我们的工具方法
编写 request.ts 封装 axios
import axios from "axios";
import { Message } from "element3";
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.BASE_URL,
timeout: 10000
});
// 请求拦截器
service.interceptors.request.use(
(config) => {
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response:any) => {
const res = response.data;
if (res.header.code !== 0) {
Message.error(res.header.msg || "Error");
return Promise.reject(
new Error(res.header.msg || "Error"));
}
return res;
},
(error) => {
Message.error("request error");
return Promise.reject(error);
}
);
/**
* 封装接口请求方法
* @param url 域名后需补齐的接口地址
* @param method 接口请求方式
* @param data d请求数据体
*/
type Method =
| "get"
| "GET"
| "delete"
| "DELETE"
| "head"
| "HEAD"
| "options"
| "OPTIONS"
| "post"
| "POST"
| "put"
| "PUT"
| "patch"
| "PATCH"
| "purge"
| "PURGE"
| "link"
| "LINK"
| "unlink"
| "UNLINK";
const request = (
url: string,
method: Method,
data: Record<string, unknown>
) => {
return service({
url,
method,
data,
});
};
export default request;
src 目录下创建 api 文件夹存放所有接口请求方法
src/api 下创建 user.ts 编写 user 相关接口请求
import request from "../utils/request";
type Method =
| "get"
| "GET"
| "delete"
| "DELETE"
| "head"
| "HEAD"
| "options"
| "OPTIONS"
| "post"
| "POST"
| "put"
| "PUT"
| "patch"
| "PATCH"
| "purge"
| "PURGE"
| "link"
| "LINK"
| "unlink"
| "UNLINK";
const curryRequest = (
url: string,
method: Method,
data?: Record<string, unknown> | any
) => {
return request(
`/module/user/${url}`,
method,
data
)
};
// 登录
export function apiLogin(data: {
name: string;
password: string;
}): PromiseLike<any> {
return curryRequest("login", "post", data);
}
将登录相关操作放在 store/modules/user.ts actions 中进行,并完善 mutations
import { apiLogin } from "@/api/user";
type userInfo = {
userId: string;
name: string;
};
type state = {
userInfo: userInfo;
};
type context = {
state: Record<string, unknown>;
mutations: Record<string, unknown>;
actions: Record<string, unknown>;
dispatch: any;
commit: any;
};
type loginData = {
name: string;
password: string;
};
export default {
namespaced: true,
state: {
userInfo: {
userId: "",
name: ""
},
},
mutations: {
// 设置用户信息
SET_USERINFO(state:state,val:userInfo){
state.userInfo = val;
}
},
actions: {
// 登录
login({ commit }: context, data: loginData) {
return new Promise((resolve) => {
apiLogin(data).then(async (res) => {
// 更新用户信息
commit("SET_USERINFO", {
userId: res.body.userId,
name: res.body.name,
});
resolve("success");
})
})
}
}
}
更新 Login.vue
<template>
<div class="login_main" @keyup.enter="login">
<!-- 中间盒子 -->
<div class="content">
<h3 class="title">登录</h3>
<el-form ref="form" :model="param" :rules="rules">
<el-form-item prop="name">
<el-input
v-model="param.name"
prefix-icon="el-icon-user"
placeholder="账号"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="param.password"
prefix-icon="el-icon-lock"
placeholder="密码"
show-password
autocomplete
></el-input>
</el-form-item>
<el-button class="w_100" type="primary" @click="login">登录</el-button>
</el-form>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from "vue";
import { useStore } from "vuex";
import { useRouter } from "vue-router";
export default defineComponent({ name: "Login", setup() {
// sotre实例
const store = useStore();
// 路由实例
const router = useRouter();
// 表单
const form = ref(null);
// 请求参数
const param = reactive({
name: "",
password: "",
});
// 表单校验规则
const rules = reactive({
name: [
{ required: true, message: "请输入账号", trigger: "blur" },
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" }
],
});
// 密码表单类型
const passInputType = ref("password");
// 修改密码表单类型
const changeInputType = (val: string) => {
passInputType.value = val;
};
// 登录
const login = () => {
form.value.validate((valid: boolean) => {
if (valid) {
store.dispatch("user/login", param).then(() => {
router.push({ name: "Home" });
});
} else {
return false;
}
});
};
return {
form,
param,
rules,
passInputType,
changeInputType,
login,
};
}
})
</script>
<style scoped>
.login_main {
display: flex;
align-items: center;
justify-content: center;
}
.content {
width: 500px;
}
</style>
我一般推荐使用 router.push({ name: "Home" }) 进行路由跳转,因为每个路由定义的 name 一般不会发生变化,但是路径可能会有调整,这样就避免了路径变化后还需要修改对应路由操作的问题
至此,请求相关逻辑编写完成,但是当我们搭建框架的时候一般后台也处于初始阶段,接口还没有准备好,这时候就需要 mockjs 帮我们模拟接口请求
12-2. 集成 mockjs
安装 mockjs
yarn add mockjs@1.1.0
yarn add vite-plugin-mock@2.9.4 -D
修改 vite.config.js 引入 mockjs
import { UserConfigExport, ConfigEnv,loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import { viteMockServe } from "vite-plugin-mock";
export default ({ command, mode }: ConfigEnv): UserConfigExport => {
const plugins = [vue()];
const env = loadEnv(mode, process.cwd());
// 如果当前为测试环境,添加mock插件
if (mode === "development") {
plugins.push(
viteMockServe({
mockPath: "mock",
localEnabled: command === "serve"
})
);
}
return {
resolve: {
alias: {
"@": resolve("./src")
},
},
plugins
}
};
项目根目录创建 mock 文件夹,新建 user.ts 编写 user 相关 mock 接口
export default [
{
url: `/module/user/login`,
method: "post",
response: (req) => {
return {
header: {
code: 0,
msg: "OK",
},
body: {
userId:"001",
name: `${req.body.name}小明`
}
}
}
}
];
打开登录页面,输入账号1,密码1,点击登录
控制台可见成功发起了请求并收到了响应,页面跳转到了Home并获取到了userInfo
12-3. 集成 sass
相关文档 安装 sass
yarn add sass -D
修改 src/views/Login.vue style 测试
<style lang="scss" scoped>
$bg: #364d81;
.login_main {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: $bg;
.content {
width: 500px;
.title {
color: #fff;
}
}}
</style>
至此,集成 sass 完成。
12-4. 解决 jest 单元测试不支持 import.meta.env
本以为到此万事大吉,但是当我们针对 src/utils/request.ts 进行单元测试的时候
tests/unit/request.spec.ts
import request from "@/utils/request";
test("request", () => {
expect(request).not.toBeNull();
expect(request).not.toBeUndefined();
});
发现 jest 报错了
这里我们之前tsconfig.js中是配置了 "module": "esnext" 的,其实该问题是当前的 ts-jest 库没有对 import.meta.env 做支持,这里采用了如下方法
修改 vite.config.js
import { UserConfigExport, ConfigEnv,loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import { viteMockServe } from "vite-plugin-mock";
export default ({ command, mode }: ConfigEnv): UserConfigExport => {
const plugins = [vue()];
const env = loadEnv(mode, process.cwd());
// 如果当前是测试环境,使用添加mock插件
if (mode === "development") {
plugins.push(
viteMockServe({
mockPath: "mock",
localEnabled: command === "serve"
})
)
}
// 处理使用import.meta.env jest 测试报错问题
const envWithProcessPrefix = Object.entries(env).reduce(
(prev, [key, val]) => {
return {
...prev,
// 环境变量添加process.env
["process.env." + key]: `"${val}"`
};
},
{}
);
return {
base: "./",
resolve: {
alias: {
"@": resolve("./src"),
},
},
plugins,
define: envWithProcessPrefix,
}};
将拿到的环境相关的变量保存到 process.env 中并向外暴露
修改 request.ts
import axios from "axios";
import { Message } from "element3";
// 创建axios实例
const service = axios.create({
baseURL: process.env.VITE_BASE_URL,
timeout: 10000
});
baseURL 中 使用 process.env 下的变量
运行 yarn test-unit 测试通过。
至此,集成 axios+mockjs+sass 完成。《项目地址》
End
至此,本文就结束了,后续会在此基础上搭建后台管理系统,感兴趣的点个关注吧!
关于本文有任何问题或建议,欢迎留言讨论!