Vue2.0

405 阅读18分钟

vue 特性(初体验)

数据驱动视图

数据的变化会驱动视图自动更新,只需要把数据维护好,会自动渲染。

双向数据绑定

在网页中,表单需要采集数据,Ajax负责提交数据

好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值!

MVVM 底层了解

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel

  • Model 表示当前页面渲染时所依赖的数据源。
  • View 表示当前页面所渲染的 DOM 结构。
  • ViewModel 表示 vue 的实例,它是 MVVM 的核心。

ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起。

image.png

  • 当数据源发生变化时,会被 ViewModel 监听到
  • VM 会根据最新的数据源自动更新页面的结构
  • 当表单元素的值发生变化时,也会被 VM 监听到
  • VM 会把变化过后最新的值自动同步到 Model 数据源中

初体验,渲染数据

<body>
<!--希望vue拿到控制这个div,渲染数据-->
<!--固定语法-->
<div id="app">{{username}}</div>

<!--  导入Vue库文件  -->
<script src="js/vue-2.6.12.js"></script>
<!--创建vue的实例对象-->
<script>
    const vm = new Vue({
        // el 固定写法,表示当前vm实例控制页面的那个区域,接收一个选择器
        el: '#app',
        // data 要渲染到页面上的数据
        data: {
            username: "zhangsan"
        }
    })

</script>
</body>

上面什么是view,什么是model,什么是 ViewModel呢

  1. el是 view
  2. data 是model
  3. viewmodel 是构造函数

安装Vue调试工具,打开访问文件URL地址

指令与过滤器

辅助开发者渲染页面的基本结构

内部渲染指令

要先写好数据,用vue语法,一定要在vue控制范围内,比如#app

  • v-test
    1. 会覆盖元素内部原有的内容!
<body>
<div id="app">
    <p v-text="username"></p>
    <p v-text="gender">性别</p>
</div>
</body>
  • {{}}
    1. 插值语法,不会覆盖
<body>
<div id="app">
    <p>名字:{{username}}</p>
    <p>性别:{{gender}}</p>
</div>
</body>
  • v-html
    1. 以上的方式不能渲染标签,只是纯文本
<div id="app">
    <div v-text="info"></div>
    <div v-html="info"></div>
</div>

属性绑定指令

有时候我们需要给属性赋值,就需要 v-bind 指令,可以简写成 :

<div id="app">
    <input type="text" v-bind:placeholder="tips">
    <img :src="imgs" alt="" width="120px">
</div>

<!--  导入Vue库文件  -->
<script src="js/vue-2.6.12.js"></script>
<!--创建vue的实例对象-->
<script>
    const vm = new Vue({
        // el 固定写法,表示当前vm实例控制页面的那个区域,接收一个选择器
        el: '#app',
        // data 要渲染到页面上的数据
        data: {
            tips: "请输入用户名:",
            imgs: "http://localhost:8080/rem/images/logo.png"
        }
    })
  • 那v-bind指令中到底可以写什么呢,其实就是JS代码,运算等
<div id="app" :title="'box' + index">
    <p>{{tips}},反转之后:{{tips.split('').reverse().join('')}}</p>
</div>

v-on 绑定事件

  • 绑定点击事件 v-on:click
<body>
<div id="app">
    <!-- 给按钮添加点击事件 -->
    <button v-on:click="add"></button>
</div>

<script src="js/vue-2.6.12.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        // 这里定义事件的处理函数
        methods: {
            add: function () {
                console.log('ok')
            }
        }
    })
</script>
</body>
  • 简化函数书写
const vm = new Vue({
    el: '#app',
    data: {
        count: 0
    },
    methods: {
        add() {
            console.log('ok')
        }
    }
})
  • v-on: 简化绑定事件书写
<button @click="add(1)">+1</button>
  • 修改 vue 中 data 里面的值
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            count: 0
        },
        methods: {
            add() {
                console.log(this === vm);
                // vm.count += 1; 不推荐
                this.count += 1;
            },
            sub() {
                this.count -= 1;
            },
        }
    })
</script>
  • 如何传参
<div id="app">
    <button v-on:click="add(1)">+1</button>
</div>

注意:原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件
替换为 vue 的事件绑定形式后
分别为:v-on:click、v-on:input、v-on:keyup

怎么获得事件源

方式一:当没有传递任何参数时获得的是事件源,传递则覆盖

<div id="app">
    <button @click="add">+1</button>
</div>

<script src="js/vue-2.6.12.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            count: 0
        },
        methods: {
            // 当没有传递任何参数时获得的是事件源
            add(e) {
                console.log(e)
                this.count += 1
                if (this.count % 2 === 0) {
                    e.target.style.backgroundColor = "red"
                } else {
                    e.target.style.backgroundColor = ""
                }
            }
        }
    })
</script>

方式二:使用内置变量 $event ,获得事件源

<body>
<div id="app">
    <button @click="add(1, $event)">+1</button>
</div>

<script src="js/vue-2.6.12.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            count: 0
        },
        methods: {
            add(n, e) {
                console.log(e)
                this.count += 1
                if (this.count % 2 === 0) {
                    e.target.style.backgroundColor = "red"
                } else {
                    e.target.style.backgroundColor = ""
                }
            }
        }
    })
</script>
</body>

事件修饰符

  1. 阻止默认行为 .prevent
<button @click.prevent="add(1, $event)">+1</button>
  1. 阻止事件冒泡:就是两个事件嵌套,同时触发就是冒泡

触发谁给谁添加

<button @click.stop="sea">按钮</button>

image.png

按键修饰符 keyUp 和 keyDown

<body>
<div id="app">
    <input type="text" @keyup.esc="clear">
</div>

<script src="js/vue-2.6.12.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            count: 0
        },
        methods: {
            clear(e) {
                e.target.value = ''
            }
        }
    })
</script>

双向数据绑定指令

就是双向数据互通,修改任何一方都会改变

使用场景

  1. input 输入框
    • type="radio"
    • type="checkbox"
    • type="xxxx"
  2. textarea
  3. select
<div id="app">
    <p>{{ count }}</p>
    <input type="text" v-model="count">
    <select name="" id="" v-model="count">
        <option value="">请选择:</option>
        <option value="1">1</option>
        <option value="2">2</option>
    </select>
</div>

<script src="js/vue-2.6.12.js"></script>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            count: 0
        }
    })
</script>
</body>

v-model 修饰符

image.png

  • .number 自动将值转为数字
<input type="text" v-model.number="n1">
  • .trim 去除两边空格
<input type="text" v-model.trim="n1" @keyup.enter="submit">
  • .lazy 延迟更新,不会和以前一样,立刻同步数据
