• 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: {
        '^@/(.*)$': '/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: '/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 ",
      "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 中,根据路由规则,会根据用户的路径请求动态加载对应的组件到 中显示

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

    本篇小结

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

  • 相关阅读:
    ARM day5
    计算机毕业设计springboot家具销售系统tj2lo源码+系统+程序+lw文档+部署
    使用PWM实现呼吸灯功能
    C++新特性笔记(1)
    Github每日精选(第17期):Django下的内容管理系统wagtail
    电子邮件营销初学者指南(二):如何开始与撰写
    Linux 文件类信息统计指令(awk、sort、wc -l)
    JS 逆向之 Hook
    mysql数据库数据如何迁移目录
    【数据结构】红黑树的插入与验证
  • 原文地址:https://blog.csdn.net/Aaaaaaatwl/article/details/140457586