Loading

【WebWiki】02 · Vue 整合(实操)

网络通信 : axios
页面跳转 : vue-router
状态管理:vuex
Vue-UI : ICE , Element UI

vue官方文档

https://cn.vuejs.org/v2/guide/

一、前端核心分析

  • Vue (读音/vju/, 类似于view)是一套用于构建用户界面的渐进式框架,发布于2014年2月。

  • 与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。

  • Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库(如: vue-router: 跳转,vue-resource: 通信,vuex:管理)或既有项目整合。

1. 前端三要素

  • HTML (结构) :超文本标记语言(Hyper Text Markup Language) ,决定网页的结构和内容
  • CSS (表现) :层叠样式表(Cascading Style sheets) ,设定网页的表现样式
  • JavaScript (行为) :是一种弱类型脚本语言,其源代码不需经过编译,而是由浏览器解释运行,用于控制网页的行为

2. JavaScript框架

  • jQuery: 大家熟知的JavaScript框架,优点是简化了DOM操作,缺点是DOM操作太频繁,影响前端性能;在前端眼里使用它仅仅是为了兼容IE6、7、8;
  • Angular: Google收购的前端框架,由一群Java程序员开发,其特点是将后台的MVC模式搬到了前端并增加了模块化开发的理念,与微软合作,采用TypeScript语法开发;对后台程序员友好,对前端程序员不太友好;最大的缺点是版本迭代不合理(如: 1代-> 2代,除了名字,基本就是两个东西;截止发表博客时已推出了Angular6)
  • React: Facebook出品,一款高性能的JS前端框架;特点是提出了新概念[虚拟DOM]用于减少真实DOM操作,在内存中模拟DOM操作,有效的提升了前端渲染效率;缺点是使用复杂,因为需要额外学习一门[JSX] 语言;
  • Vue:一款渐进式JavaScript框架,所谓渐进式就是逐步实现新特性的意思,如实现模块化开发、路由、状态管理等新特性。其特点是综合了Angular (模块化)和React (虚拟DOM)的优点;
  • Axios :前端通信框架;因为Vue 的边界很明确,就是为了处理DOM,所以并不具备通信能力,此时就需要额外使用一个通信框架与服务器交互;当然也可以直接选择使用jQuery提供的AJAX通信功能;

二、vue第一个程序

1、mvvm

MVVM是Model-View-ViewModel的简写。它本质上就是MVC (Model-View- Controller)的改进版。即模型-视图-视图模型。

  • 模型:指的是后端传递的数据。
  • 视图:指的是所看到的页面。
  • 视图模型:mvvm模式的核心,它是连接view和model的桥梁。
  • 它有两个方向:
    • 一是将模型转化成视图,将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
    • 二是将视图转化成模型,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
    • 这两个方向都实现的,我们称之为数据的双向绑定。

image-20210201152032659

总结:在MVVM的框架下视图和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。

2. 为什么要使用MVVM

MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model)。

有几大好处:

  • 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
  • 可复用:你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
  • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
  • 可测试:界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

3、Vue 是 MVVM 模式的实现者

Vue.js 就是一个MVVM的实现者,他的核心就是实现了DOM监听与数据绑定

4、Vue在线cdn

三、VUE基本语法

1、v-bind

现在数据和DOM已经被建立了关联,所有的东西都是响应式的。

我们在控制台操作对象的属性,界面可以实时更新。我们可以使用v-bind来绑定元素属性!

<!DOCTYPE>
<html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>狂神说Java</p>

<!--view层 模板-->
<div id="app">
    <span v-bind:title="message">鼠标悬停几秒钟查看此处动态绑定的提示信息!</span>
</div>
</body>

<!--导入js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    var vm = new Vue({
        el: "#app",
        data: {
            message: "hello,vue"
        }
    })
</script>
</html>

2、v-if v-else

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
</head>
<body>
<p>狂神说Java</p>

<!--view层 模板-->
<div id="app">
   <h1 v-if="type==='A'">A</h1>
   <h1 v-else-if="type==='B'">B</h1>
   <h1 v-else>C</h1>
</div>
</body>

<!--导入js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
   var vm = new Vue({
       el: "#app",
       data: {
           type: "A"
       }
   })
</script>
</html>

3、v-for

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>狂神说Java</p>

<!--view层 模板-->
<div id="app">
    <li v-for="item in items">
        姓名:{{item.name}},年龄:{{item.age}}
    </li>
</div>
</body>

<!--导入js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    var vm = new Vue({
        el: "#app",
        data: {
            items: [
                {name: "zhangsan", age: 12},
                {name: "lisi", age: 10},
                {name: "wangwu", age: 16}
            ]
        }
    })