<input type="text" v-model.lazy="n1" @keyup.enter="submit">

条件渲染指令

  • v-if 结果为false,会删除元素(频繁切换状态,性能好)
  • v-show 结果为false,会隐藏元素(后期可能不需要展示性能较好)
<div id="box">
    <p v-if="network">v-if控制的</p>
    <p v-show="network">v-show控制的</p>
</div>
  • v-else
<div V-if="type ===A'">优秀</div>
<div V-else-if="type ===B'">良好</div>
<div V-else-if="type === C'">一般</div>
<div V-else></div>

v-for 循环渲染

  • index 表示当前索引,自己也可以访问
<table class="table table-bordered table-hover table-striped">
    <tr v-for="(item, index) in list" :title="item.name">
        <td>{{index}}</td>
        <td>{{ item.id }}</td>
        <td>{{ item.name }}</td>
    </tr>
</table>
  • :key 维护列表状态

当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种 默认的性能优化策略,会导致有状态的列表无法被正确更新。

 <ul>
    <!-- 加 key 属性的好处:
    <!-- 1.正确维护列表的状态 --
    <!-- 2.复用现有的 DOM 元素,提升渲染的性能 -->
    <li V-for="user in userlist" :key="user .id">
        <input type="checkbox” />
        姓名: {{user.name}}
    </li>
</ul>
  1. key 的值只能是字符串或数字类型
  2. key 的值必须具有唯一性(即:key 的值不能重复)
  3. 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
  4. 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)
  5. 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)

总结,技巧

  1. label 的绑定可用插值语法实现循环绑定
  2. 数组的去掉某一项快速
this.list = this.list.filter(item => item.id !== id)
  1. 阻止表单默认行为,并触发事件
<form @submit.prevent="add">
  1. 如果往数组里添加,怎么保持ID一致性。
    • 提前创建一个变量保存着

监听器(watch)

watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。

  • 下面是方法监听器,缺点无法在进入页面的时候就触发一次
  • 如果监听的是对象,那么对象的属性发生变化,不会触发监听器
<script>
    let vue = new Vue({
        el: '#app',
        data: {
            username: ''
        },
        watch: {
            // 第一个是新值,第二个是旧值,每当username这个属性值被修改的时候,就会触发函数
            username(newValue, oldValue) {
                console.log(newValue)
                console.log(oldValue)
            }
        }
    });
</script>

有个需求,希望第一次也触发监听器。

  • 对象监听器,好处进入页面自动触发监听器
  • immediate: true 表示已进入页面就触发一次,默认false
  • deep:true,可以深度监听对象里面属性的变化
<script>
    let vue = new Vue({
        el: '#app',
        data: {
            username: 'admin'
        },
        watch: {
            username: {
                handler(newValue, oldValue) {
                    console.log(newValue)
                    console.log(oldValue)
                },
                // true 表示已进入页面就触发一次,默认false
                immediate: true
            }
        }
    });
</script>

对象监听

<script>
    let vue = new Vue({
        el: '#app',
        data: {
            info: {
                username: 'admin'
            }
        },
        watch: {
            info: {
                handler(newValue, oldValue) {
                    console.log(newValue)
                    console.log(oldValue)
                },
                // 深度监听对象里面的信息,一个改变,就触发
                deep: true
            }
        }
    });
</script>

简化上面的方式

  • 'info.username' 必须用单引号括起来
<script>
    let vue = new Vue({
        el: '#app',
        data: {
            info: {
                username: 'admin'
            }
        },
        watch: {
            'info.username'(newValue, oldValue) {
                    console.log(newValue)
                    console.log(oldValue)
                }
            }
    });
</script>

计算属性(computed)

计算属性指的是通过一系列运算之后,最终得到一个属性值。 这个动态计算出来的属性值可以被模板结构或 methods 方法使用。

  • 声明的时候是方法,但是使用的时候已经转换为属性了。
  • 发生变化会自动随着变化,当作普通属性即可
<!-- 专门用户呈现颜色的 div 盒子 -->
    <div class="box" :style="{ backgroundColor: rgb }">
        {{rgb}}
    </div>
    <button @click="show">按钮</button>
<script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
        el: '#app',
        data: {
            // 红色
            r: 0,
            // 绿色
            g: 0,
            // 蓝色
            b: 0
        },
        methods: {
            // 点击按钮,在终端显示最新的颜色
            show() {
                console.log(this.rgb)
            }
        },
        // 所有计算属性,都要定义到 computed节点下
        // 计算属性在定义的时候,要定义成方法格式
        computed: {
            /// rgb,定义号返回需要的内容
            rgb() {
                return (`rgb(${this.r},${this.g},${this.b})`)
            }
        }
    });
    console.log(vm)
</script>

过滤器(vue3已经砍掉了)

过滤器 (Filters)是 vue 为开发者提供的功能,常用于文本的格式化。

过滤器可以用在两个地方: 插值表达式和 v-bind 属性绑定

<!-- 在双花括号中通过“管道符”调用 capitalize 过滤器,对 message 的值进行格式化 -->
<p>{{ message | capitalize }}</p>
<!-- 在 v-bind 中通过“管道符”调用 formatId 过滤器,对 rawId 的值进行格式化 -->
<div v-bind:id="rawId  formatId"></div>
  • 定义方式
<body>
<div id="app">
    <p>{{ message | cap1 }}</p>
</div>
<script src="vue-2.6.12.js"></script>
<script>
    const vue = new Vue({
        el: "#app",
        data: {
            message: "hello vue!"
        },
        //过滤器函数,必须定义在 filters下
        // 过滤器本质就是函数,有一个形参,就是管道符前面的值
        filters: {
            cap1(val) {
                let first = val.charAt(0).toUpperCase();
                let splice = val.slice(1);
                // 一定有返回值
                return first + splice
            }
        }
    });
</script>
</body>

私有过滤器和全局过滤器

私有过滤器,只能被当前vue控制的标签所解析

如果全局过滤器和私有过滤器名字重复,私有会覆盖全局

推荐只使用全局过滤器

Vue.filter("cap1", function (val) {
    let first = val.charAt(0).toUpperCase();
    let splice = val.slice(1);
    // 一定有返回值
    return first + splice + "`````"
})

格式化时间

<td>{{ item.time | dataFormat }}</td>
Vue.filter("dataFormat", function (val) {
    return dayjs(val).format('YYYY-MM-DD HH:mm:ss')
})

可以连续多调用

