vue

724 阅读6分钟

vue 生命周期

image.png

组件创建

  • beforeCreate
    • 访问不到 data props methods 数据

image.png 结果:

image.png

  • created
    • 可以访问到 data props methods 数据
    • 一般用于调用ajax请求 把请求的数据转存到 data 中,供 template 模版渲染使用
    • 组建模版结构尚未生成

image.png 结果:

image.png

  • beforeMount
    • 将要把内存中编译好的 HTML 结构渲染到浏览器 此时还没有 DOM 结构

image.png 结果:

image.png

  • mounted
    • 已经把内存中编译好的 HTML 结构成功渲染到浏览器 可以获取到 dom 数据
    • 如果要操作当前组件的 dom 最早只能在 mounted 阶段执行

image.png 结果:

image.png

组件运行阶段

  • beforeUpdate
    • 将要根据变化过后、最新的数据,重新渲染组件的模版结构
    • data数据是新的 dom还是以前的数据
  • update
    • 已经根据最新的数据,完成了组件 DOM 结构的重新渲染
    • dom是最新的数据

组件销毁阶段

  • beforeDestroy
    • 将要销毁,此时尚未销毁,组件处于正常工作状态
  • destroy
    • 已经被销毁,对应的 DOM 结构完全移除

组件data为什么必须是函数

  • 以对象形式
    • componentAcomponentB的data都指向同一个对象,导致我们的组件数据相互污染。
    • eg
        class Component {
            constructor() {
                this.data = {
                    name: '测试111'
                };
            }
        }
        const componentA = new Component();
        const componentB = new Component();
        console.log(componentA.data, componentB.data);
- 效果图

image.png

  • 以函数形式
    • 组件A和组件B维护两个独立的data互不干扰
    • eg
        class Components {}
        const componentA = new Components();
        const componentB = new Components();
        componentA.data = function() {
            return {
                name: 'text1'
            }
        };
        componentB.data = function() {
            return {
                name: 'text2'
            }
        };
        console.log(componentA.data, componentB.data);
- 效果图

image.png

超出省略号...

    flex: 1;
    white-space: nowrap;
    text-overflow: ellipsis;
  • 效果图 image.png

element ui + sorttable 拖拽排序

ele任意时间范围打开组件默认选中

  • default-value: defailtTime
<el-time-picker is-range :default-value=defailtTime v-model="value1" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" placeholder="选择时间范围"> </el-time-picker>
data:
defailtTime: [STARTTIME, ENDTIME]
  • img image.png

form嵌套table校验

  • 解决::prop="'tableList.' + scope.$index + '.time'" :rules="rules.time"
  • prop只能在el-form-item上使用
 <el-form-item class="center" :prop="'tableList.' + scope.$index + '.text'" 
 :rules="rules.text">
     <el-input v-model="formData.text"><el-input>
 </el-form-item>

vue表格重绘

  • 解决::key="tableKey"
<el-table :key="tableKey">
</el-table>
data:
tableKey: false
init_data() {
    this.tableKey = !this.tableKey
}

数组比较是否一样

  • 解决:转JSON比较
  • JSON.stringify(数组1 === 数组2)

vue页面样式修改eleUI样式不生效

  • 解决:::v-deep
::v-deep.el-table {
    color: red
}

css属性

  • 行内元素添加高度宽度是无效的,块级元素才可以

不同场景显示不同tabs

  • 应用场景:当el-tabs通过条件显示对应的tabs时
  • 解决:通过计算属性判断
    • 计算属性不可以用data里面的属性
  • eg
        <el-tabs v-model="active">
            <el-tab-pane
                :label="item.label"
                :name="item.value"
                v-for="item in tabs_list"
                :key="item.value"
             >
             </el-tab-pane>
             <component :is="active"></component> 
        </el-tabs>
        import Text1 form ./text1.vue;
        import Text2 form ./text2.vue;
        ...
        // 组件
        components: { Text1, Text2 ... },
        data() {
            active: 'Text',
            tabsList: [
                {
                    label:'测试1',
                    value: 'Text1'
                }
            ]
        },
        computed: {
            tabs_list() {
                let _arr = [];
                // 通过路由传递的值判断显示tabs
                if (this.$route.query.text != '1') {
                    _arr = this.tabsList;
                } else {
                    _arr = [
                        ...this.tabsList,
                        {
                            label: '测试2',
                            value: 'Text2'
                        }
                    ];
                }
                return _arr;
            }
        }