</script>
</html>

4、v-on 事件绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>狂神说Java</p>

<!--view层 模板-->
<div id="app">
    <button v-on:click="sayHi">Click Me</button>
</div>
</body>

<!--导入js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    var vm = new Vue({
        el: "#app",
        data: {
            message: "你点我干嘛?"
        },
        methods: {
            //方法必须绑定在Vue的Methods对象中,v-on:事件
            sayHi: (function (event) {
                alert(this.message)
            })
        }
    })
</script>
</html>

四、Vue双向绑定 v-model

1. 什么是双向绑定

Vue.js是一个MVVM框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。这也算是Vue.js的精髓之处了。

值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的,非UI控件不会涉及到数据双向绑定。单向数据绑定是使用状态管理工具的前提。如果我们使用vuex,那么数据流也是单项的,这时就会和双向数据绑定有冲突。

2. 为什么要实现数据的双向绑定

在Vue.js 中,如果使用vuex ,实际上数据还是单向的,之所以说是数据双向绑定,这是用的UI控件来说,对于我们处理表单,Vue.js的双向数据绑定用起来就特别舒服了。即两者并不互斥,在全局性数据流使用单项,方便跟踪;局部性数据流使用双向,简单易操作。

3. 在表单中使用双向数据绑定

你可以用v-model指令在表单 <input>、<textarea> 及<select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但v-model本质上不过是语法糖。它负责监听户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<!--view层 模板-->
<div id="app">
    下拉框:
    <select v-model="selected">
        <option value="" disabled>-请选择-</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
    </select>
    <p>value:{{selected}}</p>
</div>
</body>

<!--导入js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    var vm = new Vue({
        el: "#app",
        data: {
            selected: ""
        }
    })
</script>
</html>

五、Vue组件

组件是可复用的Vue实例,说白了就是一组可以重复使用的模板,跟JSTL的自定义标签、Thymeleaf的th:fragment 等框架有着异曲同工之妙。通常一个应用会以一棵嵌套的组件树的形式来组织:

image-20210202013232421

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<!--view层 模板-->
<div id="app">
    <qinjiang v-for="item in items" v-bind:qin="item"></qinjiang>
</div>
</body>

<!--导入js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    Vue.component("qinjiang",{
        props: ['qin'],
        template: '<li>{{qin}}</li>'
    })

    var vm = new Vue({
        el: "#app",
        data: {
            items: ['Java','Python','Php']
        }
    })
</script>
</html>

六、Axios通信

1. 什么是Axios

Axios是一个开源的可以用在浏览器端和NodeJS 的异步通信框架,她的主要作用就是实现AJAX异步通信,其功能特点如下:

  • 从浏览器中创建XMLHttpRequests

  • 从node.js创建http请求

  • 支持Promise API [JS中链式编程]

  • 拦截请求和响应

  • 转换请求数据和响应数据

  • 取消请求

  • 自动转换JSON数据

  • 客户端支持防御XSRF (跨站请求伪造)

    GitHub: https://github.com/ axios/axios
    中文文档: http://www.axios-js.com/

2. 为什么要使用Axios

由于Vue.js是一个视图层框架且作者(尤雨溪) 严格准守SoC (关注度分离原则),所以Vue.js并不包含Ajax的通信功能,为了解决通信问题,作者单独开发了一个名为vue-resource的插件,不过在进入2.0 版本以后停止了对该插件的维护并推荐了Axios 框架。少用jQuery,因为它操作Dom太频繁 !

模拟Json数据:data.json

{
  "name": "weg",
  "age": "18",
  "sex": "男",
  "url":"https://www.baidu.com",
  "address": {
    "street": "文苑路",
    "city": "南京",
    "country": "中国"
  },
  "links": [
    {
      "name": "bilibili",
      "url": "https://www.bilibili.com"
    },
    {
      "name": "baidu",
      "url": "https://www.baidu.com"
    },
    {
      "name": "cqh video",
      "url": "https://www.4399.com"
    }
  ]
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--view层 模板-->
<div id="vue">
    {{info}}
</div>
</body>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<!--导入axios-->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script>
    var vm = new Vue({
        el: "#vue",
        //data:vm的属性
        //data():vm方法
        data(){
            return{
                //请求的返回参数,必须和json字符串一样
                info:{
                    name: null,
                    age: null,
                    sex: null,
                    url: null,
                    address: {
                        street: null,
                        city: null,
                        country: null
                    }
                }
            }
        },
        //钩子函数,链式编程,ES6新特性
        mounted(){
            axios.get("../data.json").then(res => (this.info=res.data))
        }
    })
</script>
</html>

3. Vue计算属性

计算属性的重点突出在属性两个字上(属性是名词),首先它是个属性其次这个属性有计算的能力(计算是动词),这里的计算就是个函数;简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),仅此而已;可以想象为缓存!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><!DOCTYPE html>
        <html lang="en">
        <head>
        <meta charset="UTF-8">
        <title>Title</title>