<!-- 把 message 的值,交给 filterA 进行处理
<!-- 把 filterA 处理的结果,再交给 filterB 进行处理 -->
<!-- 最终把 filterB 处理的结果,作为最终的值渲染到页面上 -->
{{ message | filterA | filterB )

传参

<!-- arg1 和 arg2 是传递给 filterA 的参数
<p>{{ message filterA(arg1,arg2) }}</p>
//过滤器处理函数的形参列表中:
//第一个参数:永远都是”管道符“前面待处理的值
//从第二个参数开始,才是调用过滤器时传递过来的 arg1 和 arg2 参数
Vue.filter('filterA' , (msg, arg1, arg2) => (
// 过滤器的代码逻辑...
})

axios(发送网络请求)

艾克sei奥斯

并不是服务器返回的真实数据,里面的 .data才是

<script src="lib/axios.js"></script>
<script>
    // 获得Promise对象
    let result = axios({
        method: 'GET',
        url: 'http://localhost:8081/tCustomerLoss/list'
    });
    
    // 获取结果
    result.then(function (data) {
        console.log(data)
    })

</script>

image.png

传递参数

// URL 查询参数
param: {id: 1},
// 请求体参数
data: {}

GET请求

<script>
    // 获得Promise对象
    axios({
        method: 'GET',
        url: 'http://localhost:8081/tCustomerLoss/383',
        param: {
            id: 1
        }
    }).then(function (data) {
        console.log(data.data)
    })

</script>

POST请求

<body>

<div id="app">
    <button id="btn">post请求</button>
</div>
<script src="lib/axios.js"></script>
<script src="lib/dayjs.min.js"></script>
<script src="lib/vue-2.6.12.js"></script>
<script>
    document.querySelector('#btn').addEventListener('click', async function () {
        // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
        // await 只能用在被 async “修饰”的方法中
        const res = await axios({
            method: 'POST',
            url: 'http://www.liulongbin.top:3006/api/post',
            data: {
                name: 'zs',
                age: 20
            }
        });
        console.log(res)
    })


</script>
</body>

简化上面的,直接获取data对象(解构赋值)

document.querySelector('#btn').addEventListener('click', async function () {
    // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
    // await 只能用在被 async “修饰”的方法中
    const {data} = await axios({
        method: 'POST',
        url: 'http://www.liulongbin.top:3006/api/post',
        data: {
            name: 'zs',
            age: 20
        }
    });
    console.log(data)
})

重命名

把解构出来的 data 属性,使用冒号进行重命名,一般都重名命为 {data: res}
const {data : res} = await axios({

快速发起请求

<div id="app">
    <button id="btnPost">post请求</button>
    <button id="btnGet">get请求</button>
</div>
<script src="lib/axios.js"></script>
<script>
    document.querySelector('#btnPost').addEventListener('click', async function () {
        // post直接写
        const {data: res} = await axios.post("http://www.liulongbin.top:3006/api/post", {name: 'zs', gender: "女"})
        console.log(res)
    })
    // get需要加param
    document.querySelector("#btnGet").addEventListener('click', async function () {
        const {data: res} = await axios.get("http://www.liulongbin.top:3006/api/getbooks", {
            param: {id: 1}
        })
        console.log(res)
    })

</script>

Vue-cli

简化了程序员基于webpack创建工程化的Vue项目过程

单页面程序

什么是单页面应用程序

指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能 与交互都在这唯一的一个页面内完成

安装 vue -cli

npm install -g @vue/cli

创建项目

vue create 项目的名称

项目搭建

还可以使用vue ui命令创建,效果一样是可视化的

  1. 选择模式

第一个vue2,第二个vue3,第三个自定义(推荐)

image.png

  1. 选择功能
    • 第一个是选择vue版本(必选)
    • 第二个是Babel,解决js兼容性(必选)
    • 第三个是微软脚本语言,比js强大,是ts
    • 第七个是less,用less写
    • 第八个是约束团队编写风格,比如:喜欢单引号或者双引号(不推荐)
    • 后面是测试工具

image.png

  1. 选择vue版本

image.png

  1. 选择预处理器(常用less)

image.png

  1. 选择插件配置文件存放位置
  • 每个插件是独立存放,还是和package.json放一起,选独立

image.png

  1. 是否保存,刚刚选择配置的内容,根据自己需求

运行项目 / 目录介绍

npn run serve

image.png

  • node_modules 项目源代码
  • public
    • favicon.ico 图标
    • index.html 主文件
  • src 源代码目录
    • assets 静态资源,图片,样式表
    • components 程序员封装的可复用的组件,放这里
  • main.js 入口,整个项目运行前执行main.js文件
  • app.vue 是项目的根组件

vue项目运行流程

在工程化的项目中,vue 通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。

  1. App.vue 用来编写待渲染的模板结构
  2. index.html 中需要预留一个 el 区域
  3. main.js 把 App.vue 渲染到了 index.html 所预留的区域中

导入vue包获得vue构造函数
import Vue from 'vue'

new Vue({
  // 渲染的谁谁就是根组件且唯一,下面是APP
  render: h => h(App),
  //和el指定一样
}).$mount('#app')

每个 .vue 组件都由 3 部分构成,分别是:

  • template -> 组件的模板结构
  • script -> 组件的 JavaScript 行为
  • style -> 组件的样式
// 定义结构
<template>
  <div>
    <div class="test-box">
      <h3>这是用户自定义的 Test.vue --- {{ username }}</h3>
      <button @click="chagneName">修改用户名</button>
    </div>
    <div>123</div>
  </div>
</template>

// 定义行为
<script>
// 默认导出。这是固定写法!
export default {
  // data 数据源
  // 注意:.vue 组件中的 data 不能像之前一样,不能指向对象。
  // 注意:组件中的 data 必须是一个函数
  data() {
    // 这个 return 出去的 { } 中,可以定义数据
    return {
      username: 'admin'
    }
  },
  methods: {
    chagneName() {
      // 在组件中, this 就表示当前组件的实例对象
      console.log(this)
      this.username = '哇哈哈'
    }
  },
  // 当前组件中的侦听器
  watch: {},
  // 当前组件中的计算属性
  computed: {},
  // 当前组件中的过滤器
  filters: {}
}
</script>

//定义样式
<style lang="less">
.test-box {
  background-color: pink;
  h3 {
    color: red;
  }
}
</style>

组件之间的父子关系

组件只在使用时会产生父子兄弟关系

  1. 导入需要的组件
// 导入需要的组件
import Left from '@/components/Left'
  1. 使用components 节点注册组件
components: {
  // "Left": Left, 可以简写
  Left // 简写
}
  1. 以标签的形式使用组件
<div class="box">
  <!-- 渲染 Left 组件和 Right 组件 -->
  <left></left>
</div>

形成了父子关系,通过components注册的组件是私有组件(非常麻烦,不利于扩展)

注意:在A组件中注册的组件,只能在A中使用,不能在组件C中使用

注册全局组件

// 注册全局组件
import Count from "@/components/Count";
// 载入组件
Vue.component('MyCount', Count)

然后直接标签使用即可,无法自己使用自己组件,会抛出异常

props 自定义属性,只读,用于组件设置初始值

父组件向子组件传入9,子组件属性中就可以直接使用

export default {
  props: ["init"],
}

直接传递,传递过去的是字符串,传数值,需要加:使用v-bind形式

<MyCount :init="9"></MyCount>

那么props的值是可以被修改的吗?答案是不能的,它是只读的,修改会抛出异常,但是可能会影响功能。会抛出异常,下面存入count中就可以了

data() {
  return {
    // 把 props 中的 init 值,转存到 count 上
    count: this.init
  }
},

上面这种方式啊,如果子组件没有传递,那就是undefined的了,没有默认值,设置默认值是这样的。

type:类型, 规定传递的数据必须是指定类型,否则抛出异常

props: {
  // 属性
  init: {
    // 参数 default 默认值
    default: 0,
    // 规定传递的数据必须是指定类型,否则抛出异常
    type: Number // Boolean,Array,Object,String
  }
},

required:必填项,和默认值无关系,没传就是报错

props: {
  // 属性
  init: {
    // 要求使用该组件,必须传递该数据
    required: true
  }
},

对象类型指定默认值

cover: {
  type: Object,
  default: function () {
    return { type: 0 }
  }
}

组件之间样式冲突

指在A页面修改了样式,影响到了B页面的样式

原因:vue中组件的样式会全局生效的,只有唯一一个index.html,最终都会到一个页面中。

解决方案:需要加一个特定的属性

<div class="left-container" v-data-001>

解决方案2:给style加一个属性scoped

Vue 官方已宣布 /deep/ 已被遗弃,我们使用了 ::v-deep 选择器来向下深度覆盖样式

::v-deep .el-table .cell {
  white-space: nowrap;
}
<style lang="less" scoped>

父组件的子组件不会和父组件在同一个空间,导致子组件的样式不会生效,怎么解决?

/deep/ h5 {
  color: pink;
}

这个deep 表示父组件的 v-data-ID,所以这个就会生效,包含该ID后代的H5

使用场景:当使用第三方组件库的时候,如果有需要修改默认样式的要求,需要用到/deep/

vue-template-compiler 包

它会把每个vue文件转换成js文件,交给浏览器去执行

组件的生命周期

以标签形式,使用到了一个组件,就会创建了一个实例

  • 生命周期:创建 -> 运行 -> 销毁的整个阶段
  • 生命周期函数:由vue框架提供内置函数,会伴随着组件的生命周期,自动按次序执行

image.png

vue生命周期运行流程

  1. webpack 从main.js 开始打包,发现用到了app,把App创建一个实例出来
  2. App里面用到的组件一直,webpack把这些组件全部解析成js文件
  3. 生成的文件被浏览器解析执行,渲染到页面上

下面这个图,就是创建一个实例的整个过程

image.png

lifecycle.png

beforeCreate 第一个生命周期函数(不重要)

初始化事件和生命周期函数

<script>
export default {
  // 第一个生命周期函数
  beforeCreate() {
    // 这个阶段的属性还没有被初始化,无法调用,会抛出异常
    console.log(this.info);
    // 这个message是 undefined
    console.log(this.message);
    // 这个阶段的属性还没有被初始化,无法调用,会抛出异常
    this.show()
  }
}
</script>

created 初始化数据的函数(重要)

这个阶段可以发起ajax发起请求拿数据,经常用它调用methods的方法,请求服务器拿数据,转存data中,供template模板渲染使用

  • 注意这个时候不能操作dom元素,拿到的全是null,因为结构还没有渲染
created() {
  // 都可以正常输出
  console.log(this.info);
  console.log(this.message);
  this.show()
}

发送Ajax请求

<template>
  <div class="controller">Test 测试----{{ books.length }} 本图书</div>
</template>

<script>
export default {
  data() {
    return {
      // 定义空数组,存储图书
      books: []
    }
  },
  methods: {
    // 发送ajax请求,拿数据
    initBookList() {
      const request = new XMLHttpRequest();
      request.addEventListener('load', () => {
        const parse = JSON.parse(request.responseText);
        this.books = parse.data
        console.log(parse)
      })
      request.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
      request.send()
    }
  },
  // 初始化数据
  created() {
    this.initBookList()
  },
}
</script>

渲染模板

首先判断vue构造函数中,有没有el属性,有的话就就使用,没有就初始化一个,然后继续判断有没有template模板,有的话就基于模板,初始化结构,没有使用默认的。

就是基于数据和模板在内存中编译生成HTML结构

beforeMount 准备渲染界面(不重要)

还没有渲染,即将渲染,打印的是null

beforeMount() {
  const element = document.querySelector("#myh3");
  console.log(element)
}

mounted 可以操作dom(重要)

// 完成了HTML结构的渲染,此时可以操作dom了
mounted() {
  const element = document.querySelector("#myh3");
  console.log(element)
}

组件运行阶段

beforeUpdate(data中数据每更新一次都会触发),数据是最新的,但是结构是旧的

第一次渲染也会触发,数据是最新的了,但是结构还没有重新渲染

// 每次数据发送改变会执行这个函数
  beforeUpdate() {
    console.log("beforeUpdate")
  }

updated(操作最新的元素使用)

数据是最新的,结构也是重新渲染之后的。都是最新的,这是一个圈,他们只要数据变化了,每一个函数都会被执行一次,最多n次最少0次

组件传值,数据共享

最常见关系:父子关系,兄弟关系

父向子传值(值都不建议修改)

普通值:是深复制,对象:是浅拷贝 父组件

<left :user="user"></left>

子组件

props: ['user'],

子向父传值

使用的自定义事件

子组件

methods: {
  add() {
    this.count += 1
    // 将count传递给父组件
    this.$emit('numchange', this.count)
  }
}

父组件

<template>
  <div class="app-container">
    <div class="box">
      // 第一步先定义,这个名称要和子组件传递的key一致
      <Right @numchange="getNewCount"></Right>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      countFromSon: null
    }
  },
  // 定义方法拿到值
  methods: {
    getNewCount(val) {
      this.countFromSon = val
    }
  }
}
</script>