销毁子组件

  • 应用场景:当前页面两个按钮需要调用同一个弹框子组件时
  • eg:当点击新增按钮时必填验证会验证一下,再次点击另一个按钮时发现已经交验过了
    • 问题:子组件没有销毁
    • 解决:利用v-if销毁子组件达到目的
<child v-if="visible == true" :visible.sync="visible"></child>

重置表单数据

  • 利用watch监听
  • 组件在同一个页面调用多次时,表单有数据时清空
// 父组件
<el-button @click="text_value('add')">增加</el-button>
<el-button @click="text_value('deduction')">删除</el-button>
// 子组件
props: {
        // 增加/删除 add/deduction
        type: {
            type: String,
            default: ''
        }
    },
watch: {
        // 当两个按钮切换时需要重置表单数据
        'type': {
            handler: function(val) {
                if (val) {
                    this.formData = {
                        text: '',
                        text2: ''
                    };
                }
            }
        }
    },
data() {
    return {
        formData: {
            text: '1',
            text2: ''
        }
    }
}

下拉单选互斥禁用

  • 需求:若选中其他选项,则系统不可选择(禁用)其他的选项还可以选
  • 解决根据选中的值find查找禁用的值
<template>
    <div>
        <el-select v-model="value" @change="text_change" placeholder="请选择">
            <el-option
              v-for="item in options"
              :key="item.value"
              :label="item.label"
              :value="item.value"
              :disabled="item.disabled">
            </el-option>
        </el-select>
    </div>
</template>

<script>
export default {
    data() {
        return {
            options: [{
                value: '1',
                label: '系统',
                disabled: false
            }, {
                value: '2',
                label: '大师',
                disabled: false
            }, {
                value: '3',
                label: '宗师',
                disabled: false
            }, {
                value: '4',
                label: '王者',
                disabled: false
            }, {
                value: '5',
                label: '荣耀王者',
                disabled: false
            }],
            value: '1'
        };
    },
    methods: {
        text_change(val) {
            if (val != '1') {
                this.options.find(item => item.value == '1').disabled = true;
            }
        }
    }
};
</script>
  • 效果图 image.png

下拉多选互斥

  • 需求:若选择某个单独的类型,则不可选中全部
  • 解决:根据value的值比较判断
<template>
  <div>
    <el-select v-model="value" multiple collapse-tags @change="text_sel" placeholder="请选择">
        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
    </el-select>
  </div>
</template>

<script>
export default {
    data() {
        return {
            options: [
                {value: '', label: '全部'},
                {value: '1', label: '测试1'},
                {value: '2', label: '测试2'},
                {value: '3', label: '测试3'},
                {value: '4', label: '测试4'}
            ],
            value: ['']
        };
    },
    methods: {
        text_sel(val) {
            if (val.length > 1) {
                // 根据value的值判断
                if (val[val.length - 1] == '') {
                    this.value = [''];
                } else {
                    this.value = val.filter(item => item != '');
                }
            }
        }
    }
};
</script>
  • 效果 image.png image.png

ele添加总计报高度错误

  • table添加:summary-method="get_summaries" show-summary
  • 错误:ResizeObserver loop limit exceeded
// 解决错误
beforeMount() {
        const _windowheight = document.documentElement.clientHeight || document.body.clientHeight;
        console.log('页面高度为:', _windowheight);
        this.tableHeight = _windowheight;
    },

ele时间日期组件(限制此刻和未来时间)

  • picker-options限制月份
  • 需要引入day.js 具体参考官方
