vue3+el-table實(shí)現(xiàn)行列轉(zhuǎn)換
行列轉(zhuǎn)換
為啥會(huì)出現(xiàn)行列轉(zhuǎn)換的問(wèn)題呢?因?yàn)橛脩粝肟吹氖且粡埗嗔械拇蟊韱危菙?shù)據(jù)庫(kù)里面保存的是“單列”的數(shù)據(jù)。于是就需要做一個(gè)轉(zhuǎn)換,這樣客戶看得更方便清晰。
SQL有一種寫(xiě)法可以支持這種行列轉(zhuǎn)換,但是寫(xiě)起來(lái)比如繞,不便于理解。
所以我個(gè)人還是傾向于在前端實(shí)現(xiàn)轉(zhuǎn)換的操作,因?yàn)榭梢怨?jié)省后端的性能資源。
這里以成績(jī)單為例演示一下具體的實(shí)現(xiàn)方式。
分析成績(jī)單的組成
這個(gè)又愛(ài)又恨的東東想必大家都不會(huì)陌生,這是一個(gè)比較典型的需要行列轉(zhuǎn)換的情景。
先回顧一下成績(jī)單的樣子:(圖片來(lái)自于網(wǎng)絡(luò),侵刪)
成績(jī)單
分析一下,可以得到如下幾個(gè)基本元素:
- 學(xué)科:語(yǔ)文、數(shù)學(xué)、物理、化學(xué)等。確定列數(shù)
- 學(xué)生:確定行數(shù)
- 年級(jí)班級(jí):一年一班、一年二班等。分類依據(jù)
- 考試:期中考試、期末考試等。分類依據(jù)
- 成績(jī):確定數(shù)據(jù)內(nèi)容。
元素的分類:
- 分類依據(jù):指的是生成一個(gè)大表單的查詢條件,同類別的數(shù)據(jù)匯總成一個(gè)大表單。
- 學(xué)科:確定列數(shù),科目越多列數(shù)也就越多。
- 學(xué)生:確定行數(shù),學(xué)生越多行數(shù)也就越多。
我們?cè)倏纯磾?shù)據(jù)庫(kù)里的設(shè)計(jì),一般會(huì)設(shè)計(jì)幾個(gè)基礎(chǔ)表和一個(gè)成績(jī)表。
- 學(xué)科表
- 學(xué)生表
- 班級(jí)表
- 考試表
- 成績(jī)表
篇幅有限,具體字段就不介紹了,說(shuō)全的話也是挺復(fù)雜的。
用 vue3 + el-table 做的成績(jī)單
行列轉(zhuǎn)換后的成績(jī)單
學(xué)科、學(xué)生、總分、平均分、最高分、最低分、名次都有了。還可以各種排序。下面我們來(lái)看看是如何實(shí)現(xiàn)的。
前端模擬數(shù)據(jù)
我們?cè)谇岸四M一下數(shù)據(jù)。(簡(jiǎn)化模式)
// 學(xué)科 —— 確定列 const subject = [ { id: 1, name: '數(shù)學(xué)' }, { id: 2, name: '語(yǔ)文' }, { id: 3, name: '物理' }, { id: 4, name: '化學(xué)' } ] // 學(xué)生 —— 確定行 const student = [ { id: 1, name: '張三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }, { id: 4, name: '趙六' } ] // 班級(jí) —— 分類依據(jù) const classes = [ { id: 1, name: '一年一班' }, { id: 2, name: '一年二班' } ] // 考試 —— 分類依據(jù) const exam = [ { id: 1, name: '期中考試' }, { id: 2, name: '期末考試' } ] // 成績(jī) —— 確定內(nèi)容 const reportCard = [ // 序號(hào) 考試ID 班級(jí)ID 學(xué)生ID 科目ID 成績(jī) { 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 生成成績(jī)單
element-plus 提供了一個(gè)很強(qiáng)大的 表格組件 —— el-table,可以實(shí)現(xiàn)很多基礎(chǔ)功能,比如排序、調(diào)整寬度、設(shè)置顏色、篩選等功能。那么我們可以使用這個(gè)組件來(lái)實(shí)現(xiàn)成績(jī)單。
確定表頭
行列轉(zhuǎn)換的一大特點(diǎn)是,表頭(有多少列)并不是固定的,而是需要?jiǎng)討B(tài)生成的。
以成績(jī)單為例,表頭列數(shù)由學(xué)科決定,學(xué)科越多,表頭也就越多。
首先我們按照 el-table 的要求設(shè)置一下表頭:
/** * 根據(jù)學(xué)科建立表頭 * * 學(xué)號(hào)、姓名、【各個(gè)學(xué)科】、總分、平均分、名次 */ const createTableHead = () => { // 添加學(xué)生 const head = [ { prop: 'id', label: '學(xué)號(hào)', 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 }
這里表頭分為兩種,一個(gè)是固定的,一個(gè)是根據(jù)科目動(dòng)態(tài)生成的。
我們采用簡(jiǎn)單粗暴的方式,先直接添加固定列,然后遍歷學(xué)科添加動(dòng)態(tài)列。
確定數(shù)據(jù)
表頭確定好了之后,我們需要確定一下 data 部分,正式開(kāi)始行列轉(zhuǎn)換。
還是按照 el-table 的需要來(lái)定義一下數(shù)據(jù)格式:
{ id: 1, name: '張三', sub_1: 100, sub_2: 100, ... totalScore: 200, averageScore: 100, ranking: 1 }
中間可以有各個(gè)學(xué)科,屬性命名規(guī)則: 前綴 “sub_” + 學(xué)科ID 。
這樣便于后續(xù)添加數(shù)據(jù)。
遍歷成績(jī)表來(lái)填充數(shù)據(jù)。
/** * 行列轉(zhuǎn)換 */ const rowToCol = () => { // 對(duì)象形式的成績(jī)列表 const _code = {} // 數(shù)組形式的成績(jī)列表 const _arr = [] // 遍歷成績(jī)單 for (const key in reportCard) { const rep = reportCard[key] if(typeof _code[rep.studentId] === 'undefined') { // 沒(méi)有記錄。創(chuàng)建一行學(xué)生成績(jī),加入固定列的數(shù)據(jù) _code[rep.studentId] = { id: rep.studentId, // 學(xué)生ID name: (student.filter((item)=>item.id === rep.studentId)[0] || {name:''}).name, // 根據(jù)id獲取學(xué)生姓名 totalScore: 0, // 學(xué)生的各科總分,后續(xù)修改 averageScore: 0, // 學(xué)生各科的平均分,后續(xù)修改 ranking: 1 // 名次,后續(xù)修改 } } // 記錄各科分?jǐn)?shù) _code[rep.studentId]['sub_' + rep.subjectId] = rep.score } // 計(jì)算總分、平均分 下面介紹 // 計(jì)算名次 下面介紹 return _arr }
遍歷成績(jī)單的數(shù)據(jù),先依據(jù)學(xué)生ID建立一個(gè)對(duì)象,然后根據(jù)學(xué)科確定各科成績(jī)。
計(jì)算學(xué)生的總分和平均分
一般成績(jī)單還需要得到學(xué)生的總分和平均分,我們可以遍歷成績(jī)統(tǒng)計(jì)總分和平均分。
... // 計(jì)算總分、平均分 for(const key in _code) { const code = _code[key] // 遍歷學(xué)科 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') { // 有成績(jī),計(jì)算總分和平均分 count++ total += fenshu ave = Math.floor(total / count * 10) / 10 // 保留一位小數(shù)。 // 如果保留兩位小數(shù)的話可以這樣 (total / count ).toFixed(2) } } code.totalScore = total code.averageScore = ave // 對(duì)象的數(shù)據(jù)轉(zhuǎn)為數(shù)組的數(shù)據(jù) _arr.push(code) }
計(jì)算排名
成績(jī)單有了,我們還需要做一個(gè)排名。
如果不需要排名次的話,可以跳過(guò)此步驟。
let _ranking = 0 _arr.sort((a, b) => { // 按照總分倒序 return b.totalScore - a.totalScore }).forEach((item) => { // 設(shè)置名次 _ranking++ _arr.find((a) => a.id === item.id).ranking = _ranking })
計(jì)算各個(gè)學(xué)科的平均分。
這個(gè)也是比較常見(jiàn)的需求。
el-table 提供了自動(dòng)求和的功能,同時(shí)也提供了自定義求和的方法,我們可以利用他來(lái)實(shí)現(xiàn)學(xué)科的平均分。
下面是官網(wǎng)給出的方法,本來(lái)是求和的,稍微修改一下就可以得到學(xué)科的平均分。
// 計(jì)算各學(xué)科的平均分 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
跟蹤了一下,發(fā)現(xiàn)這個(gè)函數(shù)會(huì)被調(diào)用七次,這個(gè)次數(shù)似乎和行數(shù)、列數(shù)沒(méi)有關(guān)系。
而且前幾次的 return 沒(méi)啥作用,最后一次才會(huì)有效,所以加了一個(gè)判斷。
記錄各個(gè)科目的最高分和最低分
一張成績(jī)單還可以計(jì)算各種數(shù)據(jù),比如看看學(xué)科的最高分?jǐn)?shù)、最低分?jǐn)?shù)。
一般也可以關(guān)注一下這些統(tǒng)計(jì)數(shù)據(jù),同樣我么可以用自定義求和的方法來(lái)實(shí)現(xiàn)需求,我么改進(jìn)一下 getSummaries。
// 計(jì)算各科目的平均分、最高分和最低分 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 // 學(xué)科,計(jì)算平均值 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] = '-' // 沒(méi)有成績(jī) } else { // 計(jì)算平均分 ave = Math.floor(sum/_arr.length * 10) / 10 sums[index] = `${ave}(${max}-${min})` } } else { // 不計(jì)算 sums[index] = '' } }) sums[0] = '統(tǒng)計(jì)' return sums }
這次我么直接同 reportCard 來(lái)統(tǒng)計(jì)平均分、最高分和最低分。
同理,我么還可以統(tǒng)計(jì)一下及格人數(shù)、各個(gè)分段的人數(shù)。
增加排序功能
這個(gè)是 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 的屬性設(shè)置。default-sort 默認(rèn)按照 總分倒序顯示。
增加色彩區(qū)分
如果想要把平均分低于60的著重表示出來(lái)怎么辦?el-table 也提供了功能,我們做一下判斷設(shè)置css即可。
// 顏色 const tableRowClassName = ({row, rowIndex}) => { if (row.averageScore < 60) { // 平均分不及格的同學(xué) return 'warning-row'; } else if (row.averageScore > 95) { // 平均分超過(guò)95的同學(xué) return 'success-row'; } return ''; }
源碼
https://gitee.com/naturefw/nf-vite2-element
小結(jié)
優(yōu)點(diǎn):
- 不需要后端做行列轉(zhuǎn)換,只需要提供基礎(chǔ)數(shù)據(jù)即可,節(jié)省后端性能資源。
- 功能比較全面,我能想到的基本都加上了。
缺點(diǎn):
- 沒(méi)能做成通用的形式,其他行列轉(zhuǎn)換的需求還需要編寫(xiě)代碼。
到此這篇關(guān)于vue3+el-table實(shí)現(xiàn)行列轉(zhuǎn)換的文章就介紹到這了,更多相關(guān)vue3 行列轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue組件從開(kāi)發(fā)到發(fā)布的實(shí)現(xiàn)步驟
這篇文章主要介紹了vue組件從開(kāi)發(fā)到發(fā)布的實(shí)現(xiàn)步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11vue使用js-audio-recorder實(shí)現(xiàn)錄音功能
這篇文章主要為大家詳細(xì)介紹了vue如何使用js-audio-recorder實(shí)現(xiàn)錄音功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12vue中實(shí)現(xiàn)拖拽排序功能的詳細(xì)教程
在業(yè)務(wù)中列表拖拽排序是比較常見(jiàn)的需求,下面這篇文章主要給大家介紹了關(guān)于vue中實(shí)現(xiàn)拖拽排序功能的詳細(xì)教程,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08vue3如何使用vant-picker封裝省市二級(jí)聯(lián)動(dòng)
這篇文章主要介紹了vue3如何使用vant-picker封裝省市二級(jí)聯(lián)動(dòng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10用VueJS寫(xiě)一個(gè)Chrome瀏覽器插件的實(shí)現(xiàn)方法
這篇文章主要介紹了用VueJS寫(xiě)一個(gè)Chrome瀏覽器插件的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動(dòng)態(tài)合并方式
這篇文章主要介紹了vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動(dòng)態(tài)合并方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02