兄弟之间互相传值

在vue2中用的是EventBus

image.png

  1. 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
  2. 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件
  3. 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件

发送方组件

<template>
  <div class="left-container">
    {{ message }}
    <button @click="send">发送好诗</button>
  </div>
</template>

<script>
// 导入eventBus.js 模板
import bus from './eventBus'

export default {
  data() {
    return {
      // 把这个发送给right组件
      message: '黑云压城城欲摧'
    }
  },
  // 发送
  methods: {
    send() {
      // 这个key值一定保持一致
      bus.$emit('share', this.message)
    }
  }
}
</script>

接收方

<template>
  <div class="right-container">
    <p>{{ message }}</p>
  </div>
</template>

<script>
// 导入 eventBus.js 模块
import bus from './eventBus'

export default {
  data() {
    return {
      message: ''
    }
  },
  created() {
    // 为bus绑定自定义事件,val就是数据
    bus.$on('share', (val) => {
      console.log('share被触发勒' + val)
      this.message = val
    })
  }
}
</script>

eventBus.js 文件

import Vue from 'vue'
// 共享函数
export default new Vue

传值2 provide 组件向子孙组件传值

实现响应式的方式,在data中定义对象,将对象赋值给 provide 中,修改对象子孙组件的值将同步修改,非对象不可以