// 当天和未来可选(没有精确到时分秒)
disabledDate(time) {
    return time.getTime() < Date.now() - 1 * 24 * 3600 * 1000;
}
// 保存和生效按钮在外部传递index
@chang="(e) => date_change(e, scope.$index)"
date_change(val, index){
    // 判断,不然直接修改会报错
    if(val){
        // 时间戳转换
        const _todaystamp = new Date(Date.now()).getTime(); // 当前时间戳
        const _valstamp = dayjs(val[0]).valueOf(); // 用户选择的时间戳
        // 时间戳转时间
        const today = dayjs(_todaystamp).format('YYYY-MM-DD HH:mm:ss');
        if(_valstamp < _todaystamp) {
            // 传值操作
        } else {
            // 传值操作
        }
    } else{
        // 可以设置为空
    }
}

联想查询(可支持手动输入和联想到的内容)

  • 手动输入可以保存,联想到的可以选择也可以不选中
    • 联想查询函数:通过接口联想查询
    • 清除数据函数:将联想数据清空
    • 监听值的函数:拿到值更新替换 (用于离开判断取值)
    • 离开失去焦点函数:恢复输入值 (判断页面是否有数据)
  • 解决清除属性clearable搜索词点击搜索框还有以前的搜索记录
  • eg

image.png

  • 解决方法: 添加clear事件并把筛选项列表清空
  • eg

image.png

相同属性的多个对象快速赋值法

  • Object.keys().forEach()
  • 接口获取为所有筛选项下拉列表添加全部属性
    Object.keys(_rest.data.select_options).forEach((key) => {
        _rest.data.select_options[key].unshift({ label: '全部', value: '' });
    });

获取页面滚动位置

        function debounce(fn, delay) {
            let timer;
            return function () {
                const context = this;
                const args = arguments;
                if (timer) clearTimeout(timer);
                timer =  setTimeout(() => {
                    fn.apply(context, args);
                }, delay);
            }
        }
        function showTop() {
            var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
          console.log('滚动条位置:' + scrollTop);
        }
        window.onscroll  = debounce(showTop, 1000);

user-agent判断用户登陆是否为QQ还是微信

    is_weixin_qq() {
        let ua = navigator.userAgent.toLowerCase();
        if (ua.match(/MicroMessenger/i) == 'micromessenger') {
            return 'weixin';
        } else if (ua.match(/QQ/i) == 'qq') {
            return "QQ";
        }
        return false;
    }

this.$set用法

  • 给对象添加属性值
    • case1:点击后报错 (用case 2 $set来添加)
      • 原因:objText中没有age属性名
    • case2:点击后页面显示 '更改前'
  • eg
<template>
    <div>
        <p v-if="objText.isEdit">{{objText.name}}</p>
        <el-button @click="addAgeHandler">添加isedit</el-button>
        <el-button @click="modifyAgeHandler">更改name</el-button>
    </div>
</template>
<script>
export default {
    data() {
        return {
            objText: {
                name: '更改前'
            }
        };
    },
    methods: {
        // case 1 给对象添加属性值 报错
        addAgeHandler() {
            this.student.age = 18;
        },
        // case 2 给对象添加属性值
        addAgeHandler() {
            this.$set(this.objText, 'isEdit', true);
        },
        modifyAgeHandler() {
            this.objText.name = '更改后';
        }
    }
};
</script>
  • 效果

image.png

el-tabs and keepAlive

        <el-tabs v-model="active">
            <el-tab-pane
                :label="item.label"
                :name="item.value"
                v-for="item in list"
                :key="item.value"
             >
             </el-tab-pane>
             <component :is="active"></component> 
        </el-tabs>
        import Text form ./text.vue;
        ...
        // 组件
        components: { Text, ... },
        data() {
            active: 'Text',
            list: [
                {
                    label:'测试',
                    value: 'Text'
                }
            ]
        }

在表单外保存数据

  • 通过findIndex来判断是否原数据保存还是新增数据的保存
    • findIndex:存在返回 -1 没有返回当前行下标 eg:
     // 通过 id 来判断
     const _index = this.tableList.findIndex( item => item.id === '' );
     // 有id返回 -1 没有返回 当前行下标 来判断并特殊处理

v-model双向绑定

  • v-model既是双向绑定:当数据变化之后,视图同步更新,当视图变化之后,数据也会更新。

    • 语法糖
  • v-model也是单向数据流:当数据变化之后,视图同步更新,当视图变化之后,数据不会更新。

    • 简单来说:数据向下,事件向上。
  • 原理:传递一个value字段、并监听input事件 二者结合的语法糖 eg:

     <input v-model="value"> === <input :value="value" @input="this.value = $event">