</head>
<body>

<!--view层 模板-->
<div id="app">
    <div>currentTime1: {{currentTime1()}}</div>
    <div>currentTime2: {{currentTime2}}</div>
</div>
</body>

<!--导入js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    var vm = new Vue({
        el: "#app",
        methods: {
            currentTime1: function () {
                return Date.now(); // 返回一个时间戳
            }
        },
        computed: {
            //计算属性:methods,computed 方法名不能重名,重名字后,只会调用methods的方法
            currentTime2: function () {
                this.message;
                // 返回一个时间戳
                return Date.now();
            }
        }
    })
</script>

结论:
 调用方法时,每次都需要进行计算,既然有计算过程则必定产生系统开销,那如果这个结果是不经常变化的呢?此时就可以考虑将这个结果缓存起来,采用计算属性可以很方便的做到这一点,计算属性的主要特性就是为了将不经常变化的计算结果进行缓存,以节约我们的系统开销;

七、内容分发 slot

在Vue.js中我们使用 元素作为承载分发内容的出口,作者称其为插槽,可以应用在组合组件的场景中;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
<div id="app">
    <todo>
        <todo-title slot="todo-title" v-bind:name="title"></todo-title>
        <todo-items slot="todo-items" v-for="item in todoItems" v-bind:item="item"></todo-items>
    </todo>
</div>

<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    //slot 插槽 这个组件要定义在前面不然出不来数据
    Vue.component("todo", {
        template: '<div>\
                <slot name="todo-title"></slot>\
                <ul>\
                <slot name="todo-items"></slot>\
                </ul>\
                <div>'
    });
    Vue.component("todo-title", {
        //属性
        props: ['name'],
        template: '<div>{{name}}</div>'
    });
    Vue.component("todo-items", {
        props: ['item'],
        template: '<li>{{item}}</li>'
    });
    let vm = new Vue({
        el: "#app",
        data: {
            //标题
            title: "图书馆系列图书",
            //列表
            todoItems: ['三国演义', '红楼梦', '西游记', '水浒传']
        }
    });
</script>
</body>
</html>

八、自定义事件内容分发

组件如何才能删除Vue实例中的数据呢?此时就涉及到参数传递与事件分发了,Vue为我们提供了自定义事件的功能很好的帮助我们解决了这个问题;

使用this.$emit (‘自定义事件名’,参数)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
<div id="app">
    <todo>
        <todo-title slot="todo-title" v-bind:name="title"></todo-title>
        <todo-items slot="todo-items" v-for="(item,index) in todoItems" v-bind:item="item"
                    v-bind:index="index" v-on:remove2="removeItems(index)"></todo-items>
    </todo>
</div>

<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
    //slot 插槽 这个组件要定义在前面不然出不来数据
    Vue.component("todo", {
        template: '<div>\
                <slot name="todo-title"></slot>\
                <ul>\
                <slot name="todo-items"></slot>\
                </ul>\
                <div>'
    });
    Vue.component("todo-title", {
        //属性
        props: ['name'],
        template: '<div>{{name}}</div>'
    });
    Vue.component("todo-items", {
        props: ['item','index'],
        template: '<li>{{index}}---{{item}} <button @click="remove1">删除</button></li>',
        methods: {
            remove1: function (index) {
                // this.$emit 自定义事件分发
                this.$emit('remove2',index)
            }
        }
    });
    let vm = new Vue({
        el: "#app",
        data: {
            //标题
            title: "图书馆系列图书",
            //列表
            todoItems: ['三国演义', '红楼梦', '西游记', '水浒传']
        },
        methods: {
            removeItems: function (index) {
                console.log("删除了"+this.todoItems[index]+"OK");
                this.todoItems.splice(index,1);
            }
        }
    });
</script>
</body>
</html>

九、第一个vue-cli项目

1. 什么是vue-cli

vue-cli 官方提供的一个脚手架,用于快速生成一个 vue 的项目模板;

预先定义好的目录结构及基础代码,就好比咱们在创建 Maven 项目时可以选择创建一个骨架项目,这个骨架项目就是脚手架,我们的开发更加的快速;

