vue3+el-table實現行列轉換
行列轉換
為啥會出現行列轉換的問題呢?因為用戶想看的是一張多列的大表單,但是數據庫裡面保存的是“單列”的數據。於是就需要做一個轉換,這樣客戶看得更方便清晰。
SQL有一種寫法可以支持這種行列轉換,但是寫起來比如繞,不便於理解。
所以我個人還是傾向於在前端實現轉換的操作,因為可以節省後端的性能資源。
這裡以成績單為例演示一下具體的實現方式。
分析成績單的組成
這個又愛又恨的東東想必大傢都不會陌生,這是一個比較典型的需要行列轉換的情景。
先回顧一下成績單的樣子:(圖片來自於網絡,侵刪)
成績單
分析一下,可以得到如下幾個基本元素:
- 學科:語文、數學、物理、化學等。確定列數
- 學生:確定行數
- 年級班級:一年一班、一年二班等。分類依據
- 考試:期中考試、期末考試等。分類依據
- 成績:確定數據內容。
元素的分類:
- 分類依據:指的是生成一個大表單的查詢條件,同類別的數據匯總成一個大表單。
- 學科:確定列數,科目越多列數也就越多。
- 學生:確定行數,學生越多行數也就越多。
我們再看看數據庫裡的設計,一般會設計幾個基礎表和一個成績表。
- 學科表
- 學生表
- 班級表
- 考試表
- 成績表
篇幅有限,具體字段就不介紹瞭,說全的話也是挺復雜的。
用 vue3 + el-table 做的成績單
行列轉換後的成績單
學科、學生、總分、平均分、最高分、最低分、名次都有瞭。還可以各種排序。下面我們來看看是如何實現的。
前端模擬數據
我們在前端模擬一下數據。(簡化模式)
// 學科 —— 確定列 const subject = [ { id: 1, name: '數學' }, { id: 2, name: '語文' }, { id: 3, name: '物理' }, { id: 4, name: '化學' } ] // 學生 —— 確定行 const student = [ { id: 1, name: '張三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }, { id: 4, name: '趙六' } ] // 班級 —— 分類依據 const classes = [ { id: 1, name: '一年一班' }, { id: 2, name: '一年二班' } ] // 考試 —— 分類依據 const exam = [ { id: 1, name: '期中考試' }, { id: 2, name: '期末考試' } ] // 成績 —— 確定內容 const reportCard = [ // 序號 考試ID 班級ID 學生ID 科目ID 成績 { id: 1, examId: 1, classId: 1, studentId: 1, subjectId: 1, score: 100 }, { id: 2, examId: 1, classId: 1, studentId: 1, subjectId: 2, score: 98 }, { id: 3, examId: 1, classId: 1, studentId: 1, subjectId: 3, score: 90 }, { id: 4, examId: 1, classId: 1, studentId: 2, subjectId: 1, score: 90 }, { id: 5, examId: 1, classId: 1, studentId: 2, subjectId: 2, score: 90 }, { id: 6, examId: 1, classId: 1, studentId: 2, subjectId: 3, score: 40 }, { id: 7, examId: 1, classId: 1, studentId: 3, subjectId: 1, score: 30 }, { id: 8, examId: 1, classId: 1, studentId: 3, subjectId: 2, score: 90 }, { id: 8, examId: 1, classId: 1, studentId: 3, subjectId: 3, score: 40 }, { id: 9, examId: 1, classId: 1, studentId: 4, subjectId: 1, score: 64 }, { id: 8, examId: 1, classId: 1, studentId: 4, subjectId: 2, score: 90 }, { id: 9, examId: 1, classId: 1, studentId: 4, subjectId: 3, score: 70 } ]
使用 el-table 生成成績單
element-plus 提供瞭一個很強大的 表格組件 —— el-table,可以實現很多基礎功能,比如排序、調整寬度、設置顏色、篩選等功能。那麼我們可以使用這個組件來實現成績單。
確定表頭
行列轉換的一大特點是,表頭(有多少列)並不是固定的,而是需要動態生成的。
以成績單為例,表頭列數由學科決定,學科越多,表頭也就越多。
首先我們按照 el-table 的要求設置一下表頭:
/** * 根據學科建立表頭 * * 學號、姓名、【各個學科】、總分、平均分、名次 */ const createTableHead = () => { // 添加學生 const head = [ { prop: 'id', label: '學號', width: 120 }, { prop: 'name', label: '姓名', width: 120 }] // 添加科目 for (const key in subject) { const sub = subject[key] head.push({ prop: 'sub_' + sub.id, label: sub.name, width: 120 }) } head.push({ prop: 'totalScore', label: '總分', width: 120 }) head.push({ prop: 'averageScore', label: '平均分', width: 120 }) head.push({ prop: 'ranking', label: '名次', width: 120 }) return head }
這裡表頭分為兩種,一個是固定的,一個是根據科目動態生成的。
我們采用簡單粗暴的方式,先直接添加固定列,然後遍歷學科添加動態列。
確定數據
表頭確定好瞭之後,我們需要確定一下 data 部分,正式開始行列轉換。
還是按照 el-table 的需要來定義一下數據格式:
{ id: 1, name: '張三', sub_1: 100, sub_2: 100, ... totalScore: 200, averageScore: 100, ranking: 1 }
中間可以有各個學科,屬性命名規則: 前綴 “sub_” + 學科ID 。
這樣便於後續添加數據。
遍歷成績表來填充數據。
/** * 行列轉換 */ const rowToCol = () => { // 對象形式的成績列表 const _code = {} // 數組形式的成績列表 const _arr = [] // 遍歷成績單 for (const key in reportCard) { const rep = reportCard[key] if(typeof _code[rep.studentId] === 'undefined') { // 沒有記錄。創建一行學生成績,加入固定列的數據 _code[rep.studentId] = { id: rep.studentId, // 學生ID name: (student.filter((item)=>item.id === rep.studentId)[0] || {name:''}).name, // 根據id獲取學生姓名 totalScore: 0, // 學生的各科總分,後續修改 averageScore: 0, // 學生各科的平均分,後續修改 ranking: 1 // 名次,後續修改 } } // 記錄各科分數 _code[rep.studentId]['sub_' + rep.subjectId] = rep.score } // 計算總分、平均分 下面介紹 // 計算名次 下面介紹 return _arr }
遍歷成績單的數據,先依據學生ID建立一個對象,然後根據學科確定各科成績。
計算學生的總分和平均分
一般成績單還需要得到學生的總分和平均分,我們可以遍歷成績統計總分和平均分。
... // 計算總分、平均分 for(const key in _code) { const code = _code[key] // 遍歷學科 let total = 0 let ave = 0 let count = 0 for (const key in subject) { const fenshu = code['sub_' + subject[key].id] if (typeof fenshu !== 'undefined') { // 有成績,計算總分和平均分 count++ total += fenshu ave = Math.floor(total / count * 10) / 10 // 保留一位小數。 // 如果保留兩位小數的話可以這樣 (total / count ).toFixed(2) } } code.totalScore = total code.averageScore = ave // 對象的數據轉為數組的數據 _arr.push(code) }
計算排名
成績單有瞭,我們還需要做一個排名。
如果不需要排名次的話,可以跳過此步驟。
let _ranking = 0 _arr.sort((a, b) => { // 按照總分倒序 return b.totalScore - a.totalScore }).forEach((item) => { // 設置名次 _ranking++ _arr.find((a) => a.id === item.id).ranking = _ranking })
計算各個學科的平均分。
這個也是比較常見的需求。
el-table 提供瞭自動求和的功能,同時也提供瞭自定義求和的方法,我們可以利用他來實現學科的平均分。
下面是官網給出的方法,本來是求和的,稍微修改一下就可以得到學科的平均分。
// 計算各學科的平均分 let i = 0 const getSummaries = (param) => { i++ if (i < 7) return [] const { columns, data } = param; const sums = []; columns.forEach((column, index) => { if (index === 0) { sums[index] = '平均分' return } const values = data.map(item => Number(item[column.property])); if (!values.every(value => isNaN(value))) { sums[index] = values.reduce((prev, curr) => { const value = Number(curr) if (!isNaN(value)) { return prev + curr } else { return prev } }, 0); sums[index] = Math.floor(sums[index] / values.length * 10) / 10 } else { sums[index] = 'N/A'; } }) return sums
跟蹤瞭一下,發現這個函數會被調用七次,這個次數似乎和行數、列數沒有關系。
而且前幾次的 return 沒啥作用,最後一次才會有效,所以加瞭一個判斷。
記錄各個科目的最高分和最低分
一張成績單還可以計算各種數據,比如看看學科的最高分數、最低分數。
一般也可以關註一下這些統計數據,同樣我麼可以用自定義求和的方法來實現需求,我麼改進一下 getSummaries。
// 計算各科目的平均分、最高分和最低分 const getSummaries = ({ columns }) => { i++ if (i < 7) return [] const sums = []; columns.forEach((item, index) => { if (item.property.indexOf('sub_') >= 0 ){ const subjectId = item.property.replace('sub_', '')*1 // 學科,計算平均值 let ave = 0 let sum = 0 let max = 0 let min = 99999 const _arr = reportCard.filter((r) => r.subjectId === subjectId) _arr.forEach((item, index) => { sum += item.score // 求和 if (max < item.score) max = item.score // 記錄最高分 if (min > item.score) min = item.score // 記錄最低分 }) if (_arr.length === 0) { sums[index] = '-' // 沒有成績 } else { // 計算平均分 ave = Math.floor(sum/_arr.length * 10) / 10 sums[index] = `${ave}(${max}-${min})` } } else { // 不計算 sums[index] = '' } }) sums[0] = '統計' return sums }
這次我麼直接同 reportCard 來統計平均分、最高分和最低分。
同理,我麼還可以統計一下及格人數、各個分段的人數。
增加排序功能
這個是 el-table 自帶的功能,我們加上就好。
<el-table :data="tableData" style="width: 100%;height: 300px;" :default-sort = "{prop: 'totalScore', order: 'descending'}" :row-class-name="tableRowClassName" border show-summary :summary-method="getSummaries" > <el-table-column v-for="(item, index) in tableHead" :key="'s'+ index" fixed sortable :prop="item.prop" :label="item.label" :width="item.width"> </el-table-column> </el-table>
el-table 的屬性設置。default-sort 默認按照 總分倒序顯示。
增加色彩區分
如果想要把平均分低於60的著重表示出來怎麼辦?el-table 也提供瞭功能,我們做一下判斷設置css即可。
// 顏色 const tableRowClassName = ({row, rowIndex}) => { if (row.averageScore < 60) { // 平均分不及格的同學 return 'warning-row'; } else if (row.averageScore > 95) { // 平均分超過95的同學 return 'success-row'; } return ''; }
源碼
https://gitee.com/naturefw/nf-vite2-element
小結
優點:
- 不需要後端做行列轉換,隻需要提供基礎數據即可,節省後端性能資源。
- 功能比較全面,我能想到的基本都加上瞭。
缺點:
- 沒能做成通用的形式,其他行列轉換的需求還需要編寫代碼。
到此這篇關於vue3+el-table實現行列轉換的文章就介紹到這瞭,更多相關vue3 行列轉換內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Vue使用ElemenUI對table的指定列進行合算的方法
- ElementUI對table的指定列進行合算
- vue表格(table)計算總計方式
- element 表格多級表頭子列固定的實現
- Element UI中table單元格合並的解決過程