• 美食杰项目重难点记录


    1.自定义校验规则:

    多个下拉框同时校验(子组件可以实现多错误信息同时显示,在当前表单中只能显示一个错误信息,当前面的选择成功后,才会显示下一个错误信息)


    2.input父组件和子组件双向绑定,$emit()通知父级,并带参数


    3.子组件表单校验:有自己的表单校验方法及规则,父级通过ref调用子级的validate()方法


    4.子组件内新增后,同时校验:表单校验+onblur方法传入新增后对应index


    5.父组件和子组件同时做表单检验(Promise.all)

    问题:使用Promise.all([])进行父组件表单验证和多个子组件表单验证,只进行了第一个表单验证并返回状态为fullfilled,其他的状态都为pending
    解决:校验方法逻辑问题,new Promise里必须接收到返回值,才能知道最终执行结果,才能返回执行的状态是什么(fullfilled,pending,rejected)。
    new Promise下,是表单验证validate()方法,在此方法调用时,会调用校验规则或者自定义校验规则。
    此处,if外也要写callback()才会在有值符合条件的情况下,返回true,否则new Promise()会一直是pengding状态

    1. var validateName = (rule, value, callback) => {
    2.       // 注意此处是input双向绑定,所以通过this.value就可以得到input框输入的值
    3.       if (this.value[this.index].name === "") {
    4.         callback(new Error("请输入材料!"));
    5.       } 
    6.       callback();
    7.     };

    6.:loading="loading"

    提交或修改后,需要对应修改loading值


    7.el-form-item下textarea宽度设置不起作用:

     .el-form-item :last-child { width: 130%;}只能设置textarea框宽度,如果加了错误校验,报错时宽度又会回到原来大小

    1. el-form-item :last-child
    2.     width: 150%
    3.   div.introduce-text.el-textarea
    4.     width: 100%

    8.v-model双向绑定input,textarea
    9.限制图片显示大小。

    1. imgSty:{
    2.          width: '232px',
    3.          height: '232px'
    4.       }

    imgSty通过父级传入子级

    1. <el-upload ...>
    2.  <img :src="imgUrl" v-if="imgUrl" :style="imgSty"/>
    3. el-upload>

    9.上传图片组件组件

    限制只能上传一个文件:设置属性:file-list="fileList" :on-change="handleChange",并在handleChange方法中处理fileList,最后手动触发submit()

    1. handleChange(file,fileList){
    2.       if(fileList && fileList.length>=2){
    3.         fileList.shift();
    4.       }
    5.       this.$refs.upload.submit();
    6.     }

    10.产品图刷新就没有了

    图片没有正真上传到后端,此处是action中upload必须带参数type

    11.图片上传类型限制

    属性:accept=".jpg,.png"
    方法:

    1. :before-upload="beforeAvatarUpload"
    2. beforeUpload(file){
    3.       // accept="image/jpeg,image/jpg,image/png"也可设置在样式中,这个属性设置后,点击上传后,浏览文件夹中不会展示除设置类型以外的图片
    4.       // const isJPGOrPNG = file.type === 'image/jpeg' || 'image/png';
    5.       const isLt50KB = file.size / 1024 < 100;
    6.         // if (!isJPGOrPNG) {
    7.         //   this.$message.error('上传头像图片只能是 JPG 或者 PNG 格式!');
    8.         // }
    9.         if (!isLt50KB) {
    10.           this.$message.error('上传头像图片大小不能超过 100KB!');
    11.         }
    12.         // return isJPGOrPNG && isLt50KB;
    13.         return isLt50KB;
    14.     },

    12.报错 Error in render: "TypeError: Cannot read properties of undefined (reading 'length')"

    1. v-if="menuInfo.main_material.length"
    2.      menuInfo:{
    3.       main_material:{
    4.         type: Array,
    5.         default () {
    6.           return [];
    7.         }
    8.       },
    9.      }

    解决:方法一:对象或数组,一定要判断是否存在才能使用下面的属性 

    v-if="menuInfo.main_material && menuInfo.main_material.length"

    方法二:

    1. watch: {
    2.   dataPicture: {
    3.     handler (newVal, oldVal) {
    4.       console.log(newVal, '这样也可以得到数据~~~');
    5.       // this.list = newVal
    6.       },
    7.     immediate: true,
    8.     deep: true,
    9.   },
    10. }

    或者要做什么处理,在updated里处理,此时父组件已mounted完成
    原因:父组件生命周期中会先看看子组件的生命周期有没有走完,子组件生命周期走完了,才会走父组件的生命周期。
    父子组件生命周期执行顺序

    加载渲染数据过程
    父组件 beforeCreate -->

    父组件 created -->

    父组件 beforeMount -->

    子组件 beforeCreate -->

    子组件 created -->

    子组件 beforeMount -->

    子组件 mounted -->

    父组件 mounted -->

    更新渲染数据过程
    父组件 beforeUpdate -->

    子组件 beforeUpdate -->

    子组件 updated -->

    父组件 updated -->

    销毁组件数据过程
    父组件 beforeDestroy -->

    子组件 beforeDestroy -->

    子组件 destroyed -->

    父组件 destroyed

    13.后台规定了图片尺寸大小:获取图片上传像素

    参考:https://blog.csdn.net/u012443286/article/details/106146368/
    一定要在fileReader.onload 且 img.onload 之后再获取img像素才是正确的

    1. let fileReader = new FileReader();
    2.       fileReader.readAsDataURL(file);
    3.       let img = new Image();
    4.       let _this = this;
    5.       
    6.       fileReader.onload = function(e) {
    7.         let base64 = this.result;
    8.         img.src = base64;
    9.         img.onload = function(){
    10.          console.log(img.width, img.height);
    11.         }
    12.     }

    14.上传图片时,图片会加载两次,也会请求后端两次

    组件会自动上传,又在on-change又手动上传了一次
    如果需要手动上传,关闭自动上传:auto-upload="false",不需要手动上传就不要this.$refs.upload.submit();

    15.before-upload上传文件

    elementUI 官方文档中说明before-upload上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
    但是实际测试,返回false还是会发起请求,可能是使用了自动提交(auto-upload),只有返回new Promise()且有reject()才停止上传

    16.图片显示不了,图片显示一定要是后端传过来的url地址

    1. handleSuccess(res, file) {
    2.       if(res.code===0){
    3.         this.$message({
    4.           message:"图片上传成功",
    5.           type:"success"
    6.         });
    7.       this.$emit("imgUploaded", res.data.url);
    8.       }
    9.     },

    17.菜谱分类和属性 点击后样式设置

    当点击后更改路由路径,而在监听路由$route中的值有变化后,设置对应样式标识,渲染时标识满足条件即设置值

    1. "info" @click="clickType($event,property.type)"
    2.                                     v-for="property in item.list"
    3.                                     :title="property.title"
    4.                                     :key="property.type"
    5.                                     :name="property.type"
    6.                                     :class="{'tag-selected':propertyType[property.title]==property.type}"
    7.                                 >{{property.name}}
    8. clickType(e,type){
    9.             this.$router.push({
    10.                 path:this.$route.path,
    11.                 query:{
    12.                     ...this.$route.query,
    13.                     // ES6动态设置对象的key值[]
    14.                     [e.target.title]:type
    15.                 }
    16.             });
    17.         },
    18. ...
    19. watch:{
    20.         // 监听$route,如果没有classify则设置默认
    21.         $route:{
    22.             handler(){         
    23.                // property点击时,路由变化后&craft=1-1&flavor=2-2&hard=3-2&people=4-1,如果路由中存在的和该点击元素的值相同,则设置tag-selected样式为true 
    24.                 // 注意设置值要在路由变化后设置,在点击方法中设置会是点击前的query值
    25.                 this.propertyType = this.$route.query;
    26.             },
    27.             immediate:true
    28.         }
    29.     },

    18.过滤对象方法

    1. let queryInfo = this.$route.query;
    2. // 过滤对象方法
    3. // 方法一:delete
    4. // delete queryInfo.page;
    5. // delete queryInfo.classify;
    6. // this.propertyInfo = queryInfo;
    7. // 方法二:解构
    8. const { page,classify, ...query} = queryInfo;
    9. this.propertyInfo = query;

    19.点击上一页和下一页时,报错vue-router.esm.js?8c4f:2085 Uncaught (in promise) undefined,但是能跳转页面

    1. if="menuInfos.length"
    2.                         style="display: inline-block;"
    3.                         :pageSize="pageSize"
    4.                         :total="total"
    5.                         :current-page.sync="page"
    6.                         @current-change="currentChange"
    7.                         @prev-click="preClick"
    8.                         @next-click="nextClick"
    9.                     >
    10.                    

    原因:el-pagination组件中使用了 @current-change="currentChange"后,prev-click和next-click点击时也会触发current-change方法
    解决:直接点击某页时this.page和this.$route.query.page 相等,而prev-click和next-click点击时this.$route.query.page为undefined
    所以只需要在currentChange判断相等时才触发即可

    1. currentChange(){
    2.              // 点击的下一页,所有this.page会-1
    3.             if(this.$route.query.page===this.page){
    4.                 this.changePage(this.page);
    5.             }
    6.         },

    20.对象动态取值

    title为变量,propertyType为对象或数组

    this.propertyType[`${title}`];

    21.el-menu 在手动刷新地址栏后显示的不是当前sub-menu

    解决:监听路由地址并通过this.$nextTick()设置动态activerouter

    1. default-active="activerouter" class="el-menu-demo" mode="horizontal" :unique-opened='true'>
    2.     <el-menu-item index="1">
    3.       <router-link class="nav-link" :to="{name:'home'}">首页router-link>
    4.     el-menu-item>
    5.     <el-menu-item index="2">
    6.       <router-link class="nav-link" :to="{name:'recipe'}">菜谱大全router-link>
    7.     el-menu-item>
    8.  
    9.   data(){
    10.     return {
    11.       activerouter :'1'
    12.     }
    13.   },
    14.   watch:{
    15.     $route:{
    16.       handler(){
    17.         // 监听,如果路由地址变化,则改变nav-link
    18.         // 因为el-menu组件没有任何更改,所以直接设置是
    19.           this.$nextTick(()=>{
    20.             if(this.$route.name==='recipe'){
    21.               this.activerouter = '2';
    22.             }else{
    23.               this.activerouter = '1';
    24.             }
    25.           })
    26.       },
    27.       immediate:true
    28.     }
    29.   },

    22.this.$nextTick()使用场景

    Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
    Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
    首先修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及 DOM 。
    然后Vue 开启一个异步队列,并缓冲在此事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
    this.$nextTick()使用情景:
    created 和 mounted 阶段,如果需要操作渲染后的视图,要使用 nextTick 方法。因为mounted不会承诺其子组件也挂载完毕
    通过v-show切换到输入框后,自动聚焦用得到。
    点击获取某个元素的style样式时用得到

    vue的降级策略(兼容)
    promise -> MutationObserver ->(macro-task) setTimeout
    创建一个新的任务,优先使用 Promise,如果浏览器不支持,再尝试 MutationObserver。又不支持,只能用 setTimeout 创建 task 了。
    具体有下面几点变动的监听:
    childList:子元素的变动 
    attributes:属性的变动 
    characterData:节点内容或节点文本的变动 
    subtree:所有下属节点(包括子节点和子节点的子节点)的变动
    原文链接:https://blog.csdn.net/zhang1339435196/article/details/102911923

    23.多个侧边菜单同时选择和取消选中

    设置propertyInfo:[],用于接收每次选中,发送请求时,同时以参数形式传给后端
    在点击时将原有的和当前选中的放入数据中

    1. this.propertyInfo = {...this.propertyInfo,[title]:type};
    2. clickType(e,type){
    3.             let {title} = e.target;
    4.             // 判断如果this.propertyType[title] === type时过滤掉当前query中的title,否则会报错Uncaught (in promise) NavigationDuplicated {_name: 'NavigationDuplicated', name: 'NavigationDuplicated', message: 'Navigating to current location ("/recipe?classify=1-1&page=1&craft=1-2") is not allowed', stack: 'Error\n    at new NavigationDuplicated (webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)'}
    5.             if(this.$route.query[`${title}`] === type){
    6.                 delete this.propertyInfo[`${title}`];
    7.                 this.$router.push({
    8.                     path:this.$route.path,
    9.                     // this.propertyInfo是专门存储各个property点击或取消后的值,query参数可是使用其进行设置
    10.                     // 使用this.$route.query[`${title}`]只能控制当前点击的property单个值,不能记录所有点击或取消或的property值
    11.                     query:{
    12.                         ...this.propertyInfo
    13.                     }
    14.                 });
    15.             }else{
    16.                 this.$router.push({
    17.                     path:this.$route.path,
    18.                     query:{
    19.                         ...this.$route.query,
    20.                         // ES6动态设置对象的key值[]
    21.                         [title]:type
    22.                     }
    23.                 });
    24.             }
    25.             // 记录property选择项:将参数及值设置进请求数据中
    26.             this.propertyInfo = {...this.propertyInfo,[title]:type};
    27.             // 如果this.propertyType[title] === type证明地址栏传过来有该title对象,undefined表示点击,需要给地址栏加上该参数
    28.         },

    24:使用this.$refs['commentTextArea'].value = ''清空会报错

    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
    [Vue warn]:避免直接改变属性,因为每当父组件重新渲染时,该值将被覆盖。相反,使用基于属性值的数据或计算属性。正在变异的属性:“值”
    解决:使用this.$refs['commentForm'].resetFields();即可

    25.时间日期格式化

    使用npm i --save moment

    npm i --save moment
    {{userInfo.createdAt | dateTime}}
    1. //filter.js
    2. import Vue from 'vue'
    3. import moment from 'moment';
    4. moment.updateLocale('zh-cn', {
    5.     // 注意meridiem在moment()下的_local对象下,所以访问路径必须为moment()._locale.meridiem()
    6.     // 官方文档:逻辑有问题
    7.     meridiem: function(hour, minute, isLowercase) {
    8.         if (hour < 6) {
    9.             return "凌晨";
    10.         } else if (hour >= 6 && hour < 9) {
    11.             return "早上";
    12.         } else if (hour >= 6 && hour < 11) {
    13.             return "上午";
    14.         } else if (hour >= 11 && hour < 13) {
    15.             return "中午";
    16.         } else if (hour >= 13 && hour < 18) {
    17.             return "下午";
    18.         } else {
    19.             return "晚上";
    20.         }
    21.     },
    22.    
    23. });
    24. Vue.filter('dateTime', (val) => {
    25.     
    26.     return moment(val).format('YYYY年MM月DD日') + ' ' +
    27.         moment(val)._locale.meridiem(moment(val).format('hh'), moment(val).format('mm'), false) + ' ' +
    28.         moment(val).format('hh') + '点' +
    29.         moment(val).format('mm') + '分';
    30. });

    26.首页瀑布流

    首页需要不断发起请求,这种需求不适合用vuex存储数据
    瀑布流子组件中设置loading用于控制是否加载,初始值为true
    首次进入,加载第一页5条数据,加载完成后将loading=false
    因为loading在父组件加载完数据后会设置为false,所以也可将其在子组件中作为父组件数据是否加载完成的标识(因为如果父组件数据没有加载完成,就滚动滚动条的话获取的waterfall底部距离浏览器顶部距离不正确)
    子组件滑动滚动条后,判断如果当前的waterfall的底部距离浏览器顶部的高度 小于 浏览器可视区高度,即可加载下一页
    加载时通过loading标识决定,如果为true则表示正在loading,此时不能继续加载
    加载下一页,必须有子组件waterfall通知父组件home进行加载
    加载下一页,即this.page++,并发起请求
    发起请求前,判断数据是否全部加载完成this.page>Math.ceil(this.total/this.pageSize),如果是,则不再发起请求,且将loading设置为false

    27.防抖和节流,此处使用节流

    throttle 应用场景:鼠标多次点击按钮进行操作,输入框频繁搜索内容等条件下,为节省资源使用节流。(单位时间内只触发一次)

    debounce应用场景:滚动加载请求数据,window触发resize的时候,使用防抖函数可以让其只在最后触发一次。

    节流(throttle)和防抖(debounce)的区别:函数防抖和函数节流都是防止某一时间频繁触发,防抖和节流原理却不一样。函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。节流是在频繁操作的情况下按照给定的时间定时执行,防抖在频繁操作过程中会清楚上次的执行时间重新进行计算,从而只执行一次,类似于人进入电梯时电梯关门时间的处理。

    vue文档中有提到debounce,但该在2.0已被属性被移除及处理,推荐lodash

    原文链接:https://blog.csdn.net/gll19910602/article/details/103427360

    节流:每当用户触发了一次 scroll 事件,就为这个触发操作开启计时器。一段时间内,后续所有的 scroll 事件都无法触发新的 scroll 回调。直到“一段时间”到了,第一次触发的 scroll 事件对应的回调才会执行,而“一段时间内”触发的后续的 scroll 回调都会被节流阀无视掉。

    详细讲解:
    https://www.jb51.net/article/199265.htm

    节流函数示例:

    1. mounted(){
    2.     // 节流函数
    3.     this.scrollHandler = _.throttle(this.watchScroll.bind(this), 300)
    4.     window.addEventListener('scroll',this.scrollHandler);
    5.   },
    6.   destroyed(){
    7.     window.removeEventListener('scroll',this.scrollHandler);
    8.   },
    9.   methods:{
    10.     watchScroll(e){
    11.       if(this.loading) return;
    12.       
    13.       let heightToBrowserTop = this.$refs['waterfall'].getBoundingClientRect().bottom;
    14.       let browserHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
    15.       if(heightToBrowserTop < browserHeight){
    16.         this.loading = true;
    17.         this.$emit('loadContinue');
    18.       }
    19.     }
    20.   }

    28. 使用 label=" "设置*必填项

    1.  
    2.             <el-form-item prop="specs" label=" ">
    3.               <el-input placeholder="请输入内容" v-model="item.specs" @blur="getInputValue(index,$event,'specs')">el-input>
    4.             el-form-item>

    设置完后,*和input框会变不同行,进行以下设置即可(两个注释的地方)       

    1. .raw-item
    2.   // required:true的*号 和input框显示在一行,且input框会变宽,去掉float left
    3.   // float left
    4.   margin-right 65px
    5.   margin-bottom 20px
    6.   .el-input 
    7.     margin-right 2px
    8.     // required:true的*号 和input框显示在一行,将width: 100%;改为width: 86%;
    9.     width: 86%;

    29.一句代码实现请求分发

    方法一:第一种方式实现数据分类获取:返回数据添加flag

    1. // 封装方法:一句代码,根据参数不同,发起不同请求
    2. // 这里的函数名要和this.activeName对应才能获取
    3. let getSpaceInfo = {
    4. async works(params) {
    5. // 要用(await getMenus(params)).data才能请求正确
    6. let data = (await getMenus(params)).data;
    7. data.flag = "works";
    8. return data;
    9. },
    10. async fans(params){
    11. let data = (await getFans(params)).data;
    12. data.flag = "fans";
    13. return data;
    14. },
    15. async following(params){
    16. let data = (await following(params)).data;
    17. data.flag = "following";
    18. return data;
    19. },
    20. async collection(params){
    21. let data = (await getCollection(params)).data;
    22. data.flag = "collection";
    23. return data;
    24. }
    25. }
    26. // 第一种方式实现数据分类获取:返回数据添加flag
    27. let data = await getSpaceInfo[this.activeName]({userId:this.$route.query.userId});
    28. // 切换太快时数据会错乱
    29. if(this.activeName === data.flag){
    30. this.info = data.list;
    31. }

    方法二:第二种方式实现数据分类获取:闭包和第三方变量queen

    1. data(){
    2. return {
    3. // 传给子组件信息
    4. info:[],
    5. queen: {}
    6. }
    7. },
    8. ......
    9. (async (activeName)=>{
    10. let data = await getSpaceInfo[activeName]({userId:this.$route.query.userId});
    11. this.queen[activeName] = data.list; // this.queen.works = 作品的数据
    12. // 取当前路由name对应的数据
    13. if(activeName === this.activeName){
    14. this.info = this.queen[this.activeName];
    15. }
    16. this.queen = {};
    17. })(this.activeName)

  • 相关阅读:
    Tomcat服务器的简介
    阿里在职5年,一个女测试工师的坎坷之路
    ComfyUI中实现反推提示词的多种方案
    Ant Design Vue
    如何重置iPhone的网络设置?这里提供详细步骤
    Pytorch转TensorRT相关代码及其报错解决方法
    [oeasy]python0017_解码_decode_字节序列_bytes_字符串_str
    CSA研讨会 | 研讨零信任SASE安全架构,吉利控股首谈其部署方案
    电脑重装系统
    剖析SGI STL空间配置器(deallocate内存回收函数和reallocate内存扩充函数)
  • 原文地址:https://blog.csdn.net/qq_34569497/article/details/126546447