主要功能:

  • 统一的目录结构
  • 本地调试
  • 热部署
  • 单元测试
  • 集成打包上线

2. 需要的环境

确认nodejs安装成功:

  • cmd 下输入 node -v,查看是否能够正确打印出版本号即可!
  • cmd 下输入 npm-v,查看是否能够正确打印出版本号即可!

这个npm,就是一个软件包管理工具,就和linux下的apt软件安装差不多!

npm 是 JavaScript 世界的包管理工具,并且是 Node.js 平台的默认包管理工具。通过 npm 可以安装、共享、分发代码,管理项目依赖关系。

安装 Node.js 淘宝镜像加速器(cnpm)

这样子的话,下载会快很多~

# -g 就是全局安装
npm install cnpm -g

# 若安装失败,则将源npm源换成淘宝镜像
# 因为npm安装插件是从国外服务器下载,受网络影响大
npm config set registry https://registry.npm.taobao.org

# 然后再执行
npm install cnpm -g

3. 安装vue-cli

#在命令台输入
cnpm install vue-cli -g
#查看是否安装成功
vue list

4. 第一个 vue-cli 应用程序

创建一个Vue项目,我们随便建立一个空的文件夹在电脑上。

# 这里的 myvue 是项目名称,可以根据自己的需求起名
vue init webpack myvue

一路都选择no即可;

初始化并运行

cd myvue
npm install
npm run dev

执行完成后,目录多了很多依赖

十、vue-router路由

Vue Router是Vue.js官方的路由管理器(路径跳转)。

它和Vue.js的核心深度集成,让构建单页面应用变得易如反掌。

包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于Vue.js过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的CSS class的链接
  • HTML5历史模式或hash模式,在IE9中自动降级
  • 自定义的滚动条行为

1. 安装

基于第一个vue-cli进行测试学习;

先查看node_modules中是否存在 vue-router

vue-router 是一个插件包,所以我们还是需要用 npm/cnpm 来进行安装的。打开命令行工具,进入你的项目目录,输入下面命令。

npm install vue-router --save-dev

十一、vue + ElementUI

  1. 安装依赖,我们需要安装 vue-router、element-ui、sass-loader 和node-sass 四个插件
# 进入工程目录
cd hello-vue
# 安装 vue-router
npm install vue-router --save-dev
# 安装 element-ui
npm i element-ui -S
# 安装依赖
npm install
# 安装 SASS 加载器
cnpm install sass-loader node-sass --save-dev
# 启动测试
npm run dev	

https://element.eleme.cn/#/zh-CN/component/installation

十二、路由嵌套

嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成。

1、Demo

  1. 创建用户信息组件,在 views/user 目录下创建一个名为 Profile.vue 的视图组件;

    Profile.vue

    <template>
      <h1>个人信息</h1>
    </template>
    <script>
      export default {
        name: "UserProfile"
      }
    </script>
    
  2. 在用户列表组件在 views/user 目录下创建一个名为 List.vue 的视图组件;

    List.vue

    <template>
      <h1>用户列表</h1>
    </template>
    <script>
      export default {
        name: "UserList"
      }
    </script>
    
  3. 修改首页视图,我们修改 Main.vue 视图组件,此处使用了 ElementUI 布局容器组件,代码如下:

Main.vue

<template>
  <div>
    <el-container>
      <el-aside width="200px">
        <el-menu :default-openeds="['1']">
          <el-submenu index="1">
            <template slot="title"><i class="el-icon-caret-right"></i>用户管理</template>
            <el-menu-item-group>
              <el-menu-item index="1-1">
                <!--插入的地方-->
                <router-link to="/user/profile">个人信息</router-link>
              </el-menu-item>
              <el-menu-item index="1-2">
                <!--插入的地方-->
                <router-link to="/user/list">用户列表</router-link>
              </el-menu-item>
            </el-menu-item-group>
          </el-submenu>
          <el-submenu index="2">
            <template slot="title"><i class="el-icon-caret-right"></i>内容管理</template>
            <el-menu-item-group>
              <el-menu-item index="2-1">分类管理</el-menu-item>
              <el-menu-item index="2-2">内容列表</el-menu-item>
            </el-menu-item-group>
          </el-submenu>
        </el-menu>
      </el-aside>

      <el-container>
        <el-header style="text-align: right; font-size: 12px">
          <el-dropdown>
            <i class="el-icon-setting" style="margin-right: 15px"></i>
            <el-dropdown-menu slot="dropdown">
              <el-dropdown-item>个人信息</el-dropdown-item>
              <el-dropdown-item>退出登录</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
        </el-header>
        <el-main>
          <!--在这里展示视图-->
          <router-view />
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
<script>
  export default {
    name: "Main"
  }
