文章目錄
- 前言
- 一、創作曆程
-
- 1. 動态表頭
-
-
- 第一步 肯定是要擷取選中的日期啦
- 第二步 根據日期擷取對應的星期
-
- 2.行名稱(對應的課程)
- 3.表格内資料(具體的排課資訊)
- 最後附上完整源碼
- 總結
前言
先别考慮樣式!!!!
最近項目要求按老系統的格式使用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>
排課元件 完結.
總結
遇到困難不要退縮,仔細思考,或者換一種角度看問題,沒準問題就迎刃而解了,千萬不要放棄,千萬不要放棄,千萬不要放棄,也不要繞過問題,學習的過程是痛苦的但結果是美好的。