v-for尽量避免用index作为key

eg:

 <el-radio v-for="(item, index) in headerList" :key="index" :label="item.value">{{ item.label }}</el-radio>

原因:

  • 用 index 作为 key 时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会产生没必要的真实 DOM更新,从而导致效率低

  • 用 index 作为 key 时,如果结构中包含输入类的 DOM,会产生错误的 DOM 更新

  • 在开发中最好每条数据使用唯一标识固定的数据作为 key,比如后台返回的 ID,手机号,身份证号等唯一值**

  • 如果不存在对数据逆序添加,逆序删除等破坏顺序的操作时,仅用于渲染展示用时,使用 index 作为 key 也是可以的(但是还是不建议使用,养成良好开发习惯)。

ref的作用

  • 基本用法——获取DOM元素

  • 进阶用法——获取子组件中的data 调用子组件中的方法

// 父组件 <home ref="home"/>
mounted(){
    console.log(this.$refs.home) //即可拿到子组件的实例,就可以直接操作 data 和 methods
}

v-if和v-show区别

  1. v-if是控制标签是否渲染(是否存在该标签)
  2. v-show控制标签是否显示(display:none)
  • 什么场景使用v-if,什么场景什么v-show
  • 需要大量切换的时候使用v-show,不怎么切换的就使用v-if,
  • 多条件使用时优化考虑v-if
  • 实际项目过程中基本大部分是v-if,因为它有v-else-if可用于多条件,较灵活一些。

尽量避免使用v-for和v-if同时使用

this.$nextTick()使用

应用场景:父组件通过props传值给子组件,调用子组件第一次拿不到父传递的值时,第二次可以拿到

  • 解决 $nextTick() 或者 子组件watch监听下值的变化
  • eg:
<el-button @click="show_child">调用弹窗子组建</el-button>
// 传ID给子组件
<child ref="hb-child-dialog" :id="text.id"></child>
show_child() {
    this.text.id = id;
    this.$nextTick(() => {
        // 通过ref调取子组件方法获取初始化数据
        this.$refs['hb-child-dialog'].init_data();
    })
}

Table表操作

  • Table 时间排序参考element ui 组件库 添加sortable 后端排序传参数(custom) 用sort-change监听事件携带参数{column, prop, order}
  • Table 批量删除操作参考element ui 组件库 添加type="selection"selection-change监听事件携带参数
  • Table row-class-name 属性中的某一行添加 class eg:给对应行添加颜色

table高度出现滚动条

  • 超出table高度出现滚动条
  • eg:
    <el-table 
        :height="calc(100vh - 250px)" 
    >
    </el-table>
  • 效果:

image.png

vue axios -- FormData 上传图片

  • eg:
    const formdata = new FormData()
    //formdata本身是个对象,参数名为file
    formFile.append('action', 'upload');
    formdata.append('file', file.file)
    // 请求
  • 要传的数据
    • data:formData
  • formdata打印出来为空的原因
  • 覆盖默认的上传行为,可以自定义上传的实现
  • eg:
    <el-upload
        action="#"
        :http-request=" e => { function(e, scope.row, scope.$index ) } " 
        // e => {} 这样写是为了将当前数据带函数进去,让函数方法可以获取到
    >
    
    const formdata = new FormData()
    //formdata本身是个对象,参数名为file
    formFile.append('action', 'upload');
    formdata.append('file', file.file)
    console.log(formData)  //{} 发现为空
    console.log(formData.get('action'))  // 'upload'
    console.log(formData.get('file'))   // 文件
  • 效果

image.png

时间戳格式化时间

  • time: 获取到的时间戳
  • eg:
   const time = 1579161320;
   format_time(time){
       const date = new Date(time * 1000 + 8 * 3600 * 1000);
       return date.toJSON().substr(0, 19).replace('T', ' ');
   }
  • 效果

image.png

ele日期组件:处理时间和日期的js库

