VUE專案踩坑記錄
1、虛擬DOM不渲染資料
問題描述:訊息已讀和未讀的功能,點選訊息,訊息會變成已讀,重新請求資料,在重新請求資料前會先清空舊資料,但是因為兩次的資料一樣,導致vue的diff演算法預設不更新檢視,使用 this.$set
和 this.$forceUpdate
等方法都不能解決問題
解決方法:在v-for的父元素加個v-if v-if=“list.length > 0”
。 vue
的 diff 演算法會監測陣列變化,響應式地渲染列表。
2、複雜物件陣列去重
使用reduce方法進行復雜物件陣列去重
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的數字開頭(含小數)
const isSum = /^(-|\+)?\d+(\.\d+)?$/
cosnt sunA = 1console.log(isSum.test(sumA))
4、數字格式化:千分位及並保留兩位小數(多用於金額格式化)
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; }
以上適用於無負數格式化,如果金額有負數,需要判斷是否為負數
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 物件轉換為字串,並返回結果
var d=new Date(); var n=d.toLocaleString();// 2021/11/29 下午4:24:49
除此之外:還可以將數字變成千分位格式
let num=12345678; console.log(num.toLocaleString()); // 12,345,678
還可以將時間轉換為 24 小時制
// 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表格樣式需要載入行內以物件的形式存
<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去除
去掉表格預設的高亮效果
.el-table__row:hover > td { background-color: transparent; }
修改表格下劃線顏色
/deep/.el-table td.el-table__cell, .el-table th.el-table__cell.is-leaf { border-bottom: 1px solid #e6e6e6; }
去除進度條的圓角
/deep/.el-progress-bar__outer { border-radius: 0; } /deep/.el-progress-bar__inner { border-radius: 0; } /deep/.el-progress-bar__inner { ">#5b8ff9; }
ElementUI普通表格取消高亮
/deep/.el-table__row:hover > td { background-color: transparent; }
如果表格有使用fixed進行固定,使用上面的方法會導致部分fixed固定的列的高亮無法取消,推薦使用下面的方法
/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頁面接收不到引數
解決方法:
// 在傳送方A頁面使用this.this.$nextTick(()=>{}) this.$nextTick(() => { bus.$emit('eleOpen', this.openEle) console.log('bus.$emit') }) // 在接收方B頁面正常接收即可
2、bus.$on多次觸發的問題
這個$on事件是不會自動清楚銷燬的,需要我們手動來銷燬
// 在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內處理即可
// 在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方法
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
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的資料夾
在該檔案內輸入
module.exports = { publicPath: './' }重新打包即可
10、vue路由傳參
專案中很多情況下都需要進行路由之間的傳值,可以使用sessionstorage/localstorage/cookie 進行離線快取儲存也可以,用vuex也可以,如果只是簡單的傳值可以使用vue自帶的路由傳參方法
參考官方文件:https://router.vuejs.org/zh/guide/essentials/passing-props.html
想要實現點選當前頁的某個按鈕或連結跳轉到另外一個頁面去,並將某個引數帶過去
<div @click="insurance(123)">我要傳參</div>
第一種方法 頁面重新整理資料不會丟失
methods:{ insurance(id) { //直接呼叫$router.push 實現攜帶引數的跳轉 this.$router.push({ path: `/particulars/${id}`, }) }
需要對應路由配置如下:可以看出需要在path中新增/:id來對應 $router.push 中path攜帶的引數。在子元件中可以使用來獲取傳遞的引數值
{ path: '/particulars/:id', name: 'particulars', component: particulars }
目標頁面獲取引數方法:
this.$route.params.id
第二種方法 頁面重新整理資料會丟失 類似post請求
通過路由屬性中的name來確定匹配的路由,通過params來傳遞引數。
methods:{ insurance(id) { this.$router.push({ name: 'particulars', params: { id: id } }) }
對應路由配置: 注意這裡不需要使用:/id來傳遞引數了,因為元件中,已經使用params來攜帶引數了。
{ path: '/particulars', name: 'particulars', component: particulars }
目標頁面獲取引數方法:
this.$route.params.id
第三種方法 query傳遞的引數會顯示在url後面以【?id=?】的形式,類似get請求
使用path來匹配路由,然後通過query來傳遞引數
methods:{ insurance(id) { this.$router.push({ path: '/particulars', query: { id: id } }) }
對應路由配置:
{ path: '/particulars', name: 'particulars', component: particulars }
目標頁面獲取引數方法:
this.$route.query.id
11、將後臺返回的資料進行相鄰單元格合併
html內容區
<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方法區
<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進行配置
devServer: { proxy: { '/api': { target: 'http://www.xxx.com.cn/', // 要請求的API changeOrigin: true, // 是否開啟跨域 pathRewrite: { '^/api': '' // 重寫路由 } } } }
2.請求時進行字尾請求,比如請求/menu
則請求/api/menu
即可