</script>
<style scoped lang="css">
  .el-header {
    background-color: #B3C0D1;
    color: #333;
    line-height: 60px;
  }
  .el-aside {
    color: #333;
  }
</style>
  1. 配置嵌套路由修改 router 目录下的 index.js 路由配置文件,使用children放入main中写入子模块,代码如下

    index.js

    import Vue from "vue";
    import Router from "vue-router";
    import Main from "../views/Main";
    import Login from "../views/Login";
    import UserList from "../views/user/List";
    import UserProfile from "../views/user/Profile";
    
    Vue.use(Router);
    
    export default new Router({
      routes: [
        {
          path: '/main',
          component: Main,
          //路由嵌套
          children: [
            {path: '/user/profile',component: UserProfile},
            {path: '/user/list',component: UserList}
          ]
        },
        {
          path: '/login',
          component: Login
        }
      ]
    });
    
  2. 路由嵌套效果图

image-20210204003817846

2. 组件重定向

重定向的意思大家都明白,但 Vue 中的重定向是作用在路径不同但组件相同的情况下,比如:
​ 在router下面index.js的配置

{
  path: '/main',
  name: 'Main',
  component: Main
},
{
  path: '/goHome',
  redirect: '/main'
}

十三、路由钩子与异步请求

1. 路由模式与 404

路由模式有两种

  export default new Router({
    mode: 'history',
    routes: [
    ]
  });

2. 路由钩子与异步请求

beforeRouteEnter:在进入路由前执行
beforeRouteLeave:在离开路由前执行

在Profile.vue中写:

export default {
  name: "UserProfile",
  beforeRouteEnter: (to, from, next) => {
    console.log("准备进入个人信息页");
    next();
  },
  beforeRouteLeave: (to, from, next) => {
    console.log("准备离开个人信息页");
    next();
  }
}

参数说明:

  • to:路由将要跳转的路径信息
  • from:路径跳转前的路径信息
  • next:路由的控制参数
  • next() 跳入下一个页面
  • next(’/path’) 改变路由的跳转方向,使其跳到另一个路由
  • next(false) 返回原来的页面
  • next((vm)=>{}) 仅在 beforeRouteEnter 中可用,vm 是组件实例

3. 在钩子函数中使用异步请求

  1. 安装 Axios

    cnpm install --save vue-axios
    
  2. main.js引用 Axios

    import axios from 'axios'
    import VueAxios from 'vue-axios'
    Vue.use(VueAxios, axios)
    
  3. 准备数据 : 只有我们的 static 目录下的文件是可以被访问到的,所以我们就把静态文件放入该目录下。
    数据和之前用的json数据一样 需要的去上述axios例子里

    // 静态数据存放的位置
    static/mock/data.json
    
  4. 在 beforeRouteEnter 中进行异步请求

    Profile.vue:

    export default {
      //第二种取值方式
      // props:['id'],
      name: "UserProfile",
      //钩子函数 过滤器
      beforeRouteEnter: (to, from, next) => {
        //加载数据
        console.log("进入路由之前")
        next(vm => {
          //进入路由之前执行getData方法
          vm.getData()
        });
      },
      beforeRouteLeave: (to, from, next) => {
        console.log("离开路由之前")
        next();
      },
      //axios
      methods: {
        getData: function () {
          this.axios({
            method: 'get',
            url: 'http://localhost:8080/static/mock/data.json'
          }).then(function (response) {
            console.log(response)
          })
        }
      }
    }
    
  5. 执行

export default {
  name: "UserProfile",
  beforeRouteEnter: (to, from, next) => {
    console.log("准备进入个人信息页");
    next();
  },
  beforeRouteLeave: (to, from, next) => {
    console.log("准备离开个人信息页");
    next();
  }
}
==**参数说明:**==
- to:路由将要跳转的路径信息
- from:路径跳转前的路径信息
- next:路由的控制参数
- next() 跳入下一个页面
- next(’/path’) 改变路由的跳转方向,使其跳到另一个路由
- next(false) 返回原来的页面
- next((vm)=>{}) 仅在 beforeRouteEnter 中可用,vm 是组件实例

3. 在钩子函数中使用异步请求

  1. 安装 Axios

    cnpm install --save vue-axios

  2. main.js引用 Axios

