在日常开发需求中,可能会遇到给员工进行排班的需求,如果只是在table表格中显示,会显得枯燥、不直观,今天我们就来写一个可以自定义的日历排班功能,用的是vue2+element-ui。
效果图如下:
(图一):日历中显示排班数据,排班数据可以使用鼠标进行拖动,改变排班的顺序。
(图二):可以对日期范围进行批量、多班次排班
(图三):可以对单日排班信息进行操作,显示单日排班时间的具体信息等。
下面是代码部分:
<template>
<div id="app">
<div class="calender-class">
<div class="batch-add-Work-class">
<el-button class="add-btn-class" size="small" type="primary" @click="batchAddDrawer = true">批量排班</el-button>
<el-button class="add-btn-class" size="small" type="primary" @click="changeDateDrawer = true">日期选择</el-button>
</div>
<el-calendar>
<!-- 这里使用的是 2.5 slot 语法,对于新项目请使用 2.6 slot 语法-->
<template slot="dateCell" slot-scope="{ date, data }">
<div class="day-content-class">
<template v-if="viewDate[data.day]">
<div class="header-class">
<div class="day-class">
{{
data.day
.split("-")
.slice(1)
.join("-")
}}
</div>
<div class="handle-class">
<el-button icon="el-icon-edit" size="mini" circle @click="handleWorkInfo(viewDate[data.day], data)">
</el-button>
</div>
</div>
<div class="paiban-class">
<div v-for="(dayValue, i) in viewDate[data.day]" :class="[
'draggable-div' + i,
'each-paiban-class',
setWorkClass(dayValue.sort),
]" draggable="true" @dragstart="handleDragStart($event, dayValue, data.day)"
@dragover.prevent="handleDragOver($event)" @dragenter="handleDragEnter($event, dayValue)"
@dragend="handleDragEnd()">
<i :class="[
setIconClass(dayValue.shiftName),
'paiban-icon-class',
]"></i>
<div class="paiban-name-class">{{ dayValue.groupName }}</div>
</div>
</div>
</template>
<template v-else>
<div class="header-class">
<div class="day-class">
{{
data.day
.split("-")
.slice(1)
.join("-")
}}
</div>
<div class="handle-class">
<el-button icon="el-icon-edit" size="mini" circle @click="handleWorkInfo(viewDate[data.day], data)">
</el-button>
</div>
</div>
<div class="no-work-class">
<div class="icon-class"><i class="el-icon-date"></i></div>
<div class="tips-class">
暂无排班
</div>
</div>
</template>
</div>
</template>
</el-calendar>
</div>
<!-- 批量排班抽屉弹窗 -->
<div>
<el-drawer title="批量排班" :visible.sync="batchAddDrawer" size="40%">
<div class="demo-drawer__content">
<el-form :model="batchAddForm">
<el-form-item label="排班日期" label-width="80px">
<el-date-picker v-model="batchAddForm.batchDate" value-format="yyyy-MM-dd" type="daterange"
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期">
</el-date-picker>
</el-form-item>
<el-button type="primary" icon="el-icon-plus" circle @click="addDomain"></el-button>
<el-form-item label-width="80px" v-for="(data, index) in batchAddForm.classData"
:label="'排班' + (index + 1) + ':'" :key="data.key">
<p></p>
<span>班次:</span>
<el-radio-group v-model="data.shiftName">
<el-radio label="早">早</el-radio>
<el-radio label="中">中</el-radio>
<el-radio label="晚">晚</el-radio>
</el-radio-group>
<p></p>
<span>班别:</span>
<el-radio-group class="margin-left:80px" v-model="data.groupName">
<el-radio label="甲">甲</el-radio>
<el-radio label="乙">乙</el-radio>
<el-radio label="丙">丙</el-radio>
</el-radio-group>
<el-button class="remove-domain-class" @click.prevent="removeDomain(data)" type="danger"
icon="el-icon-delete" circle></el-button>
</el-form-item>
</el-form>
</div>
<div class="demo-drawer__footer">
<el-button @click="handleBatchClose">取 消</el-button>
<el-button type="primary" @click="batchAddWork()">
确定
</el-button>
</div>
</el-drawer>
</div>
<!-- 单独排班 -->
<div>
<el-drawer :title="'【' + hanleDay.day + '】排班'" :visible.sync="drawer" size="40%">
<div class="add-work-class">
<el-button class="add-btn-class" type="primary" @click="innerDrawer = true">添加</el-button>
<el-drawer title="添加排班" :append-to-body="true" :before-close="handleClose" :visible.sync="innerDrawer">
<div class="demo-drawer__content">
<el-form :model="addForm">
<el-form-item label="班次:" label-width="80px">
<el-radio-group v-model="addForm.shiftName">
<el-radio label="早">早</el-radio>
<el-radio label="中">中</el-radio>
<el-radio label="晚">晚</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="班别:" label-width="80px">
<el-radio-group v-model="addForm.groupName">
<el-radio label="甲">甲</el-radio>
<el-radio label="乙">乙</el-radio>
<el-radio label="丙">丙</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<div class="demo-drawer__footer">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" @click="addWork()">
确定
</el-button>
</div>
</el-drawer>
</div>
<el-table :data="workInfoList">
<el-table-column property="date" label="日期" width="100"></el-table-column>
<el-table-column property="shiftName" label="班次"></el-table-column>
<el-table-column property="groupName" label="班别"></el-table-column>
<el-table-column property="startTime" label="开始时间" width="160"></el-table-column>
<el-table-column property="endTime" label="结束时间" width="160"></el-table-column>
<el-table-column fixed="right" label="操作" width="120">
<template slot-scope="scope">
<el-button @click.native.prevent="deleteRow(scope, workInfoList)" type="text" size="small">
移除
</el-button>
</template>
</el-table-column>
</el-table>
</el-drawer>
</div>
<!-- 日期选择 -->
<div>
<el-drawer title="日期选择" :visible.sync="changeDateDrawer" class="change-date-drawer-class" size="30%">
<el-calendar>
<!-- 这里使用的是 2.5 slot 语法,对于新项目请使用 2.6 slot 语法-->
<template slot="dateCell" slot-scope="{ date, data }">
<div :class="['day-content-class',setDisabled(data.day)]">
<template>
<div class="header-class no-drop-class" v-show="data.type === 'current-month'"
@click="selectDate(date,data)">
<div class="day-class">
{{
data.day
.split("-")
.slice(1)
.join("-")
}}
</div>
<div :key="data.day" :id="data.day">{{ initHolidayDate(data) }}</div>
</div>
</template>
</div>
</template>
</el-calendar>
<div style="margin:10px">
<div>选中的日期:</div>
<span v-for="day in currentDate">
{{day.date}}  
</span>
</div>
</el-drawer>
</div>
</div>
</template>
<script>
import moment from "moment";
export default {
data() {
return {
viewDate: {
"2023-10-10": [{
id: "2023-10-10" + Math.random(1000),
ruleName: "三班两运转",
shiftName: "早",
groupName: "甲",
startTime: "2023-10-10 08:30",
endTime: "2023-10-10 20:30",
isNotHoliday: 0,
classId: 1,
date: "2023-10-10",
sort: 1,
},
{
id: "2023-10-10" + Math.random(1000),
ruleName: "三班两运转",
shiftName: "中",
groupName: "乙",
startTime: "2023-10-10 20:30",
endTime: "2023-10-08 08:30",
isNotHoliday: 0,
classId: 1,
date: "2023-10-10",
sort: 2,
},
],
"2023-10-08": [{
id: "2023-10-08" + Math.random(1000),
ruleName: "三班两运转",
shiftName: "早",
groupName: "甲",
startTime: "2023-10-08 08:30",
endTime: "2023-10-08 20:30",
isNotHoliday: 0,
classId: 1,
date: "2023-10-08",
sort: 1,
},
{
id: "2023-10-08" + Math.random(1000),
ruleName: "三班两运转",
shiftName: "中",
groupName: "乙",
startTime: "2023-10-08 08:30",
endTime: "2023-10-08 20:30",
isNotHoliday: 0,
classId: 1,
date: "2023-10-08",
sort: 2,
},
{
id: "2023-10-08" + Math.random(1000),
ruleName: "三班两运转",
shiftName: "晚",
groupName: "丙",
startTime: "2023-10-08 08:30",
endTime: "2023-10-09 20:30",
isNotHoliday: 0,
classId: 1,
date: "2023-10-08",
sort: 3,
},
],
},
thisDay: null,
thisDayWork: null,
ending: null,
dragging: null,
batchAddDrawer: false,
// 批量添加
batchAddForm: {
batchDate: [],
classData: [{
shiftName: "早",
groupName: "甲",
}, ],
},
// 单日添加
addForm: {
shiftName: "早",
groupName: "甲",
sort: 1,
},
drawer: false,
innerDrawer: false,
hanleDay: "",
workInfoList: [],
// 时间范围
dateRange: ['2023-10-1', '2023-10-20'],
changeDateDrawer: false,
// 点击月中已选中的日期
currentDate: [],
};
},
watch: {
"addForm.shiftName"(newVal, oldVal) {
switch (newVal) {
case "早":
this.addForm.sort = 1;
break;
case "中":
this.addForm.sort = 2;
break;
case "晚":
this.addForm.sort = 3;
break;
default:
break;
}
},
},
computed: {},
methods: {
handleDragStart(e, item, thisDay) {
this.dragging = item;
this.thisDay = thisDay;
this.thisDayWork = this.viewDate[thisDay];
},
handleDragEnd() {
if (this.ending.id === this.dragging.id) {
return;
}
let newItems = [...this.thisDayWork];
const src = newItems.indexOf(this.dragging);
const dst = newItems.indexOf(this.ending);
newItems.splice(src, 1, ...newItems.splice(dst, 1, newItems[src]));
this.$set(this.viewDate, this.thisDay, newItems);
this.$nextTick(() => {
this.dragging = null;
this.ending = null;
});
console.log(
"🚀 ~ file: App.vue:286 ~ handleDragEnd ~ this.viewDate:",
this.viewDate
);
},
handleDragOver(e) {
// 首先把div变成可以放置的元素,即重写dragenter/dragover
e.dataTransfer.dropEffect = "move"; // e.dataTransfer.dropEffect="move";//在dragenter中针对放置目标来设置!
},
handleDragEnter(e, item) {
e.dataTransfer.effectAllowed = "move"; // 为需要移动的元素设置dragstart事件
this.ending = item;
},
// 获取时间范围中的所有日期
enumerateDaysBetweenDates(startDate, endDate) {
let daysList = [];
let SDate = moment(startDate);
let EDate = moment(endDate);
daysList.push(SDate.format("YYYY-MM-DD"));
while (SDate.add(1, "days").isBefore(EDate)) {
daysList.push(SDate.format("YYYY-MM-DD"));
}
daysList.push(EDate.format("YYYY-MM-DD"));
return daysList;
},
setSortValue(value) {
let sort = 1;
switch (value) {
case "早":
sort = 1;
break;
case "中":
sort = 2;
break;
case "晚":
sort = 3;
break;
default:
break;
}
return sort;
},
setWorkClass(value) {
let classValue = "no-work-class";
switch (value) {
case 1:
classValue = "zao-work-class";
break;
case 2:
classValue = "wan-work-class";
break;
case 3:
classValue = "ye-work-class";
break;
default:
break;
}
return classValue;
},
setIconClass(value) {
let classValue = "el-icon-sunrise-1";
switch (value) {
case "早":
classValue = "el-icon-sunrise-1";
break;
case "中":
classValue = "el-icon-sunny";
break;
case "晚":
classValue = "el-icon-moon";
break;
default:
break;
}
return classValue;
},
// 编辑单日排班
handleWorkInfo(info, data) {
this.hanleDay = data;
this.drawer = true;
if (info && info.length > 0) {
this.workInfoList = info;
} else {
this.workInfoList = [];
}
},
handleClose() {
this.innerDrawer = false;
},
// 添加单日排班
addWork() {
let info = {
id: this.hanleDay.day + Math.random(1000),
ruleName: "三班两运转",
shiftName: this.addForm.shiftName,
groupName: this.addForm.groupName,
startTime: this.hanleDay.day + " 08:30",
endTime: this.hanleDay.day + " 20:30",
isNotHoliday: 0,
classId: 1,
date: this.hanleDay.day,
sort: this.addForm.sort,
};
this.workInfoList.push(info);
this.$set(this.viewDate, this.hanleDay.day, this.workInfoList);
this.innerDrawer = false;
},
// 清除单日排班数据
deleteRow(row, tableData) {
let index = row.$index;
tableData.splice(index, 1);
if (tableData.length > 0) {
this.$set(this.viewDate, this.hanleDay.day, tableData);
} else {
this.$delete(this.viewDate, this.hanleDay.day);
}
},
addDomain() {
this.batchAddForm.classData.push({
shiftName: "早",
groupName: "甲",
key: Date.now(),
});
},
removeDomain(item) {
if (this.batchAddForm.classData.length > 1) {
var index = this.batchAddForm.classData.indexOf(item);
if (index !== -1) {
this.batchAddForm.classData.splice(index, 1);
}
} else {
this.$message({
message: "请至少安排一个排班",
type: "error",
});
}
},
// 批量添加排班数据
batchAddWork() {
let dateList = this.batchAddForm.batchDate;
let classList = this.batchAddForm.classData;
let list = [];
if (dateList && dateList.length > 0) {
list = this.enumerateDaysBetweenDates(dateList[0], dateList[1]);
}
list.forEach((item) => {
let workList = [];
classList.forEach((work) => {
let info = {
id: item + Math.random(1000),
ruleName: "三班两运转",
shiftName: work.shiftName,
groupName: work.groupName,
startTime: item + " 08:30",
endTime: item + " 20:30",
isNotHoliday: 0,
classId: 1,
date: item,
sort: this.setSortValue(work.shiftName),
};
workList.push(info);
});
this.$set(this.viewDate, item, workList);
});
this.batchAddDrawer = false;
this.batchAddForm = {
batchDate: [],
classData: [{
shiftName: "早",
groupName: "甲",
}, ],
};
},
handleBatchClose() {
this.batchAddDrawer = false;
},
//初始化已选中的日期
initHolidayDate(data) {
for (let i in this.currentDate) {
if (data.day === this.currentDate[i].date) {
data.isSelected = true;
// return '✔️'
return '✔'
}
}
},
//点击选中或取消选中
selectDate(date, data) {
console.log("🚀 ~ file: App.vue:510 ~ selectDate ~ data:", data)
let day = date.getDate();
let span = document.getElementById(data.day);
if (span.innerText) {
span.innerText = ''
for (let i in this.currentDate) {
if (day === this.currentDate[i].day) {
this.currentDate.splice(i, 1)
}
}
} else {
span.innerText = '✔';
this.currentDate.push({
day: day,
date: data.day
})
}
console.log("this.currentDate:", this.currentDate)
},
// 设置禁用值
setDisabled(date) {
// console.log("🚀 ~ file: App.vue:537 ~ setDisabled ~ date:", date)
if (moment(date).isBefore(this.dateRange[0]) || moment(date).isAfter(this.dateRange[1])) {
return 'disabled-date-class'
}
}
},
};
</script>
<style>
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.el-table__fixed-right {
height: 100% !important;
}
.calender-class {
width: 100%;
height: 100%;
}
.is-selected {
color: #1989fa;
}
.el-calendar__body {
height: 85vh;
}
.el-calendar-table {
height: 100%;
}
.el-calendar-day {
height: 100% !important;
}
.day-content-class {
height: 100px;
display: flex;
flex-direction: column;
}
.header-class {
flex: 1;
display: flex;
height: 28px;
flex-direction: row;
justify-content: space-between;
}
.day-class {
flex: 4;
}
.handle-class {
flex: 1;
}
.paiban-class {
flex: 4;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-end;
}
.paiban-icon-class {
font-size: 22px;
margin: 8px 0 10px 0;
}
.paiban-name-class {
padding-top: 10px;
}
.each-paiban-class {
text-align: center;
max-width: 50px;
margin: 5px 5px 0 5px;
border-radius: 5px;
padding: 0 0 5px 0;
flex: 1;
}
.zao-work-class {
background-color: #d9ffd9;
color: #11be11;
}
.wan-work-class {
background-color: #fff0bd;
color: #fccb2c;
}
.ye-work-class {
background-color: #ddeffb;
color: #2dabff;
}
.no-work-class {
text-align: center;
color: #cacaca;
}
.icon-class {
font-size: 20px;
margin-bottom: 20px;
}
/* 侧边弹窗 */
.add-btn-class {
margin: 10px;
float: right;
}
.change-date-drawer-class .el-calendar__body {
height: 45%;
}
.change-date-drawer-class .day-content-class {
height: 30px
}
.disabled-date-class {
color: #ccc;
pointer-events: none;
}
</style>