Vue2-一篇文章带你读懂Vue的代码(保姆篇详解)

在这里插入图片描述


更多相关内容可查看

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:用户登陆,会输入用户名跟密码,这个信息传到服务端会生成一个令牌,然后这个令牌会通过响应头来返回给客户端,会保存在客户端,客户端下次访问的时候会将令牌包含在请求头发送给服务端,服务端进行解析验证是否是哪个用户,并返回该用户的数据给客户端

本篇小结

项目千千万,组件千千万,很多项目上述说明的组件可能没有,更多的组件文章可能也没涉及,但相信看完本篇文章的你一定或多或少在某一个点让你恍然大悟,希望你在前端的路上顺利直行

  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来一杯龙舌兰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值