父组件

// 这是一个 Vue.js 组件的配置对象
export default {
    // 定义该组件的名称为 "App",通常用于开发和调试目的
    name: 'App',

    // 使用 provide 选项向子孙组件提供数据或函数
    provide() {
        return {
            // 提供一个名为 'color' 的数据,设置为 'pink'
            color: 'pink',
            // 提供一个名为 'userinfo' 的数据,包含 'name' 和 'age' 属性
            userinfo: {
                name: 'zs',
                age: 18
            }
        }
    }
}

子孙组件

// 这是一个 Vue.js 组件的配置对象
export default {
    // 定义该组件的名称为 "HelloWorld",通常用于开发和调试目的
    name: 'HelloWorld',

    // 使用 inject 选项来接收父组件提供的数据
    // 在这里,我们接收了两个属性:'color' 和 'userinfo'
    inject: ['color', 'userinfo']
}

父组件调用子组件的方法

先加 ref

<component ref="child"></component>

触发方法

const series = this.$refs.child.方法名(参数)

ref 引用(操作dom)

不依赖jquery操作dom,每一个组件实例上,都包含一个refs对象,里面存着对应的dom元素引用,默认refs对象,里面存着对应的dom元素引用,默认refs指向空对象

获取dom

<h1 ref="myh1">App 根组件</h1>
methods: {
  showThis() {
    // 获取上面的dom
    console.log(this.$refs.myh1)
  }
},

还可以引用实例

<Left ref="comLeft"></Left>
refresh() {
  // 获取实例并将实例的属性值设置为0
  this.$refs.comLeft.count = 0
}

监听事件

blur:当失去焦点的时候,触发函数

<input type="text" @blur="showButton" v-else/>

延迟执行 & 自动获取焦点

延迟到页面重新渲染时执行

  • this.$nextTick 延迟执行函数
this.$nextTick(() => {
    // 获取dom为input并且自动获取焦点focus
    this.$refs.inputRef.focus()
})

some循环 & forEach 循环 & every循环

forEach 是不能终止循环的

const str = ['小红','大红','苏大强', '宝'];
str.forEach((item, index) => {
    console.log(item)
    if(item === '苏大强'){
        console.log(index)
    }
})

some 循环可以终止循环

const str = ['小红','大红','苏大强', '宝'];
str.some((item, index) => {
    console.log(item)
    if(item === '苏大强'){
        console.log(index)
        return true;
    }
})

every 循环,都满足条件为true

<script>
    const arr = [
        {id: 1, name: '西瓜', state: true},
        {id: 2, name: '榴莲', state: false},
        {id: 3, name: '草莓', state: true},
    ];
    const every = arr.every(item => item.state);
    console.log(every) // false
</script>

reduce 循环

  • reduce 参数1:累加的数量,默认是0,后面设置的
  • 参数2,循环的每一个列
const arr = [
    {id: 1, name: '西瓜', state: true, price: 10, count: 1},
    {id: 2, name: '榴莲', state: false, price: 80, count: 2},
    {id: 3, name: '草莓', state: true, price: 20, count: 3},
];
const reduce = arr.filter(item => item.state).reduce((amt, item) => {
    return amt += item.price * item.count
}, 0);
// 简化
const reduce1 = arr.filter(item => item.state).reduce((amt, item) => amt + item.price * item.count, 0);
console.log(reduce1)

两个数组合并,将this.list数组放前面,res数组放后面合并为新数组

this.list = [...this.list, ...res]

常见问题

  1. 分析父子组件传值,封装成对象传递和单传递的优缺点
    绑定对象就形成强绑定了,不灵活了。不是很建议,建议单个传递
  1. 点击第三个组件,选中的还是第一个组件解决方案?
<input type="checkbox" class="custom-control-input" id="cb1" :checked="state"/>
<label class="custom-control-label" for="cb1">
  <!-- 商品的缩略图 -->
  <img :src="pic" alt=""/>
</label>

那是因为上面的input和label标签的id和for,绑定的永远是一个名称

@change 发生改变触发函数

@change="changeStatus"

动态组件(is)

动态切换组件的显示与隐藏

<!-- 动态渲染组件 用is指定渲染的组件 -->
<component :is="comName"></component>

动态切换组件导致数据丢失怎么解决?
因为生命周期问题,当我们切换组件时会销毁并初始化一个组件所以数据丢失

创建不被销毁的动态组件,被括起来的组件会一直被缓存

生命周期,缓存&激活(监听谁写给谁)

当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。

当组件被激活时,会自动触发组件的 activated 生命周期函数。

<!-- keep alive包含住的组件不被销毁 -->
<keep-alive>
  <component :is="comName"></component>
</keep-alive>
export default {
  deactivated() {
    console.log("组件被缓存了")
  },
  activated() {
    console.log("组件被激活了")
  }
}

指定缓存的组件(include)

include 指定缓存的组件,或者exclude指定哪些组件不被缓存,无法同时使用

<keep-alive include="Left,Right">
  <component :is="comName"></component>
</keep-alive>

给组件起名

所以名称要保持一致,建议给组件都设置名称

  • 主要应用场景:keepalive组件缓存,调试
