vue怎样实现学生成绩表单行列转换的效果
Admin 2022-06-01 群英技术资讯 385 次浏览
为啥会出现行列转换的问题呢?因为用户想看的是一张多列的大表单,但是数据库里面保存的是“单列”的数据。于是就需要做一个转换,这样客户看得更方便清晰。
SQL有一种写法可以支持这种行列转换,但是写起来比如绕,不便于理解。
所以我个人还是倾向于在前端实现转换的操作,因为可以节省后端的性能资源。
这里以成绩单为例演示一下具体的实现方式。
这个又爱又恨的东东想必大家都不会陌生,这是一个比较典型的需要行列转换的情景。
先回顾一下成绩单的样子:(图片来自于网络,侵删)
成绩单
分析一下,可以得到如下几个基本元素:
元素的分类:
我们再看看数据库里的设计,一般会设计几个基础表和一个成绩表。
篇幅有限,具体字段就不介绍了,说全的话也是挺复杂的。
行列转换后的成绩单
学科、学生、总分、平均分、最高分、最低分、名次都有了。还可以各种排序。下面我们来看看是如何实现的。
我们在前端模拟一下数据。(简化模式)
// 学科 ―― 确定列 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 } ]
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
小结
优点:
缺点:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
怎样用vue实现一个无缝的轮播效果?对于轮播效果的应用场景有很多,我们经常能在网站的头部看到,轮播功能也是比较实用的,但是一些新手在刚接触轮播时,实现的轮播效果有缝隙,效果并不好看,对此下面小编就给大家分享实现无缝轮播效果的代码。
这篇文章主要介绍了Javascript闭包的使用场景, 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,闭包就是能够读取其他函数内部变量的函数。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁,下面一起进入文化在哪个了解文章内容
这篇文章主要为大家详细介绍了javascript实现随机抽奖功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
这篇文章主要和大家分享用vue实现切换图片效果的小案例,点击按钮可以实现向前切换,向后切换和顺序切换的效果。下面我们就来看看具体的实现代码。
我们在做网站开发时,登录页面很多情况下是可以用手机号接收短信验证码,本文主要介绍了vue_drf实现短信验证码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008