// 默认显示当前周
const STARTDATE = dayjs().startOf('week').add(1, 'day').format('YYYY-MM-DD 00:00');
const ENDDATE = dayjs().endOf('week').add(1, 'day').format('YYYY-MM-DD 23:59');
// 默认推前1个月
const TODAYD = dayjs().format('YYYY-MM-DD 23:59'); // 当天时间
const BEFORMOUTH = dayjs().subtract(1, 'month').format('YYYY-MM-DD 00:00'); // 推前1个月

表单优化

  • template:有判断逻辑特殊处理的用,没有则可以省略
  • key值:表头字段名称
  • eg:
   // 有逻辑特殊处理
   <el-table>
       <el-table-column>
           // 具名插槽
           <template slot="header">
               <template v-if="['key值'].includes(key)">
                   // 表头操作
               <template >
           </template>
           // 作用域插槽
           <template slot-scope="scope">
                 <template v-if="key === 'key值'">
                    // 需要处理的代码 select input switch 等                           
                 </template>
                 <template v-else>
                   {{scope.row[key]}}                              
                 </template>
            </template>      
       </el-table-column>
    </el-table>
    // 无逻辑
    <el-table>
       <el-table-column>     
       </el-table-column>
    </el-table>

筛选项

  • 用到分页的时候需添加
    • 重新获取第一页:防止翻页后搜索不到数据的情况
  • eg
    // 筛选项
   <el-input
         placeholder="请输入关键词"
         <!-- 重新获取第一页 -->
         @keyup.enter.native="handleCurrentChange(1)" 
         clearable>
   </el-input>
   // 分页
   <el-pagination 
       @current-change="handleCurrentChange" 
       layout="prev, pager, next, jumper" 
       :current-page.sync="pageData.page" 
       :page-size="pageData.page_size" 
       :total="pageData.total"> 
   </el-pagination>
   
   data() {
       pageData: {
           page: '', // 第几页
           page_size: '', // 页数
           total: '', // 条数
       }
   }
   
   methods: {
       handleCurrentChange(val) {
           this.pageData.page = val || 1;
       }
   }

一级页面向二级页面传递数据

  • 以传递id为例子
  • 特殊处理下,防止传入空的id
  • this.$route.query.id: 路由传递id
  • eg
   // 一级页面传递id
   this.$router.push({
       path: '', // 跳转的路径 
       query: {
           id: '',
           name: ''
       } // 要传的对象
   })
   // 二级页面接受并判断处理 
   data() {
       formData: {
           id: '' // -级页面传递id
       }
   } 
   created() {
       this.$route.query.id ? this.formData.id = this.$route.query.id :      
       this.$message.error('id不能为空');
       // 初始化数据
       this.initData();
   }
   methods: {
       initData() {
           // formData.id 为空 则不调取接口
           if(!this.formData.id) return;
       }
   }

切换页面刷新问题 (返回)

  • 解决:添加缓存 路由配置name属性 同时vue页面也需要配置name属性 (属性名一致)
  • 实现方法:www.jianshu.com/p/9cefe3d27…

el-input超出3位小数限制输入

  • 这里用到正则表达式,每输入一个数字会对输入框进行一次事件的触发,检查是否超过三位小数点,超过则进行删除。
  • {}🀄️ 3改成2,这样就是保留两位小数点了
  • eg
    // 超出3位小数限制输入
    <el-input
        type="number"
        onkeyup="value=value.replace(/^\D*(\d*(?:\.\d{0,3})?).*$/g, '$1')"
        // 或者
        onkeyup="this.value= this.value.match(/\d+(\.\d{0,3})?/) ? this.value.match(/\d+(\.\d{0,3})?/)[0] : ''"
        // 输入纯数字
        oninput="value=value.replace(/[^\d]/g,'')" 
        // 输入大于0的数字
        oninput="value=value.replace(/\D|^0/g,'')"
    >
    </el-input>

根据计算保留3位小数

  • eg
    // 根据计算保留3位
    const a =  Math.floor(3.125 * 1000) / 1000;

es6语法

  • 非空判断 (变量??'') !== '' 变量不能为:‘’ null undefind
    • (value??'') !== '' ? console.log('ok') : console.log('no');
  • includes 多个值满足条件
    • ['1','2','aaa','3'].includes(value) ? console.log('ok') : console.log('no');