export default {
  // 起名,在调试的时候就会显示该名称,在一些指定名称中的属性,也需要被更改
  name: 'MyLeft',

插槽(slot)

就是在引用组件时,组件中有部分并不确定,希望灵活使用,传入什么,渲染什么

App 主组件

<Left>
  <p>这是用户声明的P标签</p>
</Left>

Left 子组件

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <slot></slot>
  </div>
</template>
  • 属性name:默认default,规范是每一个插槽都要有name名称
  • 默认情况下,使用的组件默认进入default插槽中

如何指定内容到指定插槽中

  1. v-slot:必须用在template标签上
  2. 使用v-slot 指定
  3. template是虚拟标签,不会被渲染到页面上,如果不是组件才需要包,是则不需要
  4. v-slot 简写是#号
<Left>
  <template v-slot:default>
    <p>这是用户声明的P标签</p>
  </template>
</Left>

设置默认内容,如果用户指定了会被覆盖(官方:后备内容)

<slot name="default">
    <!-- 在这里面写就可以 -->
</slot>

插槽的使用

插槽定义

<template>
  <div class="artclue-container">
    <div class="header">
      <slot name="header"></slot>
    </div>
    <div class="content">
      <slot name="content"></slot>
    </div>
    <div class="footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

插槽使用

<Artclue>
  <template #header>
    <p>头部是这里</p>
  </template>
  <template #content>
    <p>这里是内容</p>
  </template>
  <template #footer>
    <p>末尾啦</p>
  </template>
</Artclue>

插槽传值

没有值就是空对象,会的到自己定义的数据(官方:叫做作用域插槽)

  • scope:标准
<div class="footer">
  <slot name="footer" msg="hello"></slot>
</div>
<template #footer="scope">
  <p>末尾啦</p>
  <p>{{ obj }}</p>
</template>

自定义指令

使用时加上v-这是固定写法

<h1 v-color>App 根组件</h1>
<p v-color="'red'">猜猜我是谁</p>
export default {
  directives: {
    // color 指令,当刚绑定的时候,出发bind函数
    color: {
      // 触发bind指令. 形参el表示被绑定的dom元素
      bind(el){
        console.log("已经触发" + el)
        el.style.color = 'red'
      }
    }
  }
}
  • v-color 自己定义的指令
  • color 这是个自定义的变量

对象中两个重要的:expression和value,前者是变量名称,后者是变量值

<p v-color="color">头部是这里</p>

update函数

由于bind函数只在被绑定的时候执行一次,不会更新内容

update函数当每次dom元素被改变,都会执行一次

export default {
  data() {
    return {
      color: 'red'
    }
  },
  directives: {
    // color 指令,当刚绑定的时候,出发bind函数
    color: {
      // 触发bind指令. 形参el表示被绑定的dom元素
      bind(el, binding) {
        console.log(binding)
        el.style.color = binding.value
      },
      update(el, binding) {
        // console.log(binding)
        el.style.color = binding.value
      }
    }
  }
}

简化以上,当bind和update中的逻辑一致,可以直接简写一下方式

export default {
  directives: {
    // color 指令,当刚绑定的时候,出发bind函数
    color(el, binding) {
      console.log(binding)
      el.style.color = binding.value
    }
  }
}

全局自定义指令

只要是全局指令都需要在main.js中定义

// 原始用法
Vue.directive('color', {
    bind(el, binding) {
        el.style.color = binding.value
    },
    update(el, binding) {
        el.style.color = binding.value
    }
})
// 简化
Vue.directive('color', function (el, binding) {
    el.style.color = binding.value
})

main.js 小提示

  • true:发布式要打开生产模式,起到提示作用
  • false:关闭,也就是不提示
Vue.config.productionTip = true

优化axios

下面这个每个模块都需要导入,所以很麻烦

<script>
import axios from 'axios'

export default {
  methods: {
    async getInfo () {
      const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/getbooks')
      console.log(res)
    }
  }
}
</script>

因为组件之间都需要导包,所以可以在main.js中导入

在main.js中

// 交给vue原型
Vue.prototype.$http = axios

使用

export default {
  methods: {
    async getInfo () {
      const { data: res } = await this.$http.get('http://www.liulongbin.top:3006/api/getbooks')
      console.log(res)
    }
  }
}

发现这样端口这块重复太多了

定义根路径

// 交给vue原型,定义根路径
axios.defaults.baseURL = 'http://www.liulongbin.top:3006'
Vue.prototype.$http = axios
  • 缺点:不利于复用

封装axios

假设有三个服务器,那就封装三个axios,实现复用

  1. 在src下创建utils文件夹,下的request.js文件
import axios from 'axios'

const request = axios.create({
  // 指定请求的根路径
  baseURL: ''
})

export default request

封装接口api模块,建立api 模块,为每个需求,建立不同的js文件(下面实例)
这样就可以直接使用了

import request from '@/utils/request'

export const getArticleList = function (_page, _limit) {
  return request.get('/articles', {
    params: {
      _page,
      _limit
    }
  })
}

就可以直接调用了

methods: {
  async initList () {
    const { data: res } = await getArticleList(this.page, this.limit)
    console.log(res)
  }
}

delete请求

export function delSysUser(params) {
  return request.delete('/index/delSysUser', {
    params: {
      id: params.id
    }
  })
}

配置拦截器加cookie

在request中使用

const request = axios.create({
  baseURL: 'http://localhost:82'
})
request.interceptors.request.use(
  config => {
    config.headers.token = `Bearer ${localStorage.getItem('token')}`
    return config
  }
)

路由(权限控制)

路由就是 hash地址与组件之间的关系(锚链接不会导致页面刷新)

前端路由工作方式

  1. 用户点击了页面上的路由链接
  2. 导致了 URL 地址栏中的 Hash 值发生了变化
  3. 前端路由监听了到 Hash 地址的变化
  4. 前端路由把当前 Hash 地址对应的组件渲染到浏览器中

image.png 结论:前端路由,指的是 Hash 地址与组件之间的对应关系!

简易路由

  • window.onhashchange:监听地址栏变化
  • location.hash:获取变化的地址
<template>
  <div class="app-container">
    <h1>App 根组件</h1>

    <a href="#/Home">首页</a>
    <a href="#/Move">电影</a>
    <a href="#/About">关于</a>
    <hr/>

    <component :is="Name"></component>
  </div>
</template>

<script>
// 导入组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'

export default {
  name: 'App',
  // 注册组件
  components: {
    Home,
    Movie,
    About
  },
  data() {
    return {
      Name: 'Home'
    }
  },
  // 只要app一创建,就监听window对象的onhashchange事件
  created() {
    window.onhashchange = () => {
      // console.log("监听到的hash地址变化" + location.hash)
      let hash = location.hash;
      switch (hash) {
        case '#/Home':
          this.Name = 'Home';
          break
        case '#/Move':
          this.Name = 'Movie';
          break
        case '#/About':
          this.Name = 'About';
          break
      }
    }
  }
}
</script>

<style lang="less" scoped>
.app-container {
  background-color: #efefef;
  overflow: hidden;
  margin: 10px;
  padding: 15px;

  > a {
    margin-right: 10px;
  }
}
</style>

vue 路由的安装与配置

只能在 vue 项目中使用

  1. 安装 vue 路由
npm i vue-router@3.5.2 -S
  1. 创建路由模块,在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码
// 导入Vue和VueRouter的包
import Vue from "vue";
import VueRouter from "vue-router";

// 调用vue.use函数,把VueRouter安装为vue插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter();

// 向外共享路由实例对象
export default router
  1. 打开main.js 建立main.js和router下的js文件建立到一起,简称挂载
// 这有一个细节点,下面用的是完整路径
// 如果给到的不是具体的文件,则默认导入文件夹下index.js的文件,基于模块化
import router from "@/router/index";

new Vue({
  // 因为变量名和值都是一样的,可以简写,挂载vue实例对象
  router
})

vue路由的基本使用

<!-- 不建议使用a链接,建议用下面的方式 -->

<router-link to="/home">首页</router-link>

<a href="#/home">首页</a>
<a href="#/move">电影</a>
<a href="#/about">关于</a>

<!--安装了VueRouter就可以使用这个组件-->
<!-- 表示占位符,等待被渲染 -->
<router-view></router-view>

配置路由与hash的关系----在router/index关系中设置

import Home from "@/components/Home";
import Movie from "@/components/Movie";
import about from "@/components/About";

// 创建路由的实例对象
const router = new VueRouter({
    // 下面叫路由规则
    routes: [
        // 表示空页面将会跳转到home,这里必须是路径,组件会无效
        {path: "/", redirect: '/home'},
        // path:表示hash地址,component表示组件
        {path: "/home", component: Home},
        {path: '/movie', component: Movie},
        {path: '/about', component: about}
    ]
});

路由嵌套

image.png

也就是继续在子组件中,继续写子组件路由

// 创建路由的实例对象
const router = new VueRouter({
    routes: [
        // 表示空页面将会跳转到home
        {path: "/", redirect: '/home'},
        // path:表示hash地址,component表示组件
        {path: "/home", component: Home},

        {path: '/movie', component: Movie},
        {
            path: '/about', component: about,
            // 设置默认子路由
            redirect: '/about/tab1',
            // 定义子路由
            children: [
                {path: 'tab1', component: Tab1},
                {path: 'tab2', component: Tab2},
            ]
        }
    ]
});

默认子路由还可以除了redirect,还可以使用以下方式

// 定义子路由
children: [
    // 数组中,空字符串路径表示默认路由
    {path: '', component: tab1},
    {path: 'tab1', component: Tab1},
    {path: 'tab2', component: Tab2},
]

动态路由

就是把hash地址中可变的部分定义为参数项,提高路由可复用性

后面定义为可变参数

<!--后面是路径参数,通过$route.params访问-->
<router-link to="/movie/3">洛基</router-link>
<router-link to="/movie/1">雷神</router-link>
<router-link to="/movie/2">妇联</router-link>

路由配置为:id,名字随意起

{path: '/movie/:id', component: Movie},

拿到router-link to参数,在路由中定义

{{ $route.params.id }

方式二

开启props 传参

{path: '/movie/:id', component: Movie, props: true},

这个id要和上面对应,写的data() 中,然后就可以直接使用id了

props: ['id'],

获取查询参数,上面是路径参数

this.$route.query.

注意:在this.$route中path只是路径不包含查询参数,fullPath是完整地址

制作导航

  • 在浏览器中,点击a链接实现导航,叫做声明式导航,vue也是
  • 调用api方式实现导航叫做编程式导航,就是location.href属于编程式

this.$router 是导航对象,this.$route 是参数对象

编程式跳转方式

  • push方法特点:会增加一条历史记录,可以回到刚刚的页面
  • replace:不会产生历史记录
  • go:后退一步或者前进一步(-1,1)
    • 后退:this.$router.back()
    • 前进:this.$router.forward()
export default {
  name: 'Home',
  methods: {
    gotoLk() {
      // hash 地址,产生历史记录
      this.$router.push("/movie/1")
      // hash 地址,不产生历史记录
      this.$router.replace("/movie/1")
      // 有历史记录才会跳转,如果超过上限会原地不动
      this.$router.go(-1)
    }
  }
}

导航守卫

全局前置守卫:每次路由跳转时,都会触发全局前置守卫。

如果只是声明了对象,什么也没写,表示不会进行跳转

  • from:将要离开的路由对象
  • to:将要访问的路由对象
  • next:表示放行
//为router对象,声明全局前置导航守卫
//只要发生了路由的跳转,必然会触发 beforeEach 指定的function回调函数
router.beforeEach(function (to, from, next) {
    // from 将要离开的路由对象
    console.log(from);
    // to 将要访问的路由对象
    console.log(to);
    // next 表示放行
    next()
})

next 的三种跳转方式

  1. 当用户拥有后台主页的访问权限,直接放行:next()
  2. 当前用户没有后台主页的访问权限,强制跳转到登录页面:next('/login') 括号里是hash地址
  3. 当前用户没有后台主页的访问权限,不允许跳转后台主页,next(false)
  • to.path: 将要访问的路径

判断是否登录案例

//为router对象,声明全局前置导航守卫
//只要发生了路由的跳转,必然会触发 beforeEach 指定的function回调函数
router.beforeEach(function (to, from, next) {
    // 拿到用户要访问的hash地址
    // 如果是main,则需要登录,否则不需要(直接放行)
    // 如果是main,则拿到token值,如果有则放行,没有跳到登录页面
    if(to.path === '/main'){
        // 获取页面上的cookie
        let item = localStorage.getItem('token');
        if(item) next()
        else next('/login')
    }else {
        next()
    }
})

获取页面上cookie

// 获取页面上的cookie
let item = localStorage.getItem('token');

token 认证

规范:必须以Bearer 开头

配置vant移动端组件库

安装vant

# Vue 3 项目,安装最新版 Vant: 
npm i vant -S 
# Vue 2 项目,安装 Vant 2: 
npm i vant@latest-v2 -S

导入want:使用方式三一次性导入所有vant组件,上线时可抽取掉vant。(最好)

在 main.js 中导入

import Vant from 'vant'
import 'vant/lib/index.css'
Vue.use(Vant)

接下来就可以直接使用vant组件了

直接使用标签语法即可

image.png

按需引入 want

依赖

npm i babel-plugin-import -D
npm i vant@latest-v2 -S

在 babel.config.js 中

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  // 添加到这里即可
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
}

创建 JS 文件,在main.js 中 引入该组件,在次js文件中引入组件

移动端适配 vh vh

依赖

npm i postcss-px-to-viewport -S

在根目录创建 postcss.config.js 文件

// postcss.config.js
module.exports = {
  plugins: {
    'postcss-px-to-viewport': {
      // vw 适配标准屏的宽度,设计图的一倍图,比例:设计图750 适配 375
      viewportWidth: 375
    }
  }
}

导航栏组件库

导航栏路由

<!-- 预留空间-->
<router-view></router-view>
<!--  定制路由  -->
<van-tabbar route>
  <van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
  <van-tabbar-item icon="user-o" to="/user">我的</van-tabbar-item>
</van-tabbar>

固定区域,上导航栏,返回前进按钮

<van-nav-bar
  title="标题"
  fixed // 固定住默认false
  left-text="返回"
  right-text="按钮"
  left-arrow // 左侧箭头是否显示
  @click-left="onClickLeft"
  @click-right="onClickRight"
/>

覆盖第三方样式的时候不生效,就要加/deep/

下拉更新数据,中间放需要更新的数据

  • loading:是否需要更新,为true不会反复触发load事件 // 声明 true,create函数触发后要改为false
  • @load:更新函数 // 创建函数
  • finished:是否加载了所有数据,没有应该为false // 声明 false
<van-list
  v-model="loading"
  :finished="finished"
  finished-text="没有更多了"
  @load="onLoad"
>


</van-list>

我们应该往上滑才触发,刚进来不需要触发,所以需要将loading改为false

实现流程

  1. 渲染的组件
<van-list
  v-model="loading"
  :finished="finished"
  finished-text="没有更多了"
  @load="onLoad"
>
  <article-info v-for="item in list" :key="item.id" :title="item.title" :aut-name="item.aut_name"
                :comm-count="item.comm_count" :pubdate="item.pubdate" :cover="item.cover"></article-info>
</van-list>
  1. js
export default {
  name: 'Home',
  components: { ArticleInfo },
  data () {
    return {
      list: [], // 数据容器
      loading: true, // 数据是否反复家长,true不加载
      finished: false, //  数据是否全部加载完毕
      page: 1,
      limit: 10
    }
  },
  created () {
    this.initList()
  },
  methods: {
    async initList () {
      const { data: res } = await getArticleList(this.page, this.limit)
      // 合并两个数组
      this.list = [...this.list, ...res]
      // 加载完改false
      this.loading = false
      // 判断是否全部加载完毕
      if (res.length === 0) this.finished = true
    },
    // 加载数据
    onLoad () {
      this.page++
      this.initList()
    }
  }
}
</script>

下拉追加数据,应该把第一页的数据重置为下一页的数据,也就是在数组前面加

  • disabled 表示没有最新数据了
<van-pull-refresh v-model="refreshing" :disabled="finished" @refresh="onRefresh">
export default {
  name: 'Home',
  components: { ArticleInfo },
  data () {
    return {
      list: [],
      loading: true,
      finished: false,
      refreshing: false, // 是否正在下拉刷新
      page: 1,
      limit: 10
    }
  },
  created () {
    this.initList()
  },
  methods: {
    async initList (isRefresh) {
      const { data: res } = await getArticleList(this.page, this.limit)‘
      // 如果数据是下拉刷新,就进入下面,
      if (isRefresh) {
       // 将新数据放前面
        this.list = [...res, ...this.list]
        // 数据更新完毕关闭刷新
        this.refreshing = false
      } else {
        this.list = [...this.list, ...res]
        this.loading = false
      }
      if (res.length === 0) this.finished = true
    },
    onLoad () {
      this.page++
      this.initList()
    },
    onRefresh () {
      this.page++
      this.initList(true)
    }
  }
}
</script>

定制主题

  1. 先把引入的index.css改成index.less
  2. 根目录创建vue.config
  3. 里面的样式表,是在vant需要的组件中查找

优缺点:需要重启,才能生效,不建议使用

module.exports = {
  css: {
    loaderOptions: {
      less: {
        // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
        lessOptions: {
          modifyVars: {
            // 直接覆盖变量
            'text-color': '#111',
            'border-color': '#eee'
            // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
            // hack: 'true; @import "your-less-file-path.less";',
          }
        }
      }
    }
  }
}

less定制主题

  1. 根目录创建vue.config,里面写
const path = require('path')
// 获取文件的绝对路径
const themePath = path.join(__dirname, '/src/theme.less')

// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          // 或者可以通过 less 文件覆盖(文件路径为绝对路径),赋值绝对路径
          hack: `true; @import "${themePath}";`
        }
      }
    }
  }
}
  1. 新建less
  2. 填写变量
