# SPH-project **Repository Path**: ouzai-zai/sph-project ## Basic Information - **Project Name**: SPH-project - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-12-01 - **Last Updated**: 2024-06-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SPH [TOC] ## 一、初始化项目 - vue-cli脚手架初始化项目。 - node + webpack + 淘宝镜像 - node_modules文件夹:项目依赖文件夹 - public文件夹:一般放置一些静态资源(图片)需要注意:,放在public文件夹中的静态资源,webpack进行打包的时候,.会原封不动打包到dist文件夹中。 - src文件夹(程序员源代码文件夹): - assets文件夹:一般也是放置静态资源(一般放置多个组件共用的静态资源),需要注意,放置在assets文件里面的静态资源,在webpack打包的时候,webpack会把静态资源当做一个模块,打包JS文件里面。 - components文件夹:一般放置的是非路由组件(全局组件) - App.vue:唯一的根组件,Vue当中的组件(.vue) - main.js:程序入口文件,也是整个程序当中最先执行的文件 - babel.config.js:配置文件(babel相关) - package.json文件:认为项目‘身份证’,记录项目叫做什么、项目中有那些依赖、项目怎么运行。 - package-lock.json:缓存性文件 ### 1.1、自动打开项目 在package.json中找到运行代码的那部分并加上 --open ```js "scripts": { "serve": "vue-cli-service serve --open", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, ``` ### 1.2、eslint校验关闭 在根目录下创建vue.config.js 比如:声明变量但是没有使用。运行时就会报错 ```js module.exports = { lintOnSave:false } ``` ### 1.3、配置别名 在根目录下创建 jsconfig.json ```js { "compilerOptions": { "baseUrl": "./", "paths": { "@/*": [ "src/*" ] } }, "exclude": [ "node_modules", "dist" ] } ``` ## 二、路由传参 2.1、路由的跳转方式: - **声明式导航**:router-link(务必有to属性),可以实现路由的跳转 - **编程式导航**:利用的是组件实例的$router.push | replace 方法,可以实现路由的跳转(可以书写一些自己的业务) 2.2、路由传参 - **params**参数:属于路径当中的一部分,需要注意,在配置路由的时候,需要占位 - **query**参数:不属于路径当中的一部分,类似于ajax中的queryString - 下文中的所有toUpperCase()是指把字符串进行大写 - 第一种:字符串形式 - 首先,在配置路由中给需要传参的路由路径后加:和一个形参![image-20211201110515682](README.assets/image-20211201110515682.png) - 然后使用params的方法传递![image-20211201110549210](README.assets/image-20211201110549210.png) - 最后通过这样的方式进行获取![image-20211201110918546](README.assets/image-20211201110918546.png) - 第二种:模板字符串 - 第一步、第三步与上相同,不同的是第二步 - ![image-20211201111144441](README.assets/image-20211201111144441.png) - 第三种:对象(最常用的) - 在配置路由中给需要传参的路由加上一个name属性 - ![image-20211201111911604](README.assets/image-20211201111911604.png) - ![image-20211201111846959](README.assets/image-20211201111846959.png) 面试题: 1. 路由传递参数(对象写法)path是否可以结合params参数一起使用? 1. 答:路由跳转传参的时候,对象的写法可以是name、path形式,但是注意,path这种写法不能与params参数一起使用 2. ![image-20211201133632401](README.assets/image-20211201133632401.png)![image-20211201133659828](README.assets/image-20211201133659828.png) 2. 如何指定params参数可传可不传? 1. 如果路由要求传递params参数,但是你就不传递params参数。 2. 指定params参数传递、或者不传递,在配置路由的时候,在占位的后面加上一个问号![image-20211201112932415](README.assets/image-20211201112932415.png) 3. params参数可以传递或不传递,但是如果传递的是空串,如何解决? 1. 使用undefined解决:params参数可以传递、不传递(空字符串),如果传递的是空串,路径会出现问题 2. ![image-20211201134215667](README.assets/image-20211201134215667.png) ## 三、编程式路由多次点击报错 因为路由跳转它有一个返回值,返回一个**promise**值 通过给push方法传递相应的成功、失败的回调函数,可以捕获到当前的错误 ```js // 先把VueRouter原型对象的push,保存一份 let originPush = VueRouter.prototype.push; // 重写push|replace // 第一个参数:告诉原来的push方法,你往哪跳转 // 第二个参数:成功的回调 // 第三个参数:失败的回调 VueRouter.prototype.push = function (location, resolve, reject) { if (resolve && reject) { originPush.call(this, location, resolve, reject) } else { originPush.call(this, location, () => { }, () => { }) } } VueRouter.prototype.replace = function (location, resolve, reject) { if (resolve && reject) { originPush.call(this, location, resolve, reject) } else { originPush.call(this, location, () => { }, () => { }) } } ``` ## 四、二次封装axios ### 4.1、为什么需要进行二次封装axios? > - 主要为了请求拦截器、响应拦截器 > > - 请求拦截器:可以在发请求之前处理一些业务 > - 响应拦截器:当服务器返回数据之后,可以处理一些业务 ### 4.2、创建request文件 在项目中经常在API文件夹中创建request ```js // 首先是引用axios // 对于axios进行二次封装 import axios from "axios"; // 1、利用axios对象的方法create,去创建一个axios实例 // 2、requests就是axios const requests = axios.create({ baseURL: '', timeout: 5000 // 超时时间 }) // 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出之前做一些事 requests.interceptors.request.use((config) => { // config:配置对象。对象里面有一个属性很重要,header请求头 return config }) // 响应拦截器 requests.interceptors.response.use((res) => { // 成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测到 return res.data }, (error) => { // 响应失败的回调函数 return Promise.reject(new Error('faile')) // return error }) // 对外暴露 export default requests ``` ### 4.3、创建index文件 ```js // 对api进行统一管理 import axios from "axios"; import requests from "./request"; // 三级接口联动 // /api/product/getBaseCategoryList get 无参数 // 发请求axios发请求返回Promise对象 export const reCategoryList = () => requests({ url: '/api/product/getBaseCategoryList', method: 'get' }) ``` ## 五、跨域问题 跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。 在vue.config.js中写入 ```js module.exports = { lintOnSave: false, // 代理跨域 devServer: { proxy: { '/api': { target: 'http://39.98.123.211', }, }, }, } ``` ## 六、nprogress进度条 - 使用npm i nprogress --save 来安装这个插件 - 然后在request.js中引入nprogress - ```js import nProgress from "nprogress"; ``` - start:进度条开始,done:进度条结束 - 分别写在请求拦截器和响应拦截器里 - ```js // 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出之前做一些事 requests.interceptors.request.use((config) => { // config:配置对象。对象里面有一个属性很重要,header请求头 nProgress.start() return config }) // 响应拦截器 requests.interceptors.response.use((res) => { // 成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测到 nProgress.done() return res.data }, (error) => { // 响应失败的回调函数 return Promise.reject(new Error('faile')) // return error }) ``` ## 七、Vuex状态管理库 ### 7.1、什么是vuex > vuex是官方提供的一个**插件**,**状态管理库**,**集中式**管理项目中组件共用的数据 ### 7.2、使用方法 - 首先创建store文件夹,下创建index.js文件 - 引入vue和vuex,再使用插件一次 - 初始化vuex - ```js import Vue from "vue"; import Vuex from 'vuex'; Vue.use(Vuex); // state:仓库储存数据的地方 const state = {} // mutations:修改state的唯一手段 const mutations = {} // actions:处理action,可以书写自己的业务逻辑,也可以处理异步 const actions = {} // getters:可以理解为计算属性,用于简化仓库数据,让组件获取仓库数据更加方便 const getters = {} const store = new Vuex.Store({ state, mutations, actions, getters }); export default store ``` - 最后在main.js中引入 - ```js // 引入vuex import store from '@/store' new Vue({ render: h => h(App), router, store }).$mount('#app') ``` ### 7.3、vuex模块式开发 - 初始化index.js文件 - ```js import Vue from "vue"; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ }); ``` - 创建各组件的小仓库例如![image-20211202094607589](README.assets/image-20211202094607589.png) - 进行小仓库初始化 - ```js const state = {} const mutations = {} const actions = {} const getters = {} export default { state, mutations, actions, getters } ``` - 再将各小仓库和大仓库合并 - ```js import home from "./home/home"; export default new Vuex.Store({ // modules里面注册你所需要的小仓库 modules: { home } }); ``` ### 7.4、使用vuex获取数据 - 首先发出请求 - ```js mounted(){ // 这里的caregoryList和vuex中组件的小仓库中的actions对象是对应的 this.$store.dispatch('caregoryList') }, ``` - 在小仓库这边引入封装好的接口![image-20211202102835646](README.assets/image-20211202102835646.png) - ```js // 通过api里面的接口函数调用,向服务器发送请求,获取数据 import { reqCategoryList } from '@/api' ``` - 书写第一步定义的方法**caregoryList** - ```js const actions = { async caregoryList({ commit }) { // 首先执行reqCategoryList方法,它的返回值是一个promise let result = await reqCategoryList() // console.log(result); 如果返回的code为200,则为成功, if (result.code == 200) { // 但是它没有CATEGORYLIST,所以需要在mutations中写 commit('CATEGORYLIST', result.data) } } } ``` - 在mutations对象中写 - ```JS const mutations = { CATEGORYLIST(state, categoryList) { // state.caregoryList:仓库的数据, categoryList:服务器返回的数据 state.caregoryList = categoryList } } ``` - 在state中写起始值,用来存储 - ```js const state = { caregoryList: [] } ``` - 这时候通过开发者工具就能看见state中已经有数据了 - ![image-20211202110517859](README.assets/image-20211202110517859.png) ## 八、节流与防抖 在多个事件需要执行时,如果用户操作太快,这多个事件会因为浏览器反应不过来而不会全部都执行,只会执行个别。 > 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速触发,只会执行一次 > > - 使用loadsh.js插件进行防抖,可通过官网下载或者 npm i loadsh --save 进行安装 > - **函数的使用方法:**`_.debounce(func, [wait=0], [options=])` > - `func` *(Function)*: 要防抖动的函数。 > - `[wait=0]` *(number)*: 需要延迟的毫秒数。 > - `[options=]` *(Object)*: 选项对象。 > - `[options.leading=false]` *(boolean)*: 指定在延迟开始前调用。 > - `[options.maxWait]` *(number)*: 设置 `func` 允许被延迟的最大值。 > - `[options.trailing=true]` *(boolean)*: 指定在延迟结束后调用。 > > - ![image-20211202145113842](README.assets/image-20211202145113842.png) > 节流:在规定的间隔时间范围内不会重复触发回调,只要大于这个时间间隔才会触发回调,把频繁触发变为少量 > > - 使用loadsh.js插件进行防抖,可通过官网下载或者 npm i loadsh --save 进行安装 > - **函数的使用方法:**`_.throttle(func, [wait=0], [options=])` > - `func` *(Function)*: 要节流的函数。 > - `[wait=0]` *(number)*: 需要节流的毫秒。 > - `[options=]` *(Object)*: 选项对象。 > - `[options.leading=true]` *(boolean)*: 指定调用在节流开始前。 > - `[options.trailing=true]` *(boolean)*: 指定调用在节流结束后。 > - ![image-20211202150545994](README.assets/image-20211202150545994.png) ## 九、多级菜单的路由跳转及传参 传参的方式有两种,声明式导航和编程式导航。如果菜单有很多的话,不适合用声明式导航,因为每一个声明式导航都是一个组件,影响性能,出现卡顿等bug。 主要手段:利用**事件委派** + **编程式导航**实现路由跳转及传参 > 存在的问题: > > ​ 1、事件委派,是把全部的子节点【h3、dt、dl】等的事件委派给父节点。 > > ​ 如何解决点击a标签的时候,才会进行路由跳转,也就是怎么确定点击的一定是a标签 > > 解决的方法: > > ​ 使用自定义属性,给所有的a标签定义一个属性,键为自定义,值为一级菜单的内容,如:![image-20211202193031749](README.assets/image-20211202193031749.png)![image-20211202193350819](README.assets/image-20211202193350819.png) > > 如何获取当前点击的子节点? > > ​ 通过**event**中的event.target,但是不需要所有的节点,只需要带有自定义属性data-categoryName这样类名的子节点 > > ```js > goSearch(event){ > let element = event.target > console.log(emelent) > } > ``` > > 可以通过event.target.dataset属性,获取节点的自定义属性与值 > > ![image-20211202200020430](README.assets/image-20211202200020430.png)使用解构赋值的方式更直观,caregory1id就是一级菜单的id,也是通过自定义标签设置的(看第二个问题),以此类推 > > 然后就是判断是不是带这个类名的 > > ![image-20211202200636164](README.assets/image-20211202200636164.png) > 存在的问题: > > ​ 2、如何区别一级菜单、二级菜单、三级菜单的标签 > > 解决的方法: > > 也通过自定义标签来解决 > > ![image-20211202201244968](README.assets/image-20211202201244968.png) > > 都设置好了就开始判断 > > ```js > if (categoryname) { > // 指定要跳转的路由 > let loaction = { name: "search" }; > // 创建一个变量,用来传获取到的标签数据, > let query = { categoryName: categoryname }; > // 因为要区分多级菜单,所有采用动态传入id值 > if (category1id) { > query.category1Id = category1id; > } else if (category2id) { > query.category2Id = category2id; > } else if (category3id) { > query.category3Id = category3id; > } > // 整理参数,将获取到的标签名传入loaction变量中 > loaction.query = query; > console.log(loaction); > // 下面拼接起来就是这样 > // this.$router.push({name: 'search',query:{categoryName:'xxx',id:xxx}}) > this.$router.push(loaction); > } > ``` ## 十、过渡动画 给需要过渡动画的部分套上``标签,并设置name,然后在css上编辑属性,例如 ![image-20211203100837082](README.assets/image-20211203100837082.png)![image-20211203100920476](README.assets/image-20211203100920476.png) ## 十一、Mock mock就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。 使用步骤: 1. 安装mockjs 1. ```bash npm install mockjs --save ``` 2. 在项目中scr文件夹下创建mock文件夹 3. 把准备好的JSON数据放到mock文件夹中(mock文件夹中创建对应的JSON文件) !----格式化,不能留空格 1. ![image-20211206100751481](README.assets/image-20211206100751481.png) 4. 关于图片放在public文件夹下,创建images文件夹,因为在webpack打包时,会把这个目录下相应的资源原封不动的打包 5. 创建mockServe.js文件,通过mockjs插件来实现模拟数据 1. 先引入mock插件 1. ```js // 引入mock import Mock from "mockjs"; ``` 2. 引入JSON数据 1. ```js // 引入json数据 import banner from './banner.json' import floor from './floor.json' ``` 3. 使用mock方法,第一个参数为JSON请求地址,第二个参数为请求数据 1. ```js Mock.mock('/mock/banner',{code:200,data:banner}) Mock.mock('/mock/floor',{code:200,data:floor}) ``` 6. 最后,把mockServe.js文件在入口文件main.js中引入 ## 十二、Swiper Swiper是一个做轮播图的插件 - 安装Swiper - ```bash // 5版本比较稳定 npm i swiper@5 --save ``` - 引入Swiper及样式 - ```js // 逐个引入 import Swiper from "swiper"; // 在入口文件引入 import 'swiper/css/swiper.css' ``` - 初始化Swiper,不需要的属性可以删掉 - ```js var mySwiper = new Swiper(".swiper-container", { loop: true, // 循环模式选项 direction: 'vertical', // 垂直切换选项 // 如果需要分页器 pagination: { el: ".swiper-pagination", clickable: true, }, // 如果需要前进后退按钮 navigation: { nextEl: ".swiper-button-next", prevEl: ".swiper-button-prev", }, // 如果需要滚动条 scrollbar: { el: '.swiper-scrollbar', }, }); ``` > 但是,这种写法在静态的HTML中是没有任何问题的,但是在动态渲染中就会出现DOM已经准备好了,图片并没有返回过来,swiper就初始化完了,会导致swiper无法使用的现象 > 解决方法:通过watch + nextTick > > watch的作用可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。 > > ```JS > // 案例 > data:{ > a:1, > b:{ > c:1 > } > }, > watch:{ > a(val, oldVal){//普通的watch监听 > console.log("a: "+val, oldVal); > }, > b:{//深度监听,可监听到对象、数组的变化 > handler(val, oldVal){ > console.log("b.c: "+val.c, oldVal.c); > }, > deep:true //true 深度监听 > } > } > ``` > > this.$nextTick():在下次DOM更新循环结束之后,执行延迟回调,在修改数据之后,立即使用这个方法,获取更新之后的DOM > > ```js > // 使用方法 > this.$nextTick(()=> { > > }) > ``` ## 十三、Vuex中四大金刚的辅助函数 分别是:**mapState、mapGetters、mapActions、mapMutations** ### 13.1、mapState 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性 配合使用ES6语法中的[...](三个点)扩展运算符,**`对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中`** ## 十四、分页器 ### 14.1、分页器功能实现 采用分页器是为了减轻一次性的请求数据量,例如电商平台同事展示的数据有很多(1w+),就可以采用分页器。 ### 14.2、分页器展示,需要那些数据 - pageNo:代表当前页数 - pageSize:代表每页展示的数据量 - total:代表一共的数据量 - 通过2、3条可以得出一共有多少页 【 total / pageSize 】 - continues:代表分页连续页码数 ### 14.3、实现逻辑 ```js // 父组件传过来的值 //【当前页码,每页的数据量,总数据量,连续页码个数】 props: ["pageNo", "pageSize", "total", "continues"], ``` > #### 首先计算出总页数,也就是总数据量 / 每页展示的数据量 > > ```js > computed: { > totalPage(){ > totalPage() { > // Math.ceil() 向上取整 > return Math.ceil(this.total / this.pageSize); > }, > } > } > ``` > #### 计算出连续的页码,起始数字和结束数字【连续页码至少是5】 > > ```js > computed:{ > startNumAndEndNum() { > // 需要用到变量,使用先解构 > //{连续页码数,当前页码数,总页码数} > const { continues, pageNo, totalPage } = this; > // 随便定义一个初始值 > let start = 0, > end = 0; > // 非正常现象,如果连续页码数大于总页码数,例如:定义的连续页码是5,而总页数只有3,所以起始一定是1,而结束页码结束总页码数 > if (continues > totalPage) { > start = 1; > end = totalPage; > } else { > // 正常现象,连续页码是5,总页数大于5 > // 起始数字,当前页码 - (连续页码数的一半取整),例如:连续页码定义的是5,取整=2,连续7,取整=3 > start = pageNo - parseInt(continues / 2); > // 结束数字 > end = pageNo + parseInt(continues / 2); > // 起始页不能是0|负数,然后起始页小于1,则为1,结束页则为连续页码数 > if (start < 1) { > start = 1; > end = continues; > } else if (end > totalPage) { > // 结束页不能超过总页数 > start = totalPage - continues + 1; > end = totalPage; > } > } > return { start, end }; > }, > } > ``` > #### 动态显示页码 > > 首先展示中间连续的页码,在v-for中不仅可以遍历数组,还可以遍历数字 > > ```html > > ``` > > > > 左右两边的页码 > > 未处理:![image-20211209103911889](README.assets/image-20211209103911889.png)![image-20211209103953735](README.assets/image-20211209103953735.png) > > 先处理左 1的隐藏与显示 > > ```html > // 判断如果起始页码大于一,也就是要当前页码最小为4时(2 3 4 5 6)才显示1 > > ``` > > 处理左...的显示与隐藏 > > ```html > // 判断如果起始页码大于二,也就是当前页码最小为5时(3 4 5 6 7),显示1 ... 3 4 5 6 7 > > ``` > > 处理右31的显示与隐藏 > > ```html > // 判断如果结束页码小于总页码数时 > > ``` > > 处理右...的显示与隐藏 > > ```html > // 判断如果结束页小于总页码-1时 > > ``` > #### 实现分页 > > 因为分页器为子组件,所以得用自定义事件去发请求,子传父用到$emit > > 首先在父组件定义一个自定义事件用来接收当前点击的页码数 > > ```html > > > ``` > > 接下来在子组件中给上一页,下一页,以及页码数实现点击事件,并传参 > > ```html > > > > > > ``` > > 最后在父组件中接收参数,并再次发请求 > > ```js > data() { > return { > // 带个服务器的参数 > searchParams: { > // 一二三级分类id > category1Id: "", > category2Id: "", > category3Id: "", > // 分类名字 > categoryName: "", > // 关键字 > keyword: "", > // 排序:初始值应该是综合 | 降序 > order: "1:desc", > // 分页器,代表当前第几页 > pageNo: 1, > // 一页显示多少条数据 > pageSize: 1, > // 平台售卖属性操作的参数 > props: [], > // 品牌 > trademark: "", > }, > }; > }, > methods:{ > getPageNo(pageNo){ > this.searchParams.pageNo = pageNo > this.getData() > } > } > ``` ## 十五、放大镜功能 例图:![image-20211210140419780](README.assets/image-20211210140419780.png) ```html // html排版
// ①需要被放大的图片 // 这里的event是来监听鼠标移入图片中的一个div,需要和图片一样大
// ②放大镜展示区域
// ③灰色的框框
``` 最主要的是@mousemove这个方法 ```js methods: { // 放大镜事件 // 传入event参数,当然不传也可以,默认是有的 handler(event) { // 创建两个常量用来保存灰色的框和放大镜展示区域 let mask = this.$refs.mask; let big = this.$refs.big // 创建两个常量用来保存,当前移动的坐标值 // offsetX、offsetY是event中的属性,表示当前鼠标位置 // offsetWidth、offsetHeight是获取mosk的宽高,1/2是因为鼠标在中间,鼠标到框外是半个mosk的宽度高度 let left = event.offsetX - mask.offsetWidth / 2; let top = event.offsetY - mask.offsetHeight / 2; // 控制范围 if (left <= 0) left = 0; if (left >= mask.offsetWidth) left = mask.offsetWidth; if (top <= 0) top = 0; if (top >= mask.offsetHeight) top = mask.offsetHeight; mask.style.left = left + "px"; mask.style.top = top + "px"; big.style.left = -2 * left + "px"; big.style.top = -2 * top + "px"; }, }, ``` ## 十六、本地存储、会话存储 > 区别: > > - 本地存储 localStorage ——**持久化** > - 在最新的JS的API中增加了localStorage对象,以便于用户存储永久存储的Web端的数据。而且数据不会随着Http请求发送到后台服务器,而且存储数据的大小机会不用考虑,因为在HTML5的标准中要求浏览器至少要支持到4MB > - **不能存储对象** > - localStorage提供了四个方法来辅助我们进行对本地存储做相关操作。 > - setItem(key,value):添加本地存储数据。两个参数,非常简单就不说了。 > - getItem(key):通过key获取相应的Value。 > - removeItem(key):通过key删除本地数据。 > - clear():清空数据。 > - 会话存储 sessionStorage ——**非持久化** > - 在Html5中增加了一个Js对象:sessionStorage;通过此对象可以直接操作存储在浏览器中的会话级别的WebStorage。存储在sessionStorage中的数据首先是Key-Value形式的,另外就是它跟浏览器当前会话相关,当会话结束后,数据会自动清除,跟未设置过期时间的Cookie类似。 > - **不能存储对象** > - sessionStorage提供了四个方法来辅助我们进行对本地存储做相关操作。 > - setItem(key,value):添加本地存储数据。两个参数,非常简单就不说了。 > - getItem(key):通过key获取相应的Value。 > - removeItem(key):通过key删除本地数据。 > - clear():清空数据。 发送![image-20211213091825201](README.assets/image-20211213091825201.png) 接收![image-20211213091857251](README.assets/image-20211213091857251.png) ## 十七、qrcode 二维码生成 qrcode API:`https://www.npmjs.com/package/qrcode` 在当前需要用到 qrcode 的组件中引入 qrcode ```js import QRCode from 'qrcode' ``` 使用qrcode中的一个方法toDateURL ```js QRCode.toDataURL('填写二维码地址,由服务器返回的数据') ``` ## 十八、图片懒加载 API:`https://www.npmjs.com/package/vue-lazyload` 全局安装lazyload ```bash npm i vue-lazyload -S ``` ```js // 在main.js 中引入lazyload // 引入懒加载 import VueLazyload from 'vue-lazyload' // 引入默认图片 import damie from '@/assets/1.jpg' // 注册懒加载 Vue.use(VueLazyload, { // 默认图片 loading: damie }) ``` 在需要懒加载的html中写入v-lazy ![image-20211215155030192](README.assets/image-20211215155030192.png) ## 十九、vee-validate 表单验证 ## 二十、组件基本通信方式 > 第一种:**props** > > - 适用于的场景:父子组件通信 > - 注意事项: > - 如果父组件给子组件传递数据(函数):本质其实是子组件给父组件传递数据 > - 如果父组件给子组件传递的数据(非函数):本质就是父组件给子组件传递数据 > - 书写方式:3种 ["todos'],{type:Array},{type:Array,default:[]} > - 小提示:路由的props > - 书写形式:布尔值,对象、函数形式 > > 第二种:**自定义事件** > > - 适用于场景:子组件给父组件传递数据 > - $on与$emit > > 第三种:**全局事件总线$bus** > > - 适用于场景:万能 > - Vue.prototype.$bus = this; > > 第四种:pubsub-js,在React框架中使用比较多(发布与订阅) > > - 适用于场景:万能 > > 第五种:**Vuex** > > - 适用于场景:万能 > > 第六种:**插槽** > > - 适用于场景:父子组件通信----(一般结构) > - 默认插槽 > - 具名插槽 > - 作用域插槽 ## 二十一、组件通信高级(面试必备) ### 21.1、event 深入 - 事件: - 系统事件:click、双击、鼠标系列等等 - 自定义事件 - 了**解事件源、事件类型、事件回调** ```js export default { methods:{ //原生DOM事件的回调 handler(event){ console.log('handler事件',event); }, } } ``` ```js export default { methods:{ handler1(){ console.log('handler1事件'); }, } } ``` ```js export default { methods:{ handler3(){ console.log('handler3事件'); }, } } ``` ```js ``` ### 21.2、v-model深入 - v-model 它是vue框架中的指令,它主要结合表单元素一起使用,(文本框,复选,单选) - 主要的作用是收集表单数据,**也是组件通信方式的一种** ```js {{msg}} ``` ```js export default { props:['value'] } ``` ### 21.3、属性修饰符sync ```js 小明的爸爸现在有{{ money }}元 export default { data() { return { money: 10000 } }, } 小明每次花100元 爸爸还剩 {{money}} 元 export default { name: 'Child', props:['money'] } 小明的爸爸现在有{{ money }}元 export default { data() { return { money: 10000 } }, } 小明每次花100元 爸爸还剩 {{money}} 元 export default { name: 'Child', props:['money'] } ``` ### 21.4、 $attrs与$listeners 他们是组件实例的属性,可以获取到父组件给子组件传递的props与自定义事件 ```js export default { methods: { handler() { alert('弹弹弹'); }, }, } 添加 export default { props:['title'], mounted(){ // $attrs:可以获取到父组件传递的所有props数据,但是如果子组件通过porops接收的属性,在$attrs属性中是获取不到的,比如有a,b,c三种属性,子组件通过props:['a']获取走了a,那么在$attrs中只能获取到b,c console.log(this.$attrs); // $listeners 是组件实例自身的一个属性,可以获取到父组件给子组件传递的自定义事件 console.log(this.$listeners); } }; ``` ### 21.5、$children与$parent $children 可以获取到当前组件中的所有子组件,有多个组件时,以数组的方式存放 ```js

效果一: 显示TODO列表时, 已完成的TODO为绿色

``` ```html ```