文章目录
更多相关内容可查看
vue的框架及代码多种多样,这里也是小编在学习过程中一点点读到的东西,学识尚浅,如有误解,希望理解,文章是针对前端几乎没有什么概念的朋友写的
现在有很多本应该是后端的朋友,迫于社会压力开始自学前端,相信大家在看完这篇文章,希望为你学习前端有所帮助
目录结构
这是一个基本的框架,并可以在此基础上实现自己的功能,根据这个图片一点一点剖析目录结构
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── plop-templates # 基本模板
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── directive # 全局指令
│ ├── filters # 全局 filter
│ ├── icons # 项目所有 svg icons
│ ├── lang # 国际化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
build
通俗点:打包用的
build目录:通常包含用于构建和打包项目的配置文件和脚本
以下是一个build文件示例注意看注释
//vue.config.js: 这个文件通常是自定义的Vue项目配置文件,用于配置Webpack、Babel等构建工具的详细参数和行为。
//runjs: 这是一个Node.js的工具库,用于在Node.js环境中运行脚本。
//chalk: 这是一个Node.js库,用于在控制台输出中添加样式,比如不同颜色的文本。
//process.argv: 这是Node.js中用于获取命令行参数的全局变量。
//connect和serve-static: 这两个模块用于在Node.js中创建一个静态文件服务器,用于提供静态资源文件。
const { run } = require('runjs') // 导入runjs模块,用于执行命令行任务
const chalk = require('chalk') // 导入chalk模块,用于控制台输出的样式
const config = require('../vue.config.js') // 导入vue项目的配置文件
const rawArgv = process.argv.slice(2) // 获取命令行参数,去掉前两个默认参数
const args = rawArgv.join(' ') // 将命令行参数拼接成字符串
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
// 如果命令行参数包含--preview或者环境变量中有npm_config_preview
const report = rawArgv.includes('--report') // 检查是否需要生成报告
// 执行vue-cli-service build命令
run(`vue-cli-service build ${args}`)
const port = 9526 // 定义预览服务器的端口号
const publicPath = config.publicPath // 获取配置文件中的publicPath
var connect = require('connect') // 导入connect模块,用于创建HTTP服务器
var serveStatic = require('serve-static') // 导入serve-static模块,用于提供静态文件服务
const app = connect() // 创建connect实例
// 将静态资源服务挂载在路径publicPath下,服务目录为./dist
app.use(
publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
// 监听端口,启动预览服务器
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) // 输出预览地址
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) // 输出报告地址
}
})
} else {
// 如果没有--preview参数,则直接执行vue-cli-service build命令
run(`vue-cli-service build ${args}`)
}
mock
通俗点:模拟后端,比如你是一个前端,你开发完了,但是你后端的程序没有开发完,那么你调用后端的服务就不会返回信息,这样你就没法测试,所以mock的作用就是自己返回后端的信息用于测试
以下看一个mock的代码示例注意注释
const tokens = {
admin: {
token: 'admin-token'
},
editor: {
token: 'editor-token'
}
}
const users = {
'admin-token': {
roles: ['admin'],
introduction: 'I am a super administrator',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Super Admin'
},
'editor-token': {
roles: ['editor'],
introduction: 'I am an editor',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Normal Editor'
}
}
module.exports = [
// user login 接口配置
{
url: '/vue-admin-template/user/login', // 接口路径
type: 'post', // 请求类型
response: config => { // 响应处理函数
const { username } = config.body // 从请求体中获取用户名
const token = tokens[username] // 根据用户名获取对应的token
// 模拟错误情况
if (!token) {
return {
code: 60204,
message: 'Account and password are incorrect.'
}
}
// 正常情况下返回成功响应及token
return {
code: 20000,
data: token
}
}
},
// get user info 接口配置
{
url: '/vue-admin-template/user/info\.*', // 接口路径支持正则匹配
type: 'get', // 请求类型
response: config => { // 响应处理函数
const { token } = config.query // 从查询参数中获取token
const info = users[token] // 根据token获取对应的用户信息
// 模拟错误情况
if (!info) {
return {
code: 50008,
message: 'Login failed, unable to get user details.'
}
}
// 正常情况下返回成功响应及用户信息
return {
code: 20000,
data: info
}
}
},
// user logout 接口配置
{
url: '/vue-admin-template/user/logout', // 接口路径
type: 'post', // 请求类型
response: _ => { // 响应处理函数
return {
code: 20000,
data: 'success'
}
}
}
]
以上的代码类似于后端Controller的三个接口分别是:
- user login:
模拟用户登录接口,根据提交的用户名(通过POST请求体),返回对应的模拟token。如果用户名不存在,返回错误信息。 - get user info:
模拟获取用户信息接口,根据查询参数中的token(通过GET请求参数),返回对应的模拟用户信息。如果token无效,返回错误信息。 - user logout: 模拟用户登出接口,无论请求内容,总是返回成功的响应。
node_modules
通俗点:这个目录存放的就是依赖包
当你在 Vue 项目中使用 npm install
或yarn install
命令时,这些命令会根据项目中的package.json
文件中的依赖项列表,从 npm
或者 Yarn
的服务器上下载相应的包,并将它们存放在 node_modules
目录下
所以如果有依赖包不完整或者因为依赖有问题的 可以把这个目录删除 重新 npm install
或yarn install
,就可以把图示中所标注的依赖重新下载
public
通俗点:存放静态资源
public 目录是用来存放不经过webpack处理的静态资源的地方
。当你在 public 目录中放置文件时,它们将会被复制到最终构建的目录(例如 dist 目录)中,而且不会经过webpack打包处理。
以下是一个public文件的示例注意注释
<!DOCTYPE html>
<html>
<head>
<!-- 设置文档编码为UTF-8 -->
<meta charset="utf-8">
<!-- 使用最新的浏览器渲染引擎 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<!-- 响应式视口设置 -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- 设置网站图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 设置网页标题,使用webpackConfig.name动态获取 -->
<title><%= webpackConfig.name %></title>
</head>
<body>
<!-- 当浏览器不支持JavaScript时显示的提示 -->
<noscript>
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- Vue.js应用挂载点 -->
<div id="app"></div>
<!-- 构建后的文件将会自动注入到这里 -->
<!-- built files will be auto injected -->
</body>
</html>
- charset 和 X-UA-Compatible: 确保文档使用UTF-8编码,并指定使用最新的IE浏览器渲染引擎。
- viewport: 设置响应式视口,确保网页在移动设备上的正确显示。
- favicon.ico: 设置网站图标,这里使用了 <%= BASE_URL %> 来动态获取基础URL。
- title: 网页的标题,使用了 <%= webpackConfig.name %> 动态获取Webpack配置中定义的项目名称。
- noscript: 当浏览器不支持JavaScript时显示的提示信息,提醒用户启用JavaScript以继续使用应用。
-
: Vue.js应用的挂载点,Vue将会在这里渲染应用程序的组件。
- 注释部分: 表示构建后的文件将会自动注入到这里,通常由构建工具(如Webpack)自动生成。
src
src就是一个项目的代码内容,可理解为类似于idea中main-java的目录,每一个目录都是需要学习并要理解如何串起来的,下面有一个将整个项目串起来的示例,可参考文章最后
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── directive # 全局指令
│ ├── filters # 全局 filter
│ ├── icons # 项目所有 svg icons
│ ├── lang # 国际化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
tests/unit
通俗点:跟idea中的test一样的用处,比如你开发了一个查询功能,就可以进行单元测试
以下是一个测试示例注意注释
import { mount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
import ElementUI from 'element-ui'
import Breadcrumb from '@/components/Breadcrumb/index.vue'
// 创建本地 Vue 实例
const localVue = createLocalVue()
// 使用 Vue Router 和 ElementUI
localVue.use(VueRouter)
localVue.use(ElementUI)
// 定义路由
const routes = [
{
path: '/',
name: 'home',
children: [{
path: 'dashboard',
name: 'dashboard'
}]
},
{
path: '/menu',
name: 'menu',
children: [{
path: 'menu1',
name: 'menu1',
meta: { title: 'menu1' },
children: [{
path: 'menu1-1',
name: 'menu1-1',
meta: { title: 'menu1-1' }
},
{
path: 'menu1-2',
name: 'menu1-2',
redirect: 'noredirect',
meta: { title: 'menu1-2' },
children: [{
path: 'menu1-2-1',
name: 'menu1-2-1',
meta: { title: 'menu1-2-1' }
},
{
path: 'menu1-2-2',
name: 'menu1-2-2'
}]
}]
}]
}]
// 创建 Vue Router 实例
const router = new VueRouter({
routes
})
// 测试 Breadcrumb.vue 组件
describe('Breadcrumb.vue', () => {
const wrapper = mount(Breadcrumb, {
localVue,
router
})
// 测试用例:dashboard 页面
it('dashboard', () => {
router.push('/dashboard')
const len = wrapper.findAll('.el-breadcrumb__inner').length
//dashboard:测试面包屑在进入 dashboard 页面后是否只有一个面包屑节点。
expect(len).toBe(1)
})
// 测试用例:普通路由
it('normal route', () => {
router.push('/menu/menu1')
const len = wrapper.findAll('.el-breadcrumb__inner').length
expect(len).toBe(2)
})
// 测试用例:嵌套路由
it('nested route', () => {
router.push('/menu/menu1/menu1-2/menu1-2-1')
const len = wrapper.findAll('.el-breadcrumb__inner').length
expect(len).toBe(4)
})
// 测试用例:没有 meta.title 的路由
it('no meta.title', () => {
router.push('/menu/menu1/menu1-2/menu1-2-2')
const len = wrapper.findAll('.el-breadcrumb__inner').length
expect(len).toBe(3)
})
// 测试用例:最后一个面包屑没有链接
it('last breadcrumb', () => {
router.push('/menu/menu1/menu1-2/menu1-2-1')
const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
const redirectBreadcrumb = breadcrumbArray.at(3)
expect(redirectBreadcrumb.contains('a')).toBe(false)
})
})
Vue项目中常见的测试框架有 Jest 和 Mocha,而端到端测试通常使用 Cypress 或者 WebDriver。
执行单元测试(Unit Tests)
使用 Jest 进行单元测试
1.安装 Jest(如果尚未安装):
npm install --save-dev jest @vue/test-utils vue-jest
或
yarn add --dev jest @vue/test-utils vue-jest
2.编写测试文件:
在tests/unit目录下创建你的测试文件,例如example.spec.js。
3.运行测试:
在 package.json 中添加 Jest 的测试命令:
{
"scripts": {
"test:unit": "jest"
}
}
然后运行测试:
npm run test:unit
或
yarn test:unit
使用 Mocha 进行单元测试
Mocha 通常与 chai 或其他断言库一起使用。安装和配置步骤类似于 Jest,但需要手动配置测试运行器。
执行端到端测试(End-to-End Tests)
使用 Cypress 进行端到端测试
1.安装 Cypress(如果尚未安装):
npm install --save-dev cypress
或
yarn add --dev cypress
2.启动 Cypress:
添加一个测试命令到 package.json:
{
"scripts": {
"test:e2e": "cypress open"
}
}
3.运行 Cypress:
npm run test:e2e
或
yarn test:e2e
.editorconfig
通俗点:用于定义不同文件类型的代码风格规则,确保团队成员在不同编辑器中编码风格的一致性
文件示例注意注释
# http://editorconfig.org
# 设置 root = true 停止在父目录中查找 EditorConfig 文件。
root = true
# 默认设置适用于所有文件。
[*]
charset = utf-8 # 字符集设为 UTF-8。
indent_style = space # 使用空格进行缩进。
indent_size = 2 # 缩进大小设为 2 个空格。
end_of_line = lf # 使用 Unix 风格的换行符。
insert_final_newline = true # 确保文件末尾有一个空行。
trim_trailing_whitespace = true # 自动移除行尾的空白字符。
# Markdown 文件特定设置。
[*.md]
insert_final_newline = false # 不强制在 Markdown 文件末尾插入空行。
trim_trailing_whitespace = false # 不移除 Markdown 文件中的行尾空白字符。
.env.xxx
通俗点:不同环境的路径设置(开发环境、测试环境、生产环境)
代码示例
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
.travis.yml
通俗点:主要看的是node版本,有时候拉下来的代码适用哪个版本是不知道的可以看这个文件
用来配置 Travis CI(持续集成服务)的。它定义了在每次提交代码到版本控制系统时,Travis CI 将如何构建、测试和部署你的项目。
代码示例
# 指定使用的编程语言环境
language: node_js
# 指定 Node.js 版本为 10
node_js: 10
# 指定运行测试的脚本命令
script: npm run test
# 关闭邮件通知功能
notifications:
email: false
babel.config.js
babel.config.js 文件是用来配置 Babel 的转译器设置的。Babel 是一个 JavaScript 编译器,它可以将较新版本的 JavaScript 代码转换为向后兼容的版本,以便在不同环境中运行
代码示例注意注释
module.exports = {
presets: [
// 使用 Vue CLI 默认的 Babel 预设配置
'@vue/cli-plugin-babel/preset'
],
'env': {
'development': {
// 在开发环境下,使用 babel-plugin-dynamic-import-node 插件将所有 import() 转换为 require()。
// 这个插件可以显著提升热更新的速度,特别是当项目中有大量页面时。
// 更多信息可参考:https://panjiachen.github.io/vue-element-admin-site/zh/guide/advanced/lazy-loading.html
'plugins': ['dynamic-import-node']
}
}
}
jest.config.js
jest.config.js 文件是用来配置 Jest 测试框架的设置。Jest 是一个流行的 JavaScript 测试框架,用于编写和运行测试用例,检查代码的正确性和性能。
代码示例注意注释
module.exports = {
// 定义能够在测试中被引入的文件后缀
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
// 指定不同类型文件的转换器
transform: {
'^.+\\.vue$': 'vue-jest', // 处理 .vue 文件,使用 vue-jest 转换器
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', // 处理样式文件等,使用 jest-transform-stub 转换器
'^.+\\.jsx?$': 'babel-jest' // 处理 .js 和 .jsx 文件,使用 babel-jest 转换器
},
// 设置模块路径别名,以 @ 开头的路径映射到 src 目录下
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
// 配置 Jest 快照序列化器,用于处理 Vue 组件快照
snapshotSerializers: ['jest-serializer-vue'],
// 指定测试文件的匹配模式
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)', // 匹配 tests/unit 目录下的 .spec 文件
'**/__tests__/*.(js|jsx|ts|tsx)' // 匹配 __tests__ 目录下的文件
],
// 指定测试覆盖率收集的路径和范围
collectCoverageFrom: [
'src/utils/**/*.{js,vue}', // 收集 src/utils 目录下的 .js 和 .vue 文件
'!src/utils/auth.js', // 排除 auth.js 文件
'!src/utils/request.js', // 排除 request.js 文件
'src/components/**/*.{js,vue}' // 收集 src/components 目录下的 .js 和 .vue 文件
],
// 指定测试覆盖率报告输出的目录
coverageDirectory: '<rootDir>/tests/unit/coverage',
// 配置测试覆盖率报告的输出格式
coverageReporters: [
'lcov', // 生成 lcov 格式的报告
'text-summary' // 在命令行中显示文本摘要
],
// 设置测试环境的 URL
testURL: 'http://localhost/'
}
jsconfig.json
jsconfig.json 文件是用来配置 JavaScript 项目的编译选项和路径映射的文件。它通常用于编辑器和开发工具来提供代码补全、导航和语法检查的支持。
代码示例注意注释
"compilerOptions": {
"baseUrl": "./", // 设置项目根目录为当前目录
"paths": {
"@/*": ["src/*"] // 定义路径别名,将 @/* 映射到 src/* 目录下的文件
}
},
"exclude": ["node_modules", "dist"] // 排除编译时不需要处理的目录,如 node_modules 和 dist
}
如果你看了以上两个文件你会发现,有两个@映射为src的地方,经常会看到路由下的路径为@映射到src,那么这两个文件的映射有何区别呢
- jest.config.js的映射是针对Jest测试框架的
- jsconfig.json的映射 则影响整个 JavaScript 项目的路径解析
package-lock.json
package-lock.json 文件是 npm 5+ 版本引入的一种锁定机制,用于确保在一个项目中安装的 npm 包的版本是确定的和一致的。在 Vue.js 2 项目中,如果你使用 npm 作为包管理工具,那么通常会生成一个 package-lock.json 文件。
具体来说,package-lock.json 文件的作用包括:
- 确保版本一致性:package-lock.json文件记录了当前项目依赖包的确切版本号。这样,当项目的依赖关系发生变化或者在不同的环境中安装时,npm能够确保安装的包版本和开发环境中保持一致,避免因为不同的安装环境导致依赖包版本不一致的问题。
- 提升安装效率:通过锁定每个包的版本,npm 可以避免重复解析依赖关系和下载包。这样,当重新安装依赖时,npm 可以直接使用package-lock.json 中的信息来确定要安装的包及其版本,从而提高安装的效率。
- 支持可重复性安装:开发团队中的每个成员或者持续集成(CI)系统,通过使用相同的 package-lock.json
文件,可以确保每次安装依赖时都获得相同的包版本,这对于项目的稳定性和一致性非常重要。
package.json
package.json 文件是一个重要的配置文件,用于定义项目的元数据和配置信息
项目信息:
-
名称 (name):项目的名称,唯一标识项目。
-
版本 (version):项目的当前版本号。
-
描述 (description):对项目的简短描述。
-
作者 (author):项目的作者信息。
依赖管理:
- 依赖 (dependencies):项目运行时所依赖的第三方库或包,这些库在生产环境中必须存在。
- 开发依赖 (devDependencies):开发过程中所需的依赖,比如测试框架、构建工具等。
脚本命令:
- 脚本 (scripts):定义了一系列可执行的脚本命令,比如启动开发服务器、打包项目、运行测试等。
项目配置:
- 配置 (config):自定义的配置选项,可以在应用程序中使用。
仓库信息:
- 仓库 (repository):项目源代码的存储库信息,通常是指向项目的代码托管平台(如 GitHub)。
其他信息:
- 许可证 (license):项目使用的许可证信息。
- 依赖解析优先级 (resolutions):npm 解决依赖版本冲突的方式。
代码示例
{
"name": "vue-admin-template",
"version": "4.4.0",
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
"author": "Pan <panfree23@gmail.com>",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
"lint": "eslint --ext .js,.vue src",
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit"
},
"dependencies": {
"axios": "0.18.1",
"core-js": "3.6.5",
"element-ui": "2.13.2",
"js-cookie": "2.2.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"vue": "2.6.10",
"vue-router": "3.0.6",
"vuex": "3.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "9.5.1",
"babel-eslint": "10.1.0",
"babel-jest": "23.6.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2",
"connect": "3.6.6",
"eslint": "6.7.2",
"eslint-plugin-vue": "6.2.2",
"html-webpack-plugin": "3.2.0",
"mockjs": "1.0.1-beta3",
"runjs": "4.3.2",
"sass": "1.26.8",
"sass-loader": "8.0.2",
"script-ext-html-webpack-plugin": "2.1.3",
"serve-static": "1.13.2",
"svg-sprite-loader": "4.1.3",
"svgo": "1.2.2",
"vue-template-compiler": "2.6.10"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"license": "MIT"
}
vue.config.js
vue.config.js 文件是一个可选的配置文件,用于配置 Vue CLI 的行为和 webpack 构建工具的配置。该文件允许开发者在不强制 eject(即暴露配置文件)的情况下,对项目的构建过程进行自定义和配置
代码示例注意注释
'use strict'
const path = require('path')
const defaultSettings = require('./src/settings.js')
function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title || 'vue Admin Template' // 页面标题
const port = process.env.port || process.env.npm_config_port || 9528 // 开发环境端口号
// 所有配置项的解释可以在 https://cli.vuejs.org/config/ 找到
module.exports = {
publicPath: '/', // 公共路径
outputDir: 'dist', // 输出目录
assetsDir: 'static', // 静态资源目录
lintOnSave: process.env.NODE_ENV === 'development', // 开发环境下是否开启 eslint 保存检测
productionSourceMap: false, // 生产环境是否生成 sourceMap 文件
devServer: {
port: port, // 开发服务器端口号
open: true, // 是否自动打开浏览器
overlay: {
warnings: false, // 是否显示警告信息
errors: true // 是否显示错误信息
},
before: require('./mock/mock-server.js') // 配置 mock 数据的服务
},
configureWebpack: {
// 在 webpack 的 name 字段中提供应用程序的标题,以便在 index.html 中注入正确的标题。
name: name,
resolve: {
alias: {
'@': resolve('src') // 设置 @ 别名为 src 路径
}
}
},
chainWebpack(config) {
// 可以提高首屏加载速度,建议开启 preload
config.plugin('preload').tap(() => [
{
rel: 'preload',
// 忽略 runtime.js 文件
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial'
}
])
// 当页面很多时,会导致太多无意义的请求
config.plugins.delete('prefetch')
// 设置 svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
// 生产环境配置
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` 必须与 runtimeChunk 名称相同,默认为 `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // 只打包初始时依赖的第三方
},
elementUI: {
name: 'chunk-elementUI', // 单独将 elementUI 拆包
priority: 20, // 权重需要大于 libs 和 app,否则会被打包进 libs 或者 app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // 为了适配 cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // 可以自定义规则
minChunks: 3, // 最小共用次数
priority: 5,
reuseExistingChunk: true
}
}
})
// https://webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk('single')
}
)
}
}
阅读手上的项目
我会把我自己的思路通过文字以及图片来走一遍,但是项目各有各的样式,这里也是一个比较基本的项目
1.查看App.vue代码–可以看到只有一个
在 Vue Router 中,根据路由规则,会根据用户的路径请求动态加载对应的组件到 <router-view> 中显示
想比较深一点了解路由的可查看这篇文章Vue2-集成路由Vue Router介绍与使用
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
2.查看路由代码–以登陆为例会发现router会跳转到登陆页面
3.查看login/index.vue代码–即为登陆界面的代码(详情看注释)
结合代码跟图片进行理解
<template>
<!-- 登录页面模板 -->
<div class="login-container">
<!-- 登录表单 -->
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<!-- 标题容器 -->
<div class="title-container">
<h3 class="title">登录表单</h3>
</div>
<!-- 用户名表单项 -->
<el-form-item prop="username">
<!-- 用户图标 -->
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<!-- 用户名输入框 -->
<el-input
ref="username"
v-model="loginForm.username"
placeholder="请输入用户名"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<!-- 密码表单项 -->
<el-form-item prop="password">
<!-- 密码图标 -->
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<!-- 密码输入框 -->
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="请输入密码"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<!-- 显示密码按钮 -->
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<!-- 登录按钮 -->
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
<!-- 提示信息 -->
<div class="tips">
<span style="margin-right:20px;">用户名:admin</span>
<span>密码:任意</span>
</div>
</el-form>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
export default {
name: 'Login',
data() {
// 用户名验证函数
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('请输入正确的用户名'))
} else {
callback()
}
}
// 密码验证函数
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('密码长度不能少于6位'))
} else {
callback()
}
}
return {
// 登录表单数据
loginForm: {
username: 'admin',
password: '111111'
},
// 登录表单验证规则
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
loading: false, // 加载状态
passwordType: 'password', // 密码框类型,初始为password
redirect: undefined // 跳转路径
}
},
watch: {
// 监听路由变化
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true
}
},
methods: {
// 显示/隐藏密码
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
// 焦点设置到密码输入框
this.$nextTick(() => {
this.$refs.password.focus()
})
},
// 处理登录事件
handleLogin() {
//取到登陆表单进行验证validate
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true // 开始加载状态
// 调用登录操作,异步处理--vuex调用dispatch触发的是action,对应store下的user文件
this.$store.dispatch('user/login', this.loginForm).then(() => {
// 登录成功,跳转至指定页面或默认首页
this.$router.push({ path: this.redirect || '/' })
this.loading = false // 加载状态结束
}).catch(() => {
this.loading = false // 加载状态结束
})
} else {
console.log('表单验证失败!')
return false
}
})
}
}
}
</script>
4.查看vuex–store下的user.js(在上述注释中标明了触发位置),会触发actions
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
const getDefaultState = () => {
return {
token: getToken(), // 获取token,默认从本地存储中获取
name: '', // 用户名
avatar: '' // 头像
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState()) // 重置状态为默认状态
},
SET_TOKEN: (state, token) => {
state.token = token // 设置token
},
SET_NAME: (state, name) => {
state.name = name // 设置用户名
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar // 设置头像
}
}
const actions = {
// 用户登录
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
//在这里发送了网络请求,调用后端接口
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token) // 设置token
setToken(data.token) // 将token保存到本地存储
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.') // 如果获取信息失败,提示重新登录
}
const { name, avatar } = data
commit('SET_NAME', name) // 设置用户名
commit('SET_AVATAR', avatar) // 设置头像
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// 用户登出
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // 必须先移除token
resetRouter() // 重置路由
commit('RESET_STATE') // 重置状态
resolve()
}).catch(error => {
reject(error)
})
})
},
// 重置token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // 必须先移除token
commit('RESET_STATE') // 重置状态
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
5.调用api接口发送网络请求,这样就可以调到服务端了
上面代码会显示一个token的问题,有很多小伙伴不理解session跟token,这里统一聊一下,以下为个人理解
- session:用户登陆,会输入用户名跟密码,这个信息传到服务端会生成一个
Session ID
,会保存在服务端
,然后这个Session ID会通过cookie
来返回给客户端,客户端下次访问的时候会将带有SessionID的cookie
发送给服务端,服务端进行解析验证是否是哪个用户,并返回该用户的数据给客户端 - token:用户登陆,会输入用户名跟密码,这个信息传到服务端会生成一个
令牌
,然后这个令牌会通过响应头
来返回给客户端,会保存在客户端
,客户端下次访问的时候会将令牌包含在请求头
发送给服务端,服务端进行解析验证是否是哪个用户,并返回该用户的数据给客户端
本篇小结
项目千千万,组件千千万,很多项目上述说明的组件可能没有,更多的组件文章可能也没涉及,但相信看完本篇文章的你一定或多或少在某一个点让你恍然大悟,希望你在前端的路上顺利直行