import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
  1. 准备数据 : 只有我们的 static 目录下的文件是可以被访问到的,所以我们就把静态文件放入该目录下。
    数据和之前用的json数据一样 需要的去上述axios例子里

    // 静态数据存放的位置
    static/mock/data.json
    
  2. 在 beforeRouteEnter 中进行异步请求

    Profile.vue:

    export default {
      //第二种取值方式
      // props:['id'],
      name: "UserProfile",
      //钩子函数 过滤器
      beforeRouteEnter: (to, from, next) => {
        //加载数据
        console.log("进入路由之前")
        next(vm => {
          //进入路由之前执行getData方法
          vm.getData()
        });
      },
      beforeRouteLeave: (to, from, next) => {
        console.log("离开路由之前")
        next();
      },
      //axios
      methods: {
        getData: function () {
          this.axios({
            method: 'get',
            url: 'http://localhost:8080/static/mock/data.json'
          }).then(function (response) {
            console.log(response)
          })
        }
      }
    }
    
  3. 执行


编者按(衔接现代工程化):上面一到十三节是 Vue2 时代的经典入门教程,已覆盖 MVVM、模板语法、组件、Axios、vue-router、ElementUI 的核心用法,建议先通读打基础。但 2026 年的生产项目早已进入 Vue3 + Vite + Pinia + TypeScript 时代。下面第十四节起,以一个「商城后台前端」为例,手把手走完从零搭建 → 工程配置原理 → 路由/状态管理接入 → Axios 封装 → 构建部署 → 踩坑排查的完整实操链路,让这份 Wiki 既保留经典、又接得住生产。

十四、现代工程化从零搭建(Vue3 + Vite)

目标:用 Vite 初始化一个带 TypeScript、Pinia、Vue Router、Axios 封装、ESLint 的商城后台工程。每一步都解释"为什么这么配"。

1. 初始化与目录划分

# 用官方脚手架生成 Vue3 + TS 骨架(替代老旧的 vue-cli / webpack)
pnpm create vite mall-admin --template vue-ts
cd mall-admin && pnpm install

推荐目录(中后台项目的常见约定):

src
├── api/            # 接口层:按模块(user/order/product)组织,调用 http
├── assets/         # 静态资源(图片、全局样式)
├── components/     # 通用业务组件(表格、弹窗、上传等)
├── composables/    # 组合函数 useXxx:可复用逻辑(如 useTable、useAuth)
├── config/         # 运行时配置(菜单、常量)
├── layouts/        # 布局(侧边栏/顶栏/内容区)
├── router/         # 路由与守卫
├── stores/         # Pinia 状态(user、cart、permission)
├── styles/         # 全局样式、变量(SCSS)
├── utils/          # 工具(http 封装、storage、校验)
├── views/          # 页面级组件,对应路由
├── App.vue
└── main.ts

为什么弃用 webpack/vue-cli? webpack 构建靠打包全部模块、冷启动慢;Vite 在开发期直接用浏览器原生 ESM 按需编译,冷启动秒级,HMR 几乎无感。生产构建仍用 Rollup(比 webpack 产物更小)。这是现代前端工程的默认选择。

2. 入口文件 main.ts(看装配全貌)

// main.ts —— 应用的装配中心
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'        // 路由
import pinia from './stores'          // Pinia(全局状态)
import 'element-plus/dist/index.css'  // 组件库样式
import './styles/index.scss'          // 全局样式

const app = createApp(App)
app.use(router)   // 挂载路由(等价于 Vue2 的 Vue.use(Router))
app.use(pinia)    // 挂载状态库
app.mount('#app') // 注意:mount 必须在 use 之后

3. 关键配置文件与原理说明

3.1 vite.config.ts(路径别名 + 代理)

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    // 别名 @ -> src,避免深层相对路径 ../../ 满天飞
    alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }
  },
  server: {
    port: 5173,
    // 开发代理:浏览器请求 /api 时由 devServer 转发到后端 8000,
    // 绕过浏览器同源策略(CORS)。仅开发期有效,生产靠 Nginx 反代。
    proxy: {
      '/api': { target: 'http://localhost:8000', changeOrigin: true, rewrite: p => p.replace(/^\/api/, '') }
    }
  },
  build: {
    rollupOptions: {
      output: {
        // 把大依赖(vue、element-plus)拆成独立 chunk,利用浏览器并行加载与缓存
        manualChunks: { 'vue-vendor': ['vue', 'vue-router', 'pinia'] }
      }
    }
  }
})

原理:alias 让打包器/TS 在解析 @/stores/user 时映射到 src/stores/user;proxy 是 Vite devServer 的请求转发,解决了"前端 5173、后端 8000"的跨域;manualChunks 做分包,避免首屏一次性下载全部代码。

