1、業務背景
使用vue+element開發報表功能時,需要列表上某列的超連結按鈕彈窗展示,在彈窗的el-table列表某列中再次使用超連結按鈕點開彈窗,以此類推多表格彈窗巢狀,本文以彈窗兩次為例
最終效果如下示例頁面
2、具體實現和問題丟擲
<template>
<div class="el_main">
<el-table
stripe
style="width: 100%"
v-loading="loading"
row-key="Id"
:data="list"
>
<el-table-column label="ID" prop="Id" min-width="3"> </el-table-column>
<el-table-column label="型別" prop="Type" min-width="5">
<template slot-scope="scope">
{{ formatTaskType(scope.row.Type) }}
</template>
</el-table-column>
<el-table-column label="詳情" prop="TaskTitle" min-width="10" show-overflow-tooltip="true"></el-table-column>
<el-table-column
label="詳情彈窗"
min-width="3">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text">檢視</el-button>
</template>
</el-table-column>
<el-table-column label="建立時間" prop="AddTime" min-width="5">
<template slot-scope="scope" v-if="scope.row.AddTime">
{{ (scope.row.AddTime * 1000) | formatDate(2) }}
</template>
</el-table-column>
</el-table>
</div>
<!-- 詳情彈窗 -->
<el-dialog
title="詳情彈窗"
:visible.sync="detailInfoDialogVisible"
append-to-body
width="50%">
<el-table
stripe
style="width: 100%"
v-loading="loading"
row-key="Id"
height="300" max-height="650"
:data="detailInfo">
<el-table-column label="ID" prop="TaskId" min-width="80"></el-table-column>
<el-table-column label="名稱" prop="TaskName" min-width="65"></el-table-column>
<el-table-column label="成功數量" prop="SuccessNum" min-width="22"></el-table-column>
<el-table-column label="失敗數量" prop="ErrorNum" min-width="22"></el-table-column>
<el-table-column label="狀態列表" min-width="22">
<template slot-scope="scope">
<el-button @click="handleStatusListClick(scope.row)" type="text">檢視</el-button>
</template>
</el-table-column>
<el-table-column label="佇列列表" min-width="30">
<template slot-scope="scope">
<el-button @click="handleQueueDataClick(scope.row)" type="text">檢視</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<!-- 狀態列表彈窗 -->
<el-dialog
title="狀態彈窗"
:visible.sync="statusListDialogVisible"
append-to-body
width="30%">
<el-table
stripe
style="width: 100%"
v-loading="loading"
row-key="Id"
height="300" max-height="300"
:data="statusListInfo">
<el-table-column label="ID" prop="Id" min-width="80" show-overflow-tooltip="true"> </el-table-column>
<el-table-column label="標題" prop="Title" min-width="80" show-overflow-tooltip="true"></el-table-column>
<el-table-column label="返回資訊" prop="Msg" min-width="80" show-overflow-tooltip="true"></el-table-column>
</el-table>
</el-dialog>
<!-- 佇列列表彈窗 -->
<el-dialog
title="佇列彈窗"
:visible.sync="queueDataDialogVisible"
append-to-body
width="30%">
<el-table
stripe
style="width: 100%"
v-loading="loading"
row-key="Id"
height="300" max-height="300"
:data="queueDataInfo">
<el-table-column label="ID" prop="Id" min-width="80" show-overflow-tooltip="true"> </el-table-column>
<el-table-column label="名稱" prop="Name" min-width="80" show-overflow-tooltip="true"></el-table-column>
</el-table>
</el-dialog>
</template>
<script type="text/ecmascript-6">
import { GetXXXReportList, ExportXXXReportList } from '@/api/reportManage'
const urlQuery = [
'id|number',
'type|number',
'currPage|number',
'pageSize|number',
]
export default {
components: {
},
data () {
return {
id: '',
type: '',
collectTime: '',
loading: false,
list: [],
currPage: 1,
pageSize: 10,
counts: 0,
detailInfo: [], // 詳情彈窗
detailInfoDialogVisible: false,
statusListInfo: [], // 狀態列表彈窗
statusListDialogVisible: false,
queueDataInfo: [], // 佇列列表彈窗
queueDataDialogVisible: false,
typeArray: [
{
value: 1,
label: '型別一',
},
{
value: 2,
label: '分類二',
},
{
value: 3,
label: '分類三',
},
{
value: 4,
label: '分類四',
},
{
value: 5,
label: '分類五',
},
{
value: 6,
label: '分類六',
},
],
exportLoading: false,
}
},
created () {
this._getList(true)
},
methods: {
async _getList (init = false) {
this.loading = true
if (init) {
this.currPage = 1
}
let startTime, endTime
if (this.collectTime) {
startTime = this.collectTime[0] / 1000
endTime = this.collectTime[1] / 1000 + 86399
}
this._setQuery(urlQuery)
try {
const data = await GetXXXReportList({
Id: this.id || 0,
StartTime: startTime || 0,
EndTime: endTime || 0,
Type: this.type || 0,
CurrPage: this.currPage,
PageSize: this.pageSize,
})
this.list = data.List
this.counts = data.Counts
} catch (error) {
this.counts = 0
this.list = []
}
this.loading = false
},
search () {
this._getList(true)
},
reset () {
this.id = ''
this.type = ''
this.collectTime = ''
this.list = []
this.counts = 0
this._getList(true)
},
pageChange () {
this._getList()
},
pageSizeChange (val) {
this.pageSize = val
this._getList(true)
},
handleClick (row) {
if (row.Type === 1) {
this.detailInfoDialogVisible = true
this.detailInfo = row.detailInfo
} else if (row.Type === 2) {
this.xxxDialogVisible = true
this.xxxInfo = row.xxx
} else if (row.Type === 3) {
this.xxxDialogVisible = true
this.xxxInfo = row.xxx
}
},
handleStatusListClick (row) {
this.statusListDialogVisible = true
this.statusListInfo = row.StatusList
},
handleQueueDataClick (row) {
this.queueDataDialogVisible = true
this.queueDataInfo = row.queueData
},
// 匯出
async exportData () {
this.exportLoading = true
let startTime, endTime
if (this.collectTime) {
startTime = this.collectTime[0] / 1000
endTime = this.collectTime[1] / 1000 + 86399
}
try {
const data = await ExportXXXReportList({
Id: this.id || 0,
StartTime: startTime || 0,
EndTime: endTime || 0,
Type: this.type || 0,
})
var raw = window.atob(data)
var uInt8Array = new Uint8Array(data.length)
for (var i = 0; i < raw.length; i++) {
uInt8Array[i] = raw.charCodeAt(i)
}
const url = window.URL.createObjectURL(new Blob([ uInt8Array ], { type: 'application/vnd.ms-excel' }))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', 'xxxx報表.xlsx')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
} catch (error) {
this.exportLoading = false
}
this.exportLoading = false
},
},
}
</script>
<style lang="scss">
</style>
3、分析問題
這裡有幾個可能的原因和建議來解決這個問題:
①資料問題:首先確保你的資料來源是正確的。檢查你的表格資料是否有任何錯誤或遺漏。
②巢狀表格的渲染時機:如果你的巢狀表格(子表格)是在父表格的某一行展開時才渲染的,那麼你需要確保子表格的資料在正確的時機進行載入。如果資料載入過早,可能會導致異常。
③彈窗的v-if與v-show:如果你使用了v-if來控制彈窗的顯示與隱藏,那麼每次彈窗開啟都會重新渲染彈窗內的內容。這可能會導致表格的重新初始化,使用v-show可能會避免這個問題。但需要注意的是,v-show只是在視覺上隱藏元素,元素仍然會被渲染。
④表格的key:如前面所說,Vue使用key來追蹤節點的身份。如果在巢狀表格的場景中,你使用了相同的key,可能會導致身份識別混亂。確保每個表格都有一個獨特的key。
⑤樣式衝突:確保沒有其他樣式影響到表格或彈窗的正常顯示。特別是當你使用了自定義樣式或與Element UI樣式衝突的其他UI庫時。
⑥元件版本:確保你使用的Element UI是最新的版本。舊版本可能存在已知的錯誤,而在新版本中可能已經被修復。
4、解決問題
下面我從表格的key角度解決下問題
1)嘗試給每個彈窗的el-table加個key -- 未解決資料錯亂的問題
示例程式碼如下:
<el-table
:key="Id"
stripe
style="width: 100%"
v-loading="loading"
row-key="Id"
height="300" max-height="300">
</el-table>
2)嘗試給每個彈窗的el-table加個唯一的key -- 解決資料錯亂的問題
示例程式碼如下:
<el-table
:key="Id"
stripe
style="width: 100%"
v-loading="loading"
row-key="Id"
height="300" max-height="300">
</el-table>
雖然此種方法解決了我們的問題,但是考慮到每次開啟彈窗都會生成隨機數存在一定風險性,具體分析如下:
隨機數改變了每次渲染時的key值,打破了Vue的節點身份追蹤機制。
在這種情況下,由於每次渲染都有一個新的隨機數作為key,Vue會將該元件視為全新的節點,從而重新渲染。這樣可以避免由於身份追蹤導致的問題,例如在巢狀表格場景中可能出現的報錯。
然而,需要注意的是,使用隨機數作為key並不是一個推薦的做法。因為key的主要作用是幫助Vue高效地識別和追蹤節點的身份,以便進行差異化更新。隨機數作為key會破壞這一機制,可能導致效能下降和潛在的問題。
因此,儘管使用隨機數作為key可以解決某些情況下的報錯,但並不是一個優雅的解決方案。更好的方式是仔細排查問題,找到導致報錯的根本原因,並採取相應的措施進行修復。如果實在無法找到其他解決方案,再考慮使用隨機數作為臨時方案。但在長期開發中,仍然建議尋求更合適、更穩定的解決方案。
3)嘗試給每個彈窗的el-table加個唯一的key(固定不是隨機數) -- 解決資料錯亂的問題(推薦)
示例程式碼如下:
<el-table
:key="generateKey(scheduledDataDownloadInfo)"
stripe
header-row-class-name="bos_table_header"
style="width: 100%"
v-loading="loading"
row-key="Id"
height="300" max-height="650"
:data="scheduledDataDownloadInfo">
</el-table>
在methods中新增方法
// 生成唯一的key,可以根據具體情況定義
generateKey (data) {
const uniqueIdentifier = data.map(item => item.Id).join('_')
return `table_${uniqueIdentifier}`
},
至此,更合適、更穩定的解決方案完成,我們開頭提到的問題得以解決。有更好辦法或者見解的同學歡迎評論區留言,互相學習。