VUE项目踩坑记录
1、虚拟DOM不渲染数据
问题描述:消息已读和未读的功能,点击消息,消息会变成已读,重新请求数据,在重新请求数据前会先清空旧数据,但是因为两次的数据一样,导致vue的diff算法默认不更新视图,使用 this.$set
和 this.$forceUpdate
等方法都不能解决问题
解决方法:在v-for的父元素加个v-if v-if=“list.length > 0”
。 vue
的 diff 算法会监测数组变化,响应式地渲染列表。
2、复杂对象数组去重
使用reduce方法进行复杂对象数组去重
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let arr = [ { id: 1, name: 'zs' }, { id: 2, name: 'ls' }, { id: 1, name: 'zs' }, ] const obj = {} arr = arr.reduce((pre, item) => { if (!obj[item.id]) { obj[item.id] = true pre.push(item) } return pre }, []) console.log(arr) // {id: 1, name: 'zs'}{id: 2, name: 'ls'} |
3、正则判断是否数据是否以负号或0-9的数字开头(含小数)
1 | const isSum = /^(-|\+)?\d+(\.\d+)?$/<br>cosnt sunA = 1console.log(isSum.test(sumA)) |
4、数字格式化:千分位及并保留两位小数(多用于金额格式化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | moneyFormatter = function (money, num) { /* * 参数说明: * money:要格式化的数字 * num:保留几位小数 * */ num = num > 0 && num <= 20 ? num : 2; money = money + '' ; var index = money.indexOf( '.' ) + 1; if (index > 1 && money.substring(index, money.length).length > num) { money = money.substring(0, index + num); } money = parseFloat((money + '' ).replace(/[^\d.-]/g, '' )).toFixed(num) + '' ; var l = money.split( '.' )[0].split( '' ).reverse(), r = money.split( '.' )[1]; var t = '' , i; for (i = 0; i < l.length; i++) { t += l[i] + ((i + 1) % 3 == 0 && i + 1 != l.length ? ',' : '' ); } return t.split( '' ).reverse().join( '' ) + '.' + r; } |
以上适用于无负数格式化,如果金额有负数,需要判断是否为负数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | moneyFormatter = function (money, num) { /* * 参数说明: * money:要格式化的数字 * num:保留几位小数 * */ num = num > 0 && num <= 20 ? num : 2 money = money + '' var index = money.indexOf( '.' ) + 1 if (index > 1 && money.substring(index, money.length).length > num) { money = money.substring(0, index + num) } money = parseFloat((money + '' ).replace(/[^\d.-]/g, '' )).toFixed(num) + '' var l let isNegative = '' if (money.startsWith( '-' )) { // 判断为负数 将负号取出 l = money .split( '.' )[0] .substring(1, money.length) .split( '' ) .reverse() // 整数部分 isNegative = money.split( '.' )[0].substring(0, 1) // 负号 } else { l = money .split( '.' )[0] .split( '' ) .reverse() } r = money.split( '.' )[1] // 小数部分 var t = '' , i for (i = 0; i < l.length; i++) { t += l[i] + ((i + 1) % 3 == 0 && i + 1 != l.length ? ',' : '' ) } return ( isNegative + t .split( '' ) .reverse() .join( '' ) + '.' + r ) } |
5、字符串toLocaleString()方法
toLocaleString() 方法可根据本地时间把 Date 对象转换为字符串,并返回结果
1 2 | var d= new Date(); var n=d.toLocaleString(); // 2021/11/29 下午4:24:49 |
除此之外:还可以将数字变成千分位格式
1 2 | let num=12345678; console.log(num.toLocaleString()); // 12,345,678 |
还可以将时间转换为 24 小时制
1 2 3 4 5 | // 2021/11/29 下午4:25:06 console.log( new Date().toLocaleString() // 2021/11/29 16:25:06 console.log( new Date().toLocaleString( 'chinese' ,{hour12: false })) |
6、ElementUI表格样式调整
ElementUI表格样式需要加载行内以对象的形式存
1 | <el-table :data= "tableData" :summary-method= "getSummaries" show-summary height= "240" :header-cell-style= "tableHeader" :row-style= "{ height: '2.5rem' }" :cell-style= "{ padding: '0' }" > |
header-cell-style设置表头样式
row-style设置每行的高度
cell-style将默认的padding去除
去掉表格默认的高亮效果
1 2 3 | .el-table__row:hover > td { background-color: transparent; } |
修改表格下划线颜色
1 2 3 4 | /deep/.el-table td.el-table__cell, .el-table th.el-table__cell.is-leaf { border-bottom: 1px solid #e6e6e6; } |
去除进度条的圆角
1 2 3 4 5 6 7 8 9 | /deep/.el-progress-bar__outer { border-radius: 0; } /deep/.el-progress-bar__inner { border-radius: 0; } /deep/.el-progress-bar__inner { "> #5b8ff9; } |
ElementUI普通表格取消高亮
1 2 3 | /deep/.el-table__row:hover > td { background-color: transparent; } |
如果表格有使用fixed进行固定,使用上面的方法会导致部分fixed固定的列的高亮无法取消,推荐使用下面的方法
1 2 3 | /deep/.el-table__body tr.hover-row > td.el-table__cell { background-color: transparent; } |
7、eventBus传值及累加触发问题、created的值在mounted内获取
1、eventBus在兄弟组件之间传值如果且触发了路由跳转(A页面跳转至B页面)会导致第一次传值失败
原因:B页面没有被创建导致发送失败,如果在B页面creted内使用bus.$on
会发生bus.$on
先触发,A页面的bus.$emit
后触发,导致B页面接收不到参数
解决方法:
1 2 3 4 5 6 | // 在发送方A页面使用this.this.$nextTick(()=>{}) this .$nextTick(() => { bus.$emit( 'eleOpen' , this .openEle) console.log( 'bus.$emit' ) }) // 在接收方B页面正常接收即可 |
2、bus.$on多次触发的问题
这个$on事件是不会自动清楚销毁的,需要我们手动来销毁
1 2 3 4 | // 在B组件页面中添加以下语句,在组件beforeDestory的时候销毁。 beforeDestroy () { bus.$off( 'get' , this .myhandle) }, |
3、在created里面发起请求或接收兄弟组件的参数,在mounted内无法调用到created内参数值
原因:虽然按照生命周期是created在前,mounted在后,但生命周期异步加载需要时间,如果延迟时间是可以获取到数据的,但是问题是不知道需要延迟多久,所以最好不要使用定时器处理。
解决方法:
1.在created
生命周期内进行异步数据的请求,且将获取到的数据赋值给this.data。
2.此时如果在mounted
生命周期里获取this.data是获取不到的。
3.不要在mounted内处理数据在watch内处理即可
1 2 3 4 5 6 7 8 9 10 11 12 | // 在data定义数据 data(){ isOpenDialog: false } // 在watch内监听 watch: { isOpenDialog() { this .$nextTick(() => { // 在这里可以获取和处理数据 }) } } |
4、Vue.js中this.$nextTick()的使用
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
假设我们更改了某个dom元素内部的文本,而这时候我们想直接打印出这个被改变后的文本是需要dom更新之后才会实现的,也就好比我们将打印输出的代码放在setTimeout(fn, 0)中
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
假设我们更改了某个dom元素内部的文本,而这时候我们想直接打印出这个被改变后的文本是需要dom更新之后才会实现的,也就好比我们将打印输出的代码放在setTimeout(fn, 0)中
8、cascader动态加载次级项视图不更新问题
使用ElementUI的级联选择器时,选中一级动态渲染次级时数据添加进去但是不显示解决办法,使用this.$set方法
1 2 3 4 5 6 7 8 9 | handleChange(val) { this .options.filter(item => { if (item.id === val[0]) { // item.children = this.children 此方法可以将数据添加进去但不会更新视图,需要使用this.$set方法 this .$set(item, 'children' , this .children) } }) console.log( this .options) } |
cascader点击文本选中当前label
1 2 3 4 5 6 7 8 9 | mounted() { setInterval(() => { document.querySelectorAll( '.el-cascader-node__label' ).forEach(el => { el.onclick = function () { if ( this .previousElementSibling) this .previousElementSibling.click() } }) }, 500) }, |
9、vue 打包报错Failed to load resource: net::ERR_FILE_NOT_FOUND
解决方法:
在项目根目录创建名为vue.config.js的文件夹
在该文件内输入
1 2 3 | module.exports = { publicPath: './' } |
10、vue路由传参
项目中很多情况下都需要进行路由之间的传值,可以使用sessionstorage/localstorage/cookie 进行离线缓存存储也可以,用vuex也可以,如果只是简单的传值可以使用vue自带的路由传参方法
参考官方文档:https://router.vuejs.org/zh/guide/essentials/passing-props.html
想要实现点击当前页的某个按钮或链接跳转到另外一个页面去,并将某个参数带过去
1 | <div @click= "insurance(123)" >我要传参</div> |
第一种方法 页面刷新数据不会丢失
1 2 3 4 5 6 7 | methods:{ insurance(id) { //直接调用$router.push 实现携带参数的跳转 this .$router.push({ path: `/particulars/${id}`, }) } |
需要对应路由配置如下:可以看出需要在path中添加/:id来对应 $router.push 中path携带的参数。在子组件中可以使用来获取传递的参数值
1 2 3 4 5 | { path: '/particulars/:id' , name: 'particulars' , component: particulars } |
目标页面获取参数方法:
1 | this .$route.params.id |
第二种方法 页面刷新数据会丢失 类似post请求
通过路由属性中的name来确定匹配的路由,通过params来传递参数。
1 2 3 4 5 6 7 8 9 | methods:{ insurance(id) { this .$router.push({ name: 'particulars' , params: { id: id } }) } |
对应路由配置: 注意这里不需要使用:/id来传递参数了,因为组件中,已经使用params来携带参数了。
1 2 3 4 5 | { path: '/particulars' , name: 'particulars' , component: particulars } |
目标页面获取参数方法:
1 | this .$route.params.id |
第三种方法 query传递的参数会显示在url后面以【?id=?】的形式,类似get请求
使用path来匹配路由,然后通过query来传递参数
1 2 3 4 5 6 7 8 9 | methods:{ insurance(id) { this .$router.push({ path: '/particulars' , query: { id: id } }) } |
对应路由配置:
1 2 3 4 5 | { path: '/particulars' , name: 'particulars' , component: particulars } |
目标页面获取参数方法:
this.$route.query.id
11、将后台返回的数据进行相邻单元格合并
html内容区
1 2 3 4 5 6 7 8 9 | <div class = "right_content" > <el-table :data= "skuListInfo" :span-method= "objectSpanMethod" border> <el-table-column prop= "name" label= "名称" > </el-table-column> <el-table-column prop= "storeIds" label= "适应门店" > </el-table-column> <el-table-column prop= "feeTypeInfo" label= "费用类型" > </el-table-column> <el-table-column prop= "productInfo" label= "适用产品" > </el-table-column> <el-table-column prop= "billing" label= "计费方法" > </el-table-column> </el-table> </div> |
script方法区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | <script> export default { data() { return { skuListInfo: [ { id: 1, name: '普通门店' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '1' , productInfo: '日托' , mergeId: 1, feeType: '1' , feeTypeInfo: '学费' , billing: '月/季/年制度' }, { id: 2, name: '普通门店' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '2' , productInfo: '晚托' , feeType: '1' , mergeId: 1, feeTypeInfo: '学费' , billing: '月/季/年制度' }, { id: 3, name: '普通门店' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '3' , productInfo: '临时托' , feeType: '1' , mergeId: 1, feeTypeInfo: '学费' , billing: '月/季/年制度' }, { id: 4, name: '普通门店' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '4' , productInfo: '越拖' , feeType: '1' , mergeId: 1, feeTypeInfo: '学费' , billing: '月/季/年制度' }, { id: 9, name: '普通门店' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '4' , productInfo: '全部' , feeType: '2' , mergeId: 1, feeTypeInfo: '定金' , billing: '月/季/年制度' }, { id: 10, name: '普通门店' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '4' , productInfo: '全部' , feeType: '3' , mergeId: 1, feeTypeInfo: '学杂费' , billing: '月/季/年制度' }, { id: 5, name: '团购' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '5' , productInfo: '寒假托' , feeType: '2' , mergeId: 1, feeTypeInfo: '定金' , billing: '月/季/年制度' }, { id: 6, name: '团购' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '5' , productInfo: '日托' , feeType: '1' , mergeId: 1, feeTypeInfo: '学费' , billing: '月/季/年制度' }, { id: 7, name: '团购' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '5' , productInfo: '晚托' , feeType: '1' , mergeId: 1, feeTypeInfo: '学费' , billing: '月/季/年制度' }, { id: 8, name: '大客户' , storeIds: [1, 2, 3, 4], storeIdInfo: '[1, 2, 3, 4]' , productType: '6' , productInfo: '暑假托' , feeType: '3' , mergeId: 1, feeTypeInfo: '学杂费' , billing: '月/季/年制度' } ], typeNameArr: [], typeNamePos: 0, storeArr: [], storePos: 0, feeArr: [], feePos: 0, hoverOrderArr: [] } }, mounted() { this .merage() }, methods: { merageInit() { this .typeNameArr = [] this .typeNamePos = 0 this .storeArr = [] this .storePos = 0 this .feeArr = [] this .feePos = 0 }, merage() { this .merageInit() for ( let i = 0; i < this .skuListInfo.length; i += 1) { if (i === 0) { // 第一行必须存在 this .typeNameArr.push(1) this .typeNamePos = 0 this .storeArr.push(1) this .storePos = 0 this .feeArr.push(1) this .feePos = 0 } else { // 判断当前元素与上一个元素是否相同,eg:this.typeNamePos 是 this.typeNameArr序号 // 第一列 // eslint-disable-next-line no-lonely-if if ( this .skuListInfo[i].name === this .skuListInfo[i - 1].name) { this .typeNameArr[ this .typeNamePos] += 1 this .typeNameArr.push(0) } else { this .typeNameArr.push(1) this .typeNamePos = i } // 第二列 if ( this .skuListInfo[i].storeIdInfo === this .skuListInfo[i - 1].storeIdInfo && this .skuListInfo[i].name === this .skuListInfo[i - 1].name) { this .storeArr[ this .storePos] += 1 this .storeArr.push(0) } else { this .storeArr.push(1) this .storePos = i } // 第三列 if ( this .skuListInfo[i].feeType === this .skuListInfo[i - 1].feeType && this .skuListInfo[i].storeIdInfo === this .skuListInfo[i - 1].storeIdInfo && this .skuListInfo[i].name === this .skuListInfo[i - 1].name) { this .feeArr[ this .feePos] += 1 this .feeArr.push(0) } else { this .feeArr.push(1) this .feePos = i } } } }, objectSpanMethod({ row, column, rowIndex, columnIndex }) { // if (columnIndex === 0) { // 用于设置要合并的列 // if (rowIndex % 2 === 0) { // 用于设置合并开始的行号 // return { // rowspan: 2, // 合并的行数 // colspan: 1, // 合并的猎术, 设置为0则直接不显示 // }; // } // return { // rowspan: 0, // colspan: 0, // }; // } if (columnIndex === 0) { // 第一列的合并方法 const row1 = this .typeNameArr[rowIndex] const col1 = row1 > 0 ? 1 : 0 // 如果被合并了row = 0; 则他这个列需要取消 console.log(row1) return { rowspan: row1, colspan: col1 } } else if (columnIndex === 1) { // 第二列的合并方法 const row2 = this .storeArr[rowIndex] const col2 = row2 > 0 ? 1 : 0 // 如果被合并了row = 0; 则他这个列需要取消 return { rowspan: row2, colspan: col2 } } else if (columnIndex === 2) { // 第三列的合并方法 const row3 = this .feeArr[rowIndex] const col3 = row3 > 0 ? 1 : 0 // 如果被合并了row = 0; 则他这个列需要取消 return { rowspan: row3, colspan: col3 } } } } } </script> |
12、
1.打开vue.config.js进行配置
1 2 3 4 5 6 7 8 9 10 11 | devServer: { proxy: { '/api' : { target: 'http://www.xxx.com.cn/' , // 要请求的API changeOrigin: true , // 是否开启跨域 pathRewrite: { '^/api' : '' // 重写路由 } } } } |
2.请求时进行后缀请求,比如请求/menu
则请求/api/menu
即可
posted on 2022-07-14 18:07 zyp_java_net 阅读(180) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?