前言
先别考虑样式!!!!
最近项目要求按老系统的格式使用vue写一个功能类似的排课插件,系统是零几年开发的哎~
大概是这个样子,需求是,时段可选,表格列名根据所选时段渲染出对应的列,左侧显示对应的课
点击添加按钮时,弹出弹框选择对应的数据,点击提交完成排课
嗯 大概就是这样一个需求
一、创作历程
当时看到这个需求的时候,我微微一笑,心想就TM这? 网上插件应该一堆一堆的,随便找找应该很容易找得到
but 当我翻阅各大论坛找资料的时候发现事情没有想象的那么简单
大多数的课表或者排课插件都是大同小异
大概都是这个样子
第一 固定周期都是周一到周日
第二 固定每天上课的时间 和 课数(就是一天最多上几门课,上午几门下午几门)
第三 每个网格中只能存在一门课程
再回头看看需求,我TM笑了没有一点对得上
所以这不是一个普通的表格可以实现的
没办法既然找不到类似的组件,那就只能自己写了
我们可以将表格分为三块
1.动态表头 2.行名称(对应的课程) 3.表格内数据(具体的排课信息)
实践开始↓↓↓↓↓↓↓↓↓↓↓↓↓
1. 动态表头
因为是基于element ui 所开发的,所以动态表头没什么好讲的
但是
要根据日期获取对应的星期,还是有点意思的,逻辑并不复杂我就直接贴码
第一步 肯定是要获取选中的日期啦
element ui的日期选择器只有开始日期和结束日期,也就是只有两个日期,然而我们需要根据这两个日期获取这个日期区间内的所有日期
getDayAll(starDay, endDay) { // 两个日期之间的所有日期
var arr = []
var dates = []
// 设置两个日期UTC时间
var db = new Date(starDay)
var de = new Date(endDay)
// 获取两个日期GTM时间
var s = db.getTime() - 24 * 60 * 60 * 1000
var d = de.getTime() - 24 * 60 * 60 * 1000
// 获取到两个日期之间的每一天的毫秒数
for (var i = s; i <= d;) {
i = i + 24 * 60 * 60 * 1000
arr.push(parseInt(i))
}
// 获取每一天的时间 YY-MM-DD
for (var j in arr) {
var time = new Date(arr[j])
var year = time.getFullYear(time)
var mouth = (time.getMonth() + 1) >= 10 ? (time.getMonth() + 1) : ('0' + (time.getMonth() + 1))
var day = time.getDate() >= 10 ? time.getDate() : ('0' + time.getDate())
var YYMMDD = year + '-' + mouth + '-' + day
dates.push(YYMMDD)
}
return dates
}
第二步 根据日期获取对应的星期
拿到日期区间后呢,需要根据日期获取对应的星期(参数是单个日期,不是集合)
weekDay(date) { // 根据日期获取对应星期
var dt = new Date(date.split('-')[0], date.split('-')[1] - 1, date = date.split('-')[2])
var weekDay = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
return weekDay[dt.getDay()]
}
这两部完成后呢,就可以封装数据生成动态表头了,具体怎么封装怎么生成动态表头大家可以去element ui官网查看相关文档
2.行名称(对应的课程)
因为这次要做的“表格”不同于一般意义上的表格,第一列居然也是表头
那好,我们把第一列的表头也渲染一下
数据格式是这样的
TableCourseName: [{
CourseName: '思想指导'
}, {
CourseName: '高数'
}, {
CourseName: '英语'
}, {
CourseName: '高精狙射击'
}, {
CourseName: '低精狙射击'
}, {
CourseName: '芭蕾舞蹈'
}, {
CourseName: '天鹅舞蹈1'
}]
然后把数据渲染到组件上(组件用的是Element ui的el-table)可以参考Element ui官方文档
<el-table-column
v-if="CourseNameType"
label="课程名称"
width="130"
fixed
class-name="CourseName"
>
<template slot-scope="{row}">
{{ row.CourseName }}
</template>
</el-table-column>
至此 我们拥有了一个X轴(日期)与Y轴(课程名称) 共同定位的表格
OK 第二步完结
3.表格内数据(具体的排课信息)
先贴一下数据格式
这里如果用层级关系数据的话,对于我的表格来说不是很好渲染,所以我决定把数据设计为平铺的
Curriculum: [{
ClassScheduleCardID: 5,
Data: '2021-05-20',
CourseName: '思想指导',
InstructorName: '张三',
ClassRoom: '一教室11',
ClassTimeSpan: '02:50-03:50'
}, {
ClassScheduleCardID: 3,
Data: '2021-05-20',
CourseName: '思想指导',
InstructorName: '张三',
ClassRoom: '一教室11',
ClassTimeSpan: '02:50-03:50'
}, {
ClassScheduleCardID: 4,
Data: '2021-05-20',
CourseName: '思想指导',
InstructorName: '张三',
ClassRoom: '一教室11',
ClassTimeSpan: '02:50-03:50'
}, {
ClassScheduleCardID: 8,
Data: '2021-05-20',
CourseName: '高数',
InstructorName: '张三',
ClassRoom: '一教室22',
ClassTimeSpan: '02:50-03:50'
}, {
ClassScheduleCardID: 6,
Data: '2021-05-21',
CourseName: '思想指导',
InstructorName: '张三',
ClassRoom: '一教室33',
ClassTimeSpan: '02:50-03:50'
}]
这一步比较有意思,因为每个单元格内的数据并不是一条,会多条数据,所以就需要在渲染数据的时候做一些判断将 X轴(日期)与Y轴(课程名称) 的数据对应起来就好了
我写的比较笨哈,就是讲每条数据的**日期与课程与X轴(日期)与Y轴(课程名称)**的对应数据做匹配,如果匹配成功则显示该数据
这里用到一个element ui的标签 < el-tag > 做了一个简单的样式
具体步骤如下
<el-table-column
v-for="item in column"
:key="item.id"
width="300"
:label="item.label"
>
<template
slot-scope="{row}"
>
<el-button type="success" style="width:100%;" size="mini" plain @click="addCourse(item,row)">添加课程</el-button>
<span v-for=" i in tableData.Curriculum" :key="i.ClassScheduleCardID" style="">
<span
v-if="item.Data === i.Data && row.CourseName === i.CourseName ? true : false"
>
<el-tag
closable
style="height:105px;width:92px"
@close="handleDelete(i, row)"
>
<div>{{ i.ClassTimeSpan }}</div>
<div>{{ i.ClassRoomName }}</div>
<div>{{ i.InstructorName, }}</div>
</el-tag>
</span>
</span>
</template>
</el-table-column>
到这一步基本的样式、数据渲染就完成了,看下我的效果图
唉 是丑了点,先还原功能再说,样式慢慢调吧哈哈哈哈哈哈哈
点击添加课程的时候 获取当前点击按钮的 X轴(日期)与Y轴(课程名称) 轴信息,然后和添加的数据做绑定就好了。
最后附上完整源码
<template>
<div class="byted-weektime" style="margin-top:1.2%;margin-left:1.2%;">
<div class="block">
<el-date-picker
v-model="datas"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
<el-select v-model="getListQuery.classId" placeholder="请选择" @change="onSelectedDrug($event)">
<el-option
v-for="item in ClassesList"
:key="item.ClassId"
:label="item.ClassName"
:value="item.ClassId"
/>
</el-select>
<el-button type="primary" @click="onSubmit(datas)">查看课程</el-button>
</div>
<div>
<el-table
v-if="ClassHoursDataType"
:data="ClassHoursData"
border
style="width: 100%;margin-top:1.2%"
>
<el-table-column
prop="CourseName"
label="课程名称"
style="width: 25%;margin-top:1.2%"
/>
<el-table-column
prop="CanScheduleClassHours"
label="可排课时"
style="width: 25%;margin-top:1.2%"
/>
<el-table-column
prop="ScheduledClassHours"
label="已排课时"
style="width: 25%;margin-top:1.2%"
/>
<el-table-column
prop="TotalClassHours"
label="总课时"
style="width: 25%;margin-top:1.2%"
/>
</el-table>
</div>
<div class="calendar" style="margin-top:50px">
<el-table
:data="tableData.TableCourseName"
:header-cell-style="{background:'#cceeff',color:'#2f4f4f'}"
:border="true"
>
<el-table-column
v-if="CourseNameType"
label="课程名称"
width="130"
fixed
class-name="CourseName"
>
<template slot-scope="{row}">
{{ row.CourseName }}
</template>
</el-table-column>
<el-table-column
v-for="item in column"
:key="item.id"
width="300"
:label="item.label"
>
<template
slot-scope="{row}"
>
<el-button type="success" style="width:100%;" size="mini" plain @click="addCourse(item,row)">添加课程</el-button>
<span v-for=" i in tableData.Curriculum" :key="i.ClassScheduleCardID" style="">
<span
v-if="item.Data === i.Data && row.CourseName === i.CourseName ? true : false"
>
<el-tag
closable
style="height:105px;width:92px"
@close="handleDelete(i, row)"
>
<div>{{ i.ClassTimeSpan }}</div>
<div>{{ i.ClassRoomName }}</div>
<div>{{ i.InstructorName, }}</div>
</el-tag>
</span>
</span>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :title="dialogStatus" :visible.sync="dialogFormVisible">
<el-form ref="rulesForm" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 60%; margin:0 auto">
<el-form-item label="教官名称" prop="InstructorId">
<el-select
v-model="temp.InstructorId"
placeholder="请选择教官"
style="width:100%"
@change="InstructorChange(temp.InstructorId)"
>
<el-option
v-for="item in InstructorList"
:key="item.InstructorId"
:label="item.TrueName"
:value="item.InstructorId"
/>
</el-select>
</el-form-item>
<el-form-item label="上课时段" prop="TimeManagementId">
<el-select
v-model="temp.TimeManagementId"
placeholder="请选择上课时段"
style="width:100%"
@change="CimeManagementChange(temp.TimeManagementId)"
>
<el-option
v-for="item in CimeManagementList"
:key="item.ID"
:label="item.TimeFrame"
:value="item.ID"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<el-form-item label="教室名称" prop="RoomId">
<el-select
v-model="temp.RoomId"
style="width:100%"
placeholder="请选择教室"
@change="ClassRooChange(temp.RoomId)"
>
<el-option
v-for="item in ClassRoomList"
:key="item.RoomID"
:label="item.ClassroomName"
:value="item.RoomID"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="temp.Remarks" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="dialogStatus==='添加课程'?createData(temp):updateData(temp)">
确认
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getList, getClassesList, getInstructorListByCourseid, getCimeManagementList, getClassRoomList, addClass, getClassScheduleCardList, deleteSchedule } from '@/api/edu-administration/row-class-management/row-class'
export default {
name: 'TimeSelect',
data() {
return {
datas: [],
dialogStatus: '', //
dialogFormVisible: false, // 添加课程弹框
currentDate: null,
AvailableTime: 0, // 全中课程的可用课时
CourseId: 0, // 选中的课程的Id
CourseName: null,
ClassName: null, // 选中的班级名称
InstructorName: null, // 选中的教官名称
ClassTimeSpan: null, // 选中的时段
RoomName: null, // 选中的教室名称
temp: {
ClassId: null,
ClassName: null,
CourseId: null,
CourseName: null,
ClassTimeSpan: null,
InstructorId: null,
InstructorName: null,
RoomId: null,
RoomName: null,
TimeManagementId: null,
Remarks: null
},
ClassHoursData: null, // 可用课时
ClassHoursDataType: false,
column: [],
CourseNameType: false,
ClassesList: null, // 班级列表
InstructorList: null, // 教官列表
CimeManagementList: null, // 时段列表
ClassRoomList: null, // 教室列表
getListQuery: {
classId: null,
className: null,
startDate: null,
endDate: null
},
rules: {
InstructorId: [{ required: true, message: '请选择教官', trigger: 'change' }],
TimeManagementId: [{ required: true, message: '请选择上课时段', trigger: 'change' }],
RoomId: [{ required: true, message: '请选择教室', trigger: 'change' }]
},
tableData: {
// TableCourseName: [{
// CourseName: '思想指导'
// }, {
// CourseName: '高数'
// }, {
// CourseName: '英语'
// }, {
// CourseName: '高精狙射击'
// }, {
// CourseName: '低精狙射击'
// }, {
// CourseName: '芭蕾舞蹈'
// }, {
// CourseName: '天鹅舞蹈1'
// }],
// Curriculum: [{
// ClassScheduleCardID: 5,
// Data: '2021-05-20',
// CourseName: '思想指导',
// InstructorName: '张三',
// ClassRoom: '一教室11',
// ClassTimeSpan: '02:50-03:50'
// }, {
// ClassScheduleCardID: 3,
// Data: '2021-05-20',
// CourseName: '思想指导',
// InstructorName: '张三',
// ClassRoom: '一教室11',
// ClassTimeSpan: '02:50-03:50'
// }, {
// ClassScheduleCardID: 4,
// Data: '2021-05-20',
// CourseName: '思想指导',
// InstructorName: '张三',
// ClassRoom: '一教室11',
// ClassTimeSpan: '02:50-03:50'
// }, {
// ClassScheduleCardID: 8,
// Data: '2021-05-20',
// CourseName: '高数',
// InstructorName: '张三',
// ClassRoom: '一教室22',
// ClassTimeSpan: '02:50-03:50'
// }, {
// ClassScheduleCardID: 6,
// Data: '2021-05-21',
// CourseName: '思想指导',
// InstructorName: '张三',
// ClassRoom: '一教室33',
// ClassTimeSpan: '02:50-03:50'
// }]
}
}
},
created() {
},
mounted() {
this.nowDate()
getClassesList().then(response => {
this.ClassesList = response.response
this.getListQuery.classId = response.response[0].ClassId
this.onSelectedDrug(response.response[0].ClassId)
this.onSubmit(this.datas)
})
},
methods: {
weekDay(date) { // 根据日期获取对应星期
var dt = new Date(date.split('-')[0], date.split('-')[1] - 1, date = date.split('-')[2])
var weekDay = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
return weekDay[dt.getDay()]
},
formatDateTime(date) { // 日期格式转换
var y = date.getFullYear()
var m = date.getMonth() + 1
m = m < 10 ? ('0' + m) : m
var d = date.getDate()
d = d < 10 ? ('0' + d) : d
return y + '-' + m + '-' + d
},
nowDate() { // 获取本周的日期
var new_Date = new Date()
var timesStamp = new_Date.getTime()
var currenDay = new_Date.getDay()
var dates = []
for (var i = 0; i < 7; i++) {
dates.push(new Date(timesStamp + 24 * 60 * 60 * 1000 * (i - (currenDay + 6) % 7)).toLocaleDateString().replace(
/[年月]/g, '-').replace(/[日上下午]/g, ''))
}
this.datas.push(this.formatDateTime(new Date(dates[0])))
this.datas.push(this.formatDateTime(new Date(dates[6])))
// this.getListQuery.startDate = this.formatDateTime(new Date(dates[0]))
// this.getListQuery.endDate = this.formatDateTime(new Date(dates[6]))
console.log('本周的日期哦', this.formatDateTime(new Date(dates[0])), this.formatDateTime(new Date(dates[6])))
},
onSubmit(date) { // 查询按钮
if (date.length > 0) {
console.log('下拉框', this.getListQuery)
this.CourseNameType = true
this.CimeManagementList = null
this.ClassRoomList = null
// console.log(this.weekDay(date))
this.getListQuery.startDate = date[0]
this.getListQuery.endDate = date[1]
getList(this.getListQuery).then(response => {
this.tableData = response.response
console.log('返回值', response.response)
})
console.log(this.tableData)
console.log(date)
console.log(this.getDayAll(date[0], date[1]))
this.column.length = 0
console.log('加载前', this.column)
for (let index = 0; index < this.getDayAll(date[0], date[1]).length; index++) {
this.column.push({
id: Math.floor(Math.random() * 9999999999 + 1),
label: '' + this.getDayAll(date[0], date[1])[index] + '(' + this.weekDay(this.getDayAll(date[0], date[1])[index]) + ')',
Data: this.getDayAll(date[0], date[1])[index]
})
}
if (this.getListQuery.classId !== undefined) {
getClassScheduleCardList(this.getListQuery.classId).then(response => {
this.ClassHoursData = response.response
})
this.ClassHoursDataType = true
}
console.log(this.column)
}
},
addCourse(row, Course) { // 添加课程按钮
const end = new Date()
const start = new Date()
this.InstructorList = null
this.CimeManagementList = null
this.ClassRoomList = null
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
console.log('当前周', new Date().getDay(), start, end)
// console.log(new Date(row.Data), new Date(), new Date(row.Data) < new Date())
// console.log(this.ClassHoursData.CourseName)
for (let index = 0; index < this.ClassHoursData.length; index++) {
if (this.ClassHoursData[index].CourseName === Course.CourseName) {
this.AvailableTime = this.ClassHoursData[index].CanScheduleClassHours
break
}
}
console.log(this.AvailableTime)
if (new Date(row.Data) >= new Date()) { // 排课时间需大于操作时间
if (this.AvailableTime > 0) { // 可排课时需大于零
this.CourseName = Course.CourseName
this.CourseId = Course.CourseId
getInstructorListByCourseid(Course.CourseId).then(response => {
this.InstructorList = response.response
})
this.dialogStatus = '添加课程'
this.dialogFormVisible = true
console.log(row, Course, this.InstructorList)
this.currentDate = row.Data // 当前选中模块的日期
this.$nextTick(() => {
this.$refs['rulesForm'].resetFields()
})
} else {
this.$message({
showClose: true,
message: '可排课时不足',
type: 'warning'
})
}
} else {
this.$message({
showClose: true,
message: '无法操作过往日期的课程',
type: 'warning'
})
}
},
createData(row) { // 添加确认按钮
this.$refs['rulesForm'].validate((valid) => {
if (valid) {
this.temp = {}
this.temp = row
this.temp.CourseName = this.CourseName
this.temp.ClassTimeSpan = this.ClassTimeSpan
this.temp.ClassTime = this.currentDate
this.temp.ClassId = this.getListQuery.classId
this.temp.ClassName = this.ClassName
this.temp.InstructorName = this.InstructorName
this.temp.RoomName = this.RoomName
this.temp.CourseId = this.CourseId
this.temp.CourseName = this.CourseName
console.log(row, '添加的方法体啦')
console.log(this.temp, '实际的数据')
addClass(this.temp).then(response => {
if (response.msg === '添加成功') {
this.$notify({
title: '提示',
message: response.msg,
type: 'success',
duration: 2000
})
this.onSubmit(this.datas)
this.dialogFormVisible = false
} else {
this.$notify({
title: '提示',
message: response.msg,
type: 'warning',
duration: 2000
})
}
console.log('添加排课接口', response)
})
}
})
},
updateData(row) { // 编辑确认按钮
this.$refs['rulesForm'].validate((valid) => {
if (valid) {
console.log('添加的方法体啦')
}
})
},
InstructorChange(InstructorId) { // 教官名称下拉框事件
if (InstructorId !== null) {
getCimeManagementList(this.currentDate, InstructorId).then(response => {
this.CimeManagementList = response.response
})
for (let index = 0; index < this.InstructorList.length; index++) {
if (this.InstructorList[index].InstructorId === InstructorId) {
this.InstructorName = this.InstructorList[index].TrueName
break
}
}
// this.InstructorName = this.InstructorList[index].TrueName
}
},
CimeManagementChange(ID) { // 上课时段下拉框事件
if (ID !== null) {
console.log('上课时段下拉框事件', ID)
getClassRoomList(this.currentDate, ID).then(response => {
this.ClassRoomList = response.response
})
// var start = 0
// while (start < this.CimeManagementList.length) {
// var
// }
for (let index = 0; index < this.CimeManagementList.length; index++) {
if (this.CimeManagementList[index].ID === ID) {
this.ClassTimeSpan = this.CimeManagementList[index].TimeFrame
break
}
}
// this.ClassTimeSpan = this.CimeManagementList[index].TimeFrame
}
},
ClassRooChange(RoomId) {
for (let index = 0; index < this.ClassRoomList.length; index++) {
if (this.ClassRoomList[index].RoomID === RoomId) {
this.RoomName = this.ClassRoomList[index].ClassroomName
console.log(this.ClassRoomList[index].ClassroomName)
break
}
}
},
RuleDelConfirm(name) {
return this.$confirm(`此操作将删除 ${name}教官的这门课程, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
},
handleDelete(i, row) { // 删除课程
// if (day === '周一'){
if (new Date(row.Data) >= new Date()) {
this.RuleDelConfirm(i.InstructorName).then(response1 => {
deleteSchedule(i.ClassScheduleCardID).then(response2 => {
if (response2.msg === '删除成功') {
this.$notify({
title: '提示',
message: response2.msg,
type: 'success',
duration: 2000
})
this.onSubmit(this.datas)
}
})
})
} else {
this.$message({
showClose: true,
message: '无法操作过往日期的课程',
type: 'warning'
})
}
// }
},
getDayAll(starDay, endDay) { // 两个日期之间的所有日期
var arr = []
var dates = []
// 设置两个日期UTC时间
var db = new Date(starDay)
var de = new Date(endDay)
// 获取两个日期GTM时间
var s = db.getTime() - 24 * 60 * 60 * 1000
var d = de.getTime() - 24 * 60 * 60 * 1000
// 获取到两个日期之间的每一天的毫秒数
for (var i = s; i <= d;) {
i = i + 24 * 60 * 60 * 1000
arr.push(parseInt(i))
}
// 获取每一天的时间 YY-MM-DD
for (var j in arr) {
var time = new Date(arr[j])
var year = time.getFullYear(time)
var mouth = (time.getMonth() + 1) >= 10 ? (time.getMonth() + 1) : ('0' + (time.getMonth() + 1))
var day = time.getDate() >= 10 ? time.getDate() : ('0' + time.getDate())
var YYMMDD = year + '-' + mouth + '-' + day
dates.push(YYMMDD)
}
return dates
},
onSelectedDrug(ClassId) { // 班级下拉框事件
for (let index = 0; index < this.ClassesList.length; index++) {
if (this.ClassesList[index].ClassId === ClassId) {
this.ClassName = this.ClassesList[index].ClassName
console.log('班级班级班级班级把基金', this.ClassesList[index])
break
}
}
// this.ClassName = this.ClassesList[row].ClassName
}
}
}
</script>
<style scoped>
排课组件 完结.
总结
遇到困难不要退缩,仔细思考,或者换一种角度看问题,没准问题就迎刃而解了,千万不要放弃,千万不要放弃,千万不要放弃,也不要绕过问题,学习的过程是痛苦的但结果是美好的。