3.2 tsconfig.json(与别名对齐)

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["src/*"] },   // 必须与 vite alias 一致,否则 TS 找不到模块
    "types": ["vite/client"]
  }
}

3.3 环境变量(.env 体系)

.env                  # 所有环境共有
.env.development      # pnpm dev 时加载
.env.production       # pnpm build 时加载
# .env.development
VITE_API_BASE=/api          # 只有 VITE_ 前缀的变量会被注入 import.meta.env
VITE_APP_TITLE=商城后台(开发)
// 代码里使用:import.meta.env.VITE_API_BASE
const base = import.meta.env.VITE_API_BASE

为什么用 VITE_ 前缀? Vite 出于安全只把显式前缀的变量暴露到客户端包,避免把服务端密钥误打包进前端。密钥类配置应只存在于后端,前端只放"能公开"的接口地址/开关。

十五、路由与状态管理接入

1. Vue Router 4 接入(含鉴权守卫)

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layouts/Default.vue'
import { useUserStore } from '@/stores/user'

const routes = [
  { path: '/login', component: () => import('@/views/Login.vue') },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    meta: { requiresAuth: true },          // 路由元信息:标记需要登录
    children: [
      { path: 'dashboard', component: () => import('@/views/Dashboard.vue') },
      { path: 'orders', component: () => import('@/views/OrderList.vue') }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),             // history 模式(URL 无 #)
  routes
})

// 全局前置守卫:统一鉴权(对应 Vue2 的 beforeEach)
router.beforeEach((to) => {
  const user = useUserStore()
  if (to.meta.requiresAuth && !user.token) return '/login'   // 未登录拦截
  if (to.path === '/login' && user.token) return '/'         // 已登录别再进登录页
})

export default router

history 模式的部署代价:URL 无 # 更美观,但刷新 /orders 时浏览器直接向服务器请求该路径,若服务端未配置 fallback 到 index.html 会 404。Nginx 用 try_files $uri $uri/ /index.html; 兜底。

2. Pinia 状态管理接入(用户态 + 权限菜单)

// stores/user.ts(组合式写法,详见 01-前端Vue3 第7节)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { http } from '@/utils/http'

export const useUserStore = defineStore('user', () => {
  const token = ref(localStorage.getItem('token') ?? '')
  const menus = ref<Menu[]>([])
  const isLoggedIn = computed(() => !!token.value)

  async function login(form: LoginForm) {
    const data = await http.post('/auth/login', form)   // 见第十六节封装
    token.value = data.token
    localStorage.setItem('token', data.token)
  }
  // 登录后拉取权限菜单,再交给路由做动态注入
  async function loadMenus() { menus.value = await http.get('/auth/menus') }
  function logout() { token.value = ''; localStorage.removeItem('token') }

  return { token, menus, isLoggedIn, login, loadMenus, logout }
})

动态路由(菜单级权限):登录成功后,把后端返回的 menus 转成路由,用 router.addRoute() 注入,实现"看不到菜单就进不了页":

// 在登录成功后调用
const menus = await user.loadMenus()
menus.forEach(m => {
  router.addRoute('Layout', {           // 挂到名为 Layout 的父路由下
    path: m.path,
    name: m.name,
    component: () => import(`@/views/${m.component}.vue`),
    meta: { permission: m.code }
  })
})
// 注意:动态路由是运行时加的,刷新会丢。解决:
// 在 main.ts 里根据用户态预先构建一次,或配合 pinia 持久化 + 守卫里补加。

十六、Axios 统一封装与拦截器

这是生产项目必做的一环:把"带 token、拆后端 R、统一报错、401 跳登录"收敛到一个 http 实例里,业务代码只关心数据。

// utils/http.ts
import axios, { type AxiosInstance } from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router'
import { useUserStore } from '@/stores/user'

// 后端统一响应结构约定:{ code: 0, msg: 'ok', data: ... }
interface R<T> { code: number; msg: string; data: T }

const http: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE,   // 来自 .env,如 /api
  timeout: 10000                            // 10s 超时,避免请求永久挂起
})

