| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790 |
- <div id="app" v-cloak>
- <el-card shadow="never" style="border:0;min-width: 1000px;"
- body-style="background-color: #f3f3f3;padding: 10px 0 0;">
- <div slot="header">
- <span>排期管理</span>
- </div>
- <!-- 排班 -->
- <el-card class="box-card" style="margin-bottom: 10px;">
- <el-container class="scheduling-box">
- <el-aside width="400px" class="scheduling-aside" v-loading="loading">
- <div class="date-box">
- <div class="title">{{userDetail.user_name}}</div>
- <div class="sub-title">{{currentDate}} {{activeDateWeek}}</div>
- </div>
- <template>
- <div class="date-box">
- <div>
- <div class="flex-between">
- <div class="date-title">{{ userDetail.type === 1 ? '休息' : '上班' }}时间设置</div>
- </div>
- <el-form ref="form" :model="form">
- <el-form-item>
- <el-radio-group v-model="form.is_all_day">
- <el-radio :label="0">选择时间</el-radio>
- <el-radio :label="1">全天</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item v-if='form.is_all_day == 0'>
- <el-button type="primary" size="small" :disabled="isExpire"
- @click="handle('add')" style="margin-bottom:5px;">添 加
- </el-button>
- <div v-for="(item, index) in form.schedulingTime"
- class="my_time_picker flex-between" style="margin-bottom:5px;">
- <el-time-picker size="small" v-model="form.schedulingTime[index].start"
- :picker-options="startTimeChange(form, index)"
- placeholder="开始时间"></el-time-picker>
- <span style="margin: 0 10px">至</span>
- <el-time-picker size="small" v-model="form.schedulingTime[index].end"
- :picker-options="{
- selectableRange: `${form.schedulingTime[index].start} - ${defaultEndTime()}`
- }" placeholder="结束时间"></el-time-picker>
- <el-button icon="el-icon-delete" type="danger" size="mini"
- :disabled="isExpire" @click="handle('del', index)"
- style="margin-left:5px;"></el-button>
- </div>
- </el-form-item>
- <el-button size="small" :disabled="isExpire" @click="handle('delete')"
- icon="el-icon-delete" type="danger" :loading="loading"
- v-if='isShowDelete'>
- 删除
- </el-button>
- <el-button v-if="form.schedulingTime && form.schedulingTime.length > 0"
- type="primary" size="small" :disabled="isExpire" @click="handle('save')"
- :loading="loading">
- 保 存
- </el-button>
- </el-form>
- </div>
- </div>
- </template>
- </el-aside>
- <el-main class="scheduling-main">
- <div class="flex-between operate-box">
- <div>
- <el-select v-model="form.user_id" placeholder="请选择" style="margin-right:10px;"
- @change="userChange">
- <el-option v-for="item in userList" :key="item.user_id" :label="item.name"
- :value="item.user_id">
- </el-option>
- </el-select>
- <el-date-picker v-model="calendarDate" type="month" placeholder="选择日期"></el-date-picker>
- </div>
- <template>
- <el-button type="primary" size="small" :disabled="isExpire"
- @click="handle('openMonthDialog')">
- 批量设置当前月份
- </el-button>
- </template>
- </div>
- <el-calendar v-model="calendarDate">
- <template slot="dateCell" slot-scope="{date, data}">
- <div class="calendar-day-box" :class="isScheduling(data.day) && 'is-active'">
- <div v-if="data.type === 'current-month'" class="cell-box"
- @click="updateCurrentDate(data)">
- <el-popover placement="top-start" :title="data.day" width="170" trigger="hover">
- <p v-html="schedulingTimeText(data.day)" class="time-text"></p>
- <div slot="reference">
- <p class="day-text">{{ data.day.split('-')[2] }}</p>
- <span>{{schedulingLabelText(data.day)}}</span>
- </div>
- </el-popover>
- </div>
- </div>
- </template>
- </el-calendar>
- </el-main>
- </el-container>
- </el-card>
- <!-- 批量设置当月的排班时间--开始-->
- <el-dialog title="批量设置" :visible.sync="dialogScheduling" width="500px">
- <div class="date-box" style="border-top:1px solid #eee; padding-top:10px;padding-left:30px;">
- <el-form ref="form1" :model="form1">
- <el-form-item :label="`${userDetail.type === 1 ? '休息' : '上班'}时间`">
- <el-radio-group v-model="form1.is_all_day">
- <el-radio :label="0">选择时间</el-radio>
- <el-radio :label="1">全天</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item v-if='form1.is_all_day == 0' label="设定范围:">
- <el-button type="primary" size="small" :disabled="isExpire" @click="batchSetSchedule"
- style="margin-bottom:5px;">添 加
- </el-button>
- <div v-for="(item, index) in form1.schedulingTime" :key="index"
- class="my_time_picker flex-between" style="margin-bottom:5px;margin-left:80px;">
- <el-time-picker size="small" v-model="form1.schedulingTime[index].start"
- :picker-options="startTimeChange(form1, index)" placeholder="开始时间"
- style="width: 120px;"></el-time-picker>
- <span style="margin: 0 10px">至</span>
- <el-time-picker size="small" v-model="form1.schedulingTime[index].end" :picker-options="{
- selectableRange: `${form1.schedulingTime[index].start} - ${defaultEndTime()}`
- }" placeholder="结束时间" style="width: 120px;"></el-time-picker>
- <el-button icon="el-icon-delete" type="danger" size="small" :disabled="isExpire"
- @click="handle('batchDelete', index)" style="margin-left:5px;"></el-button>
- </div>
- </el-form-item>
- </el-form>
- </div>
- <div slot="footer" class="dialog-footer">
- <el-button type="primary" @click="dialogScheduling = false">关 闭</el-button>
- <el-button type="primary" @click="saveMonthScheduling" :loading="loading">提 交</el-button>
- </div>
- </el-dialog>
- <!-- 批量设置讲师当月的排班时间--结束-->
- </el-card>
- </div>
- <script>
- const app = new Vue({
- el: '#app',
- data: {
- loading: false,
- // 是否显示排期删除按钮
- isShowDelete: false,
- // 批量设置弹窗
- dialogScheduling: false,
- // 单个日期表单
- form: {},
- // 批量设置表单
- form1: {},
- // 用户列表
- userList: [],
- // 排班时间段数组
- timeData: [],
- // 当前日期
- currentDate: '',
- // 当前用户id
- currentUserId: 0,
- // 日历组件日期
- calendarDate: new Date(),
- // 当前月份
- currentMonth: new Date().getMonth() + 1,
- default_start_time: '',
- default_end_time: '',
- },
- computed: {
- // 当前日期星期
- activeDateWeek() {
- const weekArray = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
- return weekArray[new Date(this.currentDate).getDay()]
- },
- // 是否已过期
- isExpire() {
- // 管理员默认可以修改已过期的排期
- return false
- // 保留注释原因:后续管理员可能改为不可修改已过期的排期
- // return new Date(this.currentDate).getTime() < new Date(this.getSpecificDate()).getTime()
- },
- userDetail() {
- return this.userList.find(item => item.user_id === this.currentUserId) || {}
- }
- },
- watch: {
- calendarDate(val) {
- this.currentDate = this.getSpecificDate(val)
- this.currentMonth = this.currentDate.split('-')[1]
- this.loadData();
- }
- },
- mounted() {
- this.currentDate = this.getSpecificDate();
- this.loadData();
- },
- methods: {
- handle(type, index) {
- switch (type) {
- case 'save':
- this.saveScheduling()
- break;
- case 'openMonthDialog':
- this.form1 = {
- is_all_day: 1,
- user_id: this.currentUserId,
- type: 1,
- schedulingTime: [
- {
- start: this.parseTime(this.defaultStartTime()),
- end: this.parseTime(this.defaultEndTime())
- }
- ]
- };
- this.dialogScheduling = true;
- break;
- case 'add':
- const lastIndex = this.form.schedulingTime.length - 1;
- let startTime;
- if (lastIndex >= 0 && this.form.schedulingTime[lastIndex].end) {
- startTime = this.form.schedulingTime[lastIndex].end;
- } else {
- startTime = this.parseTime(this.defaultStartTime());
- }
- this.form.schedulingTime.push({
- start: startTime,
- end: this.parseTime(this.defaultEndTime())
- });
- break;
- case 'del': // 删除单个排班时间段
- if (this.form.schedulingTime[index].id > 0) {
- this.deleteTimeData(this.form.schedulingTime[index], index);
- } else {
- this.form.schedulingTime.splice(index, 1)
- }
- break;
- case 'batchDelete': // 批量删除排班时间段
- this.form1.schedulingTime.splice(index, 1)
- break
- case 'delete': // 删除当日排班
- this.$confirm('此操作将删除当日排期, 是否继续?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- this.deleteScheduling();
- })
- break;
- }
- },
- // 更新当前日期数据
- updateCurrentDate(data) {
- data.isSelected = true
- // 更新当前日期
- this.currentDate = data.day
- this.getDetail(data.day)
- },
- // 批量设置排班
- batchSetSchedule() {
- const lastIndex = this.form1.schedulingTime.length - 1;
- let startTime;
- if (lastIndex >= 0 && this.form1.schedulingTime[lastIndex].end) {
- startTime = this.form1.schedulingTime[lastIndex].end;
- } else {
- startTime = this.parseTime(this.defaultStartTime());
- }
- this.form1.schedulingTime.push({
- start: startTime,
- end: this.parseTime(this.defaultEndTime())
- });
- },
- // 用户列表选择
- userChange(id) {
- this.currentUserId = id
- this.loadData()
- },
- // 日历日期排期文本处理
- schedulingLabelText(value) {
- const scheduleInfo = this.isScheduling(value)
- if (scheduleInfo) {
- const label = scheduleInfo.type === 1 ? '休息' : '上班'
- return scheduleInfo.is_all_day === 1 ? `全天${label}` : label
- }
- return `全天${this.userDetail.type === 1 ? '上班' : '休息'}`
- },
- // 排班时间段文本处理
- schedulingTimeText(value) {
- let result = '';
- const scheduleInfo = this.isScheduling(value);
- if (!scheduleInfo) return result;
- if (Array.isArray(scheduleInfo.schedulingTime) && scheduleInfo.is_all_day === 0) {
- for (const timeSlot of scheduleInfo.schedulingTime) {
- if (timeSlot.start_time && timeSlot.end_time) {
- result += `<br/>[${timeSlot.start_time} - ${timeSlot.end_time}]`;
- }
- }
- } else {
- result = '全天';
- }
- return result;
- },
- // 是否有排班
- isScheduling(value) {
- if (this.timeData.length > 0) {
- let findResult = this.timeData.find(item => item.date_time === value)
- if (findResult) {
- return findResult
- }
- }
- return false
- },
- // 初始化新表单
- initData() {
- return {
- is_all_day: 1,
- user_id: this.currentUserId,
- type: 1,
- date_time: this.getSpecificDate(this.currentDate),
- schedulingTime: [
- {
- start: this.parseTime(this.defaultStartTime()),
- end: this.parseTime(this.defaultEndTime())
- }
- ],
- business_time: `${this.defaultStartTime()} - ${this.defaultEndTime()}`
- }
- },
- // 获取默认开始时间
- defaultStartTime() {
- return this.formatTime(this.default_start_time);
- },
- // 获取默认结束时间
- defaultEndTime() {
- return this.formatTime(this.default_end_time);
- },
- formatTime(dateTimeString) {
- // 将日期时间字符串转换为 Date 对象
- const date = new Date(dateTimeString);
- // 获取小时、分钟和秒,并确保它们是两位数显示
- const hours = String(date.getHours()).padStart(2, '0');
- const minutes = String(date.getMinutes()).padStart(2, '0');
- const seconds = String(date.getSeconds()).padStart(2, '0');
- // 返回 hh:mm:ss 格式的时间字符串
- return `${hours}:${minutes}:${seconds}`;
- },
- // 上班开始时间段
- startTimeChange(formData, index) {
- if (index === 0) {
- return {
- selectableRange: `${this.defaultStartTime()} - ${this.defaultEndTime()}`
- }
- }
- const prevEndTime = formData.schedulingTime[index - 1]?.end;
- if (!prevEndTime) return {
- selectableRange: `${this.defaultStartTime()} - ${this.defaultEndTime()}`
- };
- return {
- selectableRange: `${this.formatTimeValue(prevEndTime)} - ${this.defaultEndTime()}`
- }
- },
- formatTimeValue(time) {
- if (!time) return '';
- let date;
- if (time instanceof Date) {
- date = time;
- } else if (typeof time === 'string') {
- const [hours, minutes, seconds] = time.split(':').map(Number);
- date = new Date();
- date.setHours(hours || 0, minutes || 0, seconds || 0);
- } else {
- return '';
- }
- if (isNaN(date.getTime())) return '';
- const hours = String(date.getHours()).padStart(2, '0');
- const minutes = String(date.getMinutes()).padStart(2, '0');
- const seconds = String(date.getSeconds()).padStart(2, '0');
- return `${hours}:${minutes}:${seconds}`;
- },
- parseTime(timeStr) {
- if (!timeStr) return null;
- let date = new Date();
- if (typeof timeStr === 'string') {
- const [hours, minutes, seconds] = timeStr.split(':').map(Number);
- date.setHours(hours || 0, minutes || 0, seconds || 0);
- } else if (timeStr instanceof Date) {
- return timeStr;
- }
- return date;
- },
- // 获取日期字符串
- getSpecificDate(timeStr) {
- const date = timeStr ? new Date(timeStr) : new Date();
- const year = date.getFullYear();
- const month = date.getMonth() + 1;
- const day = date.getDate();
- return year + '-' + (month < 10 ? '0' : "") + month + '-' + (day < 10 ? '0' : "") + day;
- },
- // 获取选择日期排班详情
- getDetail(date_time) {
- this.loading = true;
- const params = {
- r: 'store/client/reserve-staff/technician-scheduling-detail',
- user_id: this.currentUserId,
- date_time,
- ...this.search
- };
- request({ params, method: 'get' }).then(res => {
- const { code, msg, data } = res.data;
- if (code) throw new Error(msg);
- // 初始化表单
- this.form = this.initData();
- if (data.id) {
- // 转换接口返回的数据结构为表单所需结构
- const schedulingTime = data.schedulingTime?.map(item => ({
- id: item.id,
- start: this.parseTime(item.start_time),
- end: this.parseTime(item.end_time)
- })) || [];
- this.form = {
- ...this.form,
- id: data.id,
- is_all_day: data.is_all_day,
- user_id: data.user_id,
- type: data.type,
- date_time: data.date_time,
- schedulingTime
- };
- this.isShowDelete = true;
- }
- }).catch((error) => {
- this.$message.error(error.message);
- }).finally(() => {
- this.loading = false;
- });
- },
- //获取排班列表
- loadData() {
- this.loading = true;
- const params = {
- r: 'store/client/reserve-staff/scheduling',
- date: this.currentDate,
- month: this.currentMonth,
- user_id: this.currentUserId,
- ...this.search
- };
- request({ params, method: 'get' }).then(res => {
- const { code, msg, data } = res.data;
- this.default_start_time = data.default_start_time;
- this.default_end_time = data.default_end_time;
- if (code) throw new Error(msg);
- const { time_data, user_list, user_detail } = data;
- this.userList = user_list;
- this.timeData = time_data;
- if (!this.currentUserId) {
- this.currentUserId = user_detail.user_id;
- }
- this.getDetail(this.currentDate);
- }).catch((error) => {
- this.$message.error(error.message);
- }).finally(() => {
- this.loading = false;
- });
- },
- //保存排班
- saveScheduling() {
- if (this.form.is_all_day === 0 && !this.form.schedulingTime.length) {
- this.$message.error('请选择时间段');
- return;
- }
- if (this.checkTimeOverlap(this.form.schedulingTime)) {
- this.$message.error('时间段不能重叠');
- return;
- }
- // 转换时间数据结构
- const schedulingTime = this.form.schedulingTime.map(item => ({
- id: item.id || 0,
- start_time: this.formatTimeValue(item.start),
- end_time: this.formatTimeValue(item.end)
- }));
- const postData = {
- ...this.form,
- schedulingTime
- };
- this.loading = true;
- let params = {
- r: 'store/client/reserve-staff/scheduling'
- };
- request({
- params: params,
- method: 'post',
- data: postData
- }).then(res => {
- const { code, msg } = res.data;
- if (code) throw new Error(msg);
- this.loadData();
- this.$message.success(res.data.msg);
- }).catch((error) => {
- this.$message.error(error.message);
- }).finally(() => {
- this.loading = false;
- });
- },
- saveMonthScheduling() {
- if (this.form1.is_all_day === 0 && !this.form1.schedulingTime.length) {
- this.$message.error('请选择时间段');
- return;
- }
- if (this.checkTimeOverlap(this.form1.schedulingTime)) {
- this.$message.error('时间段不能重叠');
- return;
- }
- // 转换时间数据结构
- const schedulingTime = this.form1.schedulingTime.map(item => ({
- id: item.id || 0,
- start_time: this.formatTimeValue(item.start),
- end_time: this.formatTimeValue(item.end)
- }));
- this.loading = true;
- const params = {
- r: 'store/client/reserve-staff/set-all-month'
- };
- const postData = {
- ...this.form1,
- date: this.currentDate,
- month: this.currentMonth,
- user_id: this.userDetail.user_id,
- schedulingTime
- };
- request({
- params,
- method: 'post',
- data: postData
- }).then(res => {
- const { code, msg } = res.data;
- if (code) throw new Error(msg);
- this.dialogScheduling = false;
- this.loadData();
- this.$message.success(res.data.msg);
- }).catch((error) => {
- this.$message.error(error.message);
- }).finally(() => {
- this.loading = false;
- });
- },
- // 时间段重复验证
- checkTimeOverlap(timeDataArray) {
- // 将每个时间段转换为Date对象
- const dateRanges = timeDataArray.map(data => {
- const { start, end } = data;
- return {
- start: new Date(start),
- end: new Date(end)
- };
- });
- // 检查每个时间段是否与其他时间段重叠
- for (let i = 0; i < dateRanges.length; i++) {
- for (let j = i + 1; j < dateRanges.length; j++) {
- const range1 = dateRanges[i];
- const range2 = dateRanges[j];
- // 检查是否重叠
- if (range1.start < range2.end && range1.end > range2.start) {
- return true; // 有重叠
- }
- }
- }
- return false; // 没有重叠
- },
- deleteTimeData(data, index) {
- // 判断是否为当前日期的最后一个时间段
- if (index === this.form.schedulingTime.length - 1 && this.form.schedulingTime[index - 1] === undefined) {
- // 删除当天排期
- this.deleteScheduling()
- return
- }
- let params = {
- r: 'store/client/reserve-staff/del-scheduling-time',
- user_id: this.currentUserId,
- id: data.id,
- status: -1,
- };
- request({
- params: params,
- method: 'post',
- data: {
- user_id: this.currentUserId,
- id: data.id,
- status: -1,
- }
- }).then(res => {
- const { code, msg } = res.data;
- if (code) throw new Error(msg);
- this.form.schedulingTime.splice(index, 1);
- }).catch((error) => {
- this.$message.error(error.message);
- }).finally(() => {
- this.loading = false;
- });
- },
- deleteScheduling() {
- const params = {
- r: 'store/client/reserve-staff/delete-scheduling',
- };
- request({
- params,
- method: 'post',
- data: {
- user_id: this.currentUserId,
- id: this.form.id,
- status: -1,
- }
- }).then(res => {
- const { code, msg } = res.data;
- if (code) throw new Error(msg);
- this.$message.success(res.data.msg);
- this.isShowDelete = false
- this.loadData();
- }).catch((error) => {
- this.$message.error(error.message);
- }).finally(() => {
- this.loading = false;
- });
- }
- }
- });
- </script>
- <style lang="scss" scoped>
- .scheduling-box {
- .flex-between {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .scheduling-aside {
- color: #333333;
- background-color: #f8f8f8;
- padding: 32px;
- box-sizing: border-box;
- margin-right: 12px;
- border-radius: 8px;
- .date-box {
- padding-bottom: 32px;
- }
- .title {
- font-size: 20px;
- font-weight: bold;
- margin-bottom: 12px;
- }
- .sub-title,
- .date-title {
- font-size: 18px;
- font-weight: bold;
- }
- }
- .my_time_picker {
- .el-date-editor {
- width: 185px;
- }
- .el-range-input {
- width: 60px;
- }
- .el-range-separator {
- width: 12%;
- }
- }
- .scheduling-main {
- color: #333;
- text-align: center;
- background-color: #f8f8f8;
- border-radius: 8px;
- .operate-box {
- margin-bottom: 20px;
- }
- .cell-box {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
- flex-direction: column;
- font-size: 13px;
- .day-text {
- font-size: 26px;
- }
- .time-text {
- overflow: hidden;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- }
- }
- /* 日历设置 */
- .el-calendar {
- border-radius: 8px;
- }
- .el-calendar__body {
- padding: 0;
- }
- .el-calendar-table {
- &:not(.is-range) {
- td.next {
- pointer-events: none;
- .calendar-day-box {
- background-color: #FFF;
- }
- }
- td.prev {
- pointer-events: none;
- .calendar-day-box {
- background-color: #FFF;
- }
- }
- }
- .el-calendar-day {
- padding: 0;
- }
- .calendar-day-box {
- width: 100%;
- height: 100%;
- color: #7F7F7F;
- background-color: #f1f5f6;
- }
- }
- .calendar-day-box.is-active {
- color: #02a7f0;
- background-color: #FFF;
- }
- .is-selected {
- .calendar-day-box {
- color: #FFF;
- background-color: #02a7f0;
- }
- }
- }
- }
- </style>
|