@blue: #007bff;
// 下面的类名是vant提供的
@nav-bar-background-color: @blue;

默认打包后无法通过file协议进行打开

因为只支持http协议不支持file协议需要修改配置

module.exports = {
  // 改为空就可以了
  publicPath: '',

lodash中文文档可以提供了很多实用的api

修改默认端口号

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 7000
  }
})

Element ui 组件库

安装element UI

npm i element-ui -S

在 main.js 中引入组件

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)

可以直接复制代码使用了

分页

<!--  分页  -->
<el-pagination
  background
   :current-page="formInline.page" // 当前页码
   :page-size="formInline.limit"   // 当前每页数量
   :page-sizes="[10, 20, 30, 40, 50, 100]" // 切换每页的数量
  layout="prev, pager, next, total, jumper, sizes"
  @size-change="handleSizeChange" // 每页数据
  @current-change="handleCurrentChange" // 页面显示数据
  :total="1000">
</el-pagination>
methods: {
  // 切换每一页多少数据
  handleSizeChange (val) {
    console.log(val)
  },
  // 跳转页面
  handleCurrentChange (val) {
    console.log(val)
  }
}

利用插槽获取这一行的数据---row 就是这一行的数据对象

<template #default="scope">
  {{ scope.row.gender === 1 ? '男' : '女' }}
</template>

表单校验

校验规则

loginRules: {
  username: [{
    required: true,
    trigger: 'blur',
    message: '请输入您的账号'
  }],
  password: [{
    required: true,
    trigger: 'blur',
    message: '请输入您的密码'
  }]
}

提交时校验

handleLogin (loginForm) {
    // loginForm 这里的名字要和ref后面的名字一样
  this.$refs[loginForm].validate(async (valid) => {
    if (valid) {
      const { data: res } = await Login(this.loginForm)
      console.log(res)
    } else {
      return false
    }
  })
}