// 请求拦截器:发出前统一加 Authorization
http.interceptors.request.use(config => {
  const token = useUserStore().token
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

// 响应拦截器:收事后统一拆包 + 错误处理
http.interceptors.response.use(
  (resp) => {
    const body = resp.data as R<unknown>
    if (body.code !== 0) {                  // 业务失败(非 HTTP 错误)
      ElMessage.error(body.msg)
      return Promise.reject(new Error(body.msg))
    }
    return body.data                        // 业务成功只回 data,调用方更干净
  },
  (err) => {
    const status = err.response?.status
    if (status === 401) {                   // 未登录/过期
      useUserStore().logout()
      router.push('/login')
    } else if (status === 403) {
      ElMessage.error('无权限')
    } else if (status >= 500) {
      ElMessage.error('服务器开小差了')
    } else {
      ElMessage.error('网络异常')
    }
    return Promise.reject(err)
  }
)

// 薄封装:让业务层用起来像 http.get<T>('/orders')
export function get<T>(url: string, params?: object) {
  return http.get('', { url, params }) as Promise<T>
}
export default http

拦截器顺序:请求拦截按注册顺序、响应拦截逆序执行(后进先出)。多个拦截器要注意顺序。Promise.reject 而非 throw,是与 axios 链风格一致的写法。

取消重复请求(进阶):用 AbortController + 请求 map,在路由切换/组件卸载时取消未完成请求,防止旧响应覆盖新页面数据(接口竞态)。

十七、构建、部署与容器化

1. 构建产物

pnpm build            # 产出到 dist/
pnpm preview          # 本地预览构建结果(默认 4173)

产物优化三板斧(在 vite.config.ts 的 build 配置):

  • 路由级懒加载:() => import() 自动分包,进入页面才下载;
  • gzip/brotli 预压缩:用 vite-plugin-compression 生成 .gz,由 Nginx 直接返;
  • 大依赖外置:manualChunks 把 vue/element-plus 拆独立 chunk,利用浏览器长效缓存。

2. Nginx 部署(最常用)

server {
    listen 80;
    server_name admin.example.com;
    root /usr/share/nginx/html;
    index index.html;

    # 解决 history 模式刷新 404:所有路径回退到 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 接口反代(生产环境的"代理",替代 devServer proxy)
    location /api/ {
        proxy_pass http://backend:8000/;
        proxy_set_header Host $host;
    }

    # 开启 gzip
    gzip on;
    gzip_types text/css application/javascript application/json;
}

3. Docker 化(CI/CD 友好)

# 多阶段构建:构建阶段用 node,运行阶段用 nginx
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install
COPY . .
RUN pnpm build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

十八、常见踩坑与排查清单

现象 根因 排查 / 解法
刷新页面 404(history 模式) 服务端未 fallback Nginx 加 try_files ... /index.html;或改用 hash 模式
跨域报错 CORS 开发没配 proxy / 生产没配反代 开发期 /api 代理;生产 Nginx 反代,前后端同域
动态路由刷新白屏 路由运行时注入、刷新丢失 main.ts 里按用户态预构建,或持久化后守卫补加
状态码/数据拿不到 响应拦截只返回 data 需要 headers/原始响应时用 resp.data 或封装 getRaw
Pinia 解构后不响应 直接 const {x} = store 丢失响应性 用 storeToRefs(store) 解构
改了代码不生效 缓存/HMR 失效 清 node_modules/.vite、重启 dev;检查 import.meta.env 是否重启读取
Element Plus 样式缺失 没引 element-plus/dist/index.css main.ts 引入全量样式,或按需引入时配 unplugin-vue-components
import 找不到模块 pnpm 严格隔离 / 别名未对齐 确认 vite alias 与 tsconfig paths 一致;缺依赖显式安装
打包体积过大 未分包 / 引入了整库 manualChunks、vite-plugin-visualizer 分析、import 按需
接口竞态(旧数据覆盖新页) 路由快切请求慢 AbortController 取消旧请求;或 onUnmounted 标记

线上排错工具:浏览器 DevTools 的 Network(看请求/响应/状态码)、Application(看 localStorage/token)、Console(看报错);配合后端日志与网关 access log 串联链路。

十九、小结

  • 经典部分(一到十三节)讲清了 Vue 的 MVVM、模板、组件、Axios、vue-router、ElementUI 用法,是现代开发的地基;
  • 现代实操(十四到十八节)补齐了生产链路:Vite 初始化 → 别名/代理/环境变量配置原理 → 路由守卫 + Pinia 动态路由鉴权 → Axios 拦截器统一封装 → 构建/部署/容器化 → 踩坑排查;
  • 关键认知:开发期跨域靠 vite proxy,生产期靠 Nginx 反代;history 模式必须服务端 fallback;状态与路由权限要联动;Axios 拦截器是"统一错误处理"的唯一正道。

与本文配套:《01-前端Vue3》讲原理与 API,《03-Java 核心》《04-Spring Boot》讲后端,四篇合起来即一个全栈商城的技术底座。

posted @ 2021-02-04 15:12  Stellan7  阅读(330)  评论(0)    收藏  举报