2025-09-20 11:26:02 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="schedule-table-container">
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
:data="scheduleData"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
max-height="600"
|
|
|
|
|
|
stripe
|
|
|
|
|
|
border
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 固定列 -->
|
|
|
|
|
|
<el-table-column fixed prop="name" label="姓名" width="120" align="center" />
|
|
|
|
|
|
<el-table-column fixed="left" prop="position" label="岗位" width="120" align="center" />
|
|
|
|
|
|
<el-table-column fixed="left" prop="weeklyHours" label="周总计/小时" width="120" align="center" />
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 日期列 - 纵向显示号数和星期几 -->
|
|
|
|
|
|
<el-table-column
|
|
|
|
|
|
v-for="(dateInfo, index) in currentMonthDates"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
:prop="`day${index + 1}`"
|
|
|
|
|
|
width="80"
|
|
|
|
|
|
align="center"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="vertical-header">
|
|
|
|
|
|
<div class="date-number">{{ dateInfo.date }}</div>
|
|
|
|
|
|
<div class="week-day">{{ dateInfo.weekDay }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2025-09-22 16:15:50 +08:00
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="schedule-cell"
|
|
|
|
|
|
:style="{ color: getShiftColor(scope.row[`day${index + 1}`]) }"
|
|
|
|
|
|
@click="handleCellClick(scope.row, {...dateInfo, index}, scope)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ scope.row[`day${index + 1}`] }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2025-09-20 11:26:02 +08:00
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
2025-09-22 20:47:13 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 分页组件 -->
|
|
|
|
|
|
<div class="pagination-container">
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
v-model:current-page="currentPage"
|
|
|
|
|
|
v-model:page-size="pageSize"
|
|
|
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
|
:total="total"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-09-20 11:26:02 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue';
|
|
|
|
|
|
|
2025-09-22 16:15:50 +08:00
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
'edit-schedule': [rowData: any, columnData: any, cellEvent: any]
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
2025-09-20 11:26:02 +08:00
|
|
|
|
// 员工列表
|
|
|
|
|
|
const employees = [
|
|
|
|
|
|
{ name: '张三', position: '水泥工', weeklyHours: 142 },
|
|
|
|
|
|
{ name: '李四', position: '电工', weeklyHours: 138 },
|
|
|
|
|
|
{ name: '王五', position: '木工', weeklyHours: 145 },
|
|
|
|
|
|
{ name: '赵六', position: '钢筋工', weeklyHours: 140 },
|
|
|
|
|
|
{ name: '钱七', position: '油漆工', weeklyHours: 135 },
|
|
|
|
|
|
{ name: '孙八', position: '瓦工', weeklyHours: 143 },
|
|
|
|
|
|
{ name: '周九', position: '钳工', weeklyHours: 137 },
|
|
|
|
|
|
{ name: '吴十', position: '管道工', weeklyHours: 139 },
|
|
|
|
|
|
{ name: '郑十一', position: '焊工', weeklyHours: 141 },
|
|
|
|
|
|
{ name: '王十二', position: '起重工', weeklyHours: 136 }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 排班类型
|
|
|
|
|
|
const shifts = ['早班', '中班', '晚班', '休息'];
|
|
|
|
|
|
|
2025-09-22 16:15:50 +08:00
|
|
|
|
// 排班类型与颜色的映射关系
|
|
|
|
|
|
const shiftColorMap = {
|
|
|
|
|
|
'早班': '#67c23a', // 绿色
|
|
|
|
|
|
'中班': '#e6a23c', // 橙色
|
|
|
|
|
|
'晚班': '#409eff', // 蓝色
|
|
|
|
|
|
'休息': '#909399' // 灰色
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 根据排班类型获取对应的颜色
|
|
|
|
|
|
const getShiftColor = (shiftType: string) => {
|
|
|
|
|
|
return shiftColorMap[shiftType as keyof typeof shiftColorMap] || '#333';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-20 11:26:02 +08:00
|
|
|
|
// 获取当前月的日期信息
|
|
|
|
|
|
const currentMonthDates = ref<any[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算当前月份并生成日期信息
|
|
|
|
|
|
const getCurrentMonthDates = () => {
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
const year = today.getFullYear();
|
|
|
|
|
|
const month = today.getMonth(); // 0-11
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当月第一天
|
|
|
|
|
|
const firstDay = new Date(year, month, 1);
|
|
|
|
|
|
// 获取当月最后一天
|
|
|
|
|
|
const lastDay = new Date(year, month + 1, 0);
|
|
|
|
|
|
// 当月总天数
|
|
|
|
|
|
const daysInMonth = lastDay.getDate();
|
|
|
|
|
|
|
|
|
|
|
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
|
|
|
|
|
const dates = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 生成当月所有日期信息
|
|
|
|
|
|
for (let i = 1; i <= daysInMonth; i++) {
|
|
|
|
|
|
const date = new Date(year, month, i);
|
|
|
|
|
|
const weekDayIndex = date.getDay(); // 0-6,0表示星期日
|
|
|
|
|
|
dates.push({
|
|
|
|
|
|
date: i,
|
|
|
|
|
|
weekDay: weekdays[weekDayIndex],
|
|
|
|
|
|
fullDate: `${year}-${String(month + 1).padStart(2, '0')}-${String(i).padStart(2, '0')}`
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return dates;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 20:47:13 +08:00
|
|
|
|
// 分页相关状态
|
|
|
|
|
|
const currentPage = ref(1);
|
|
|
|
|
|
const pageSize = ref(10);
|
|
|
|
|
|
const total = ref(50); // 总数据条数,模拟数据
|
|
|
|
|
|
|
2025-09-20 11:26:02 +08:00
|
|
|
|
// 生成排班数据
|
|
|
|
|
|
const scheduleData = computed(() => {
|
2025-09-22 20:47:13 +08:00
|
|
|
|
const startIndex = (currentPage.value - 1) * pageSize.value;
|
|
|
|
|
|
const endIndex = startIndex + pageSize.value;
|
|
|
|
|
|
|
|
|
|
|
|
return Array.from({ length: total.value }, (_, index) => {
|
2025-09-20 11:26:02 +08:00
|
|
|
|
// 循环使用员工数据
|
|
|
|
|
|
const employee = employees[index % employees.length];
|
|
|
|
|
|
|
|
|
|
|
|
// 为每行生成不同的排班组合
|
|
|
|
|
|
const rowData = {
|
|
|
|
|
|
name: employee.name,
|
|
|
|
|
|
position: employee.position,
|
|
|
|
|
|
weeklyHours: employee.weeklyHours
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 为当月每一天生成排班数据
|
|
|
|
|
|
currentMonthDates.value.forEach((_, dayIndex) => {
|
|
|
|
|
|
// 使用不同的种子生成略有变化的排班模式
|
|
|
|
|
|
const seed = (index * 3 + dayIndex + 1) % shifts.length;
|
|
|
|
|
|
rowData[`day${dayIndex + 1}`] = shifts[seed];
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return rowData;
|
2025-09-22 20:47:13 +08:00
|
|
|
|
}).slice(startIndex, endIndex);
|
2025-09-20 11:26:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-22 20:47:13 +08:00
|
|
|
|
// 分页大小变化处理
|
|
|
|
|
|
const handleSizeChange = (size: number) => {
|
|
|
|
|
|
pageSize.value = size;
|
|
|
|
|
|
currentPage.value = 1; // 重置为第一页
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 当前页码变化处理
|
|
|
|
|
|
const handleCurrentChange = (current: number) => {
|
|
|
|
|
|
currentPage.value = current;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-20 11:26:02 +08:00
|
|
|
|
// 组件挂载时获取当前月数据
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
currentMonthDates.value = getCurrentMonthDates();
|
|
|
|
|
|
});
|
2025-09-22 16:15:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理单元格点击事件
|
|
|
|
|
|
const handleCellClick = (rowData: any, columnData: any, cellEvent: any) => {
|
|
|
|
|
|
emit('edit-schedule', rowData, columnData, cellEvent);
|
|
|
|
|
|
};
|
2025-09-20 11:26:02 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.schedule-table-container {
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 优化滚动条样式 */
|
|
|
|
|
|
.schedule-table-container::-webkit-scrollbar {
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-table-container::-webkit-scrollbar-track {
|
|
|
|
|
|
background: #f1f1f1;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-table-container::-webkit-scrollbar-thumb {
|
|
|
|
|
|
background: #c0c4cc;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-table-container::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
|
background: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 优化表格样式 */
|
|
|
|
|
|
:deep(.el-table) {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-table__header-wrapper th) {
|
|
|
|
|
|
background-color: #fafafa;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
padding: 0 !important;
|
|
|
|
|
|
height: auto !important;
|
|
|
|
|
|
min-height: 60px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-table__body-wrapper) {
|
|
|
|
|
|
overflow-x: visible;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 纵向表头样式 */
|
|
|
|
|
|
.vertical-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-22 16:15:50 +08:00
|
|
|
|
/* 排班单元格样式 */
|
|
|
|
|
|
.schedule-cell {
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-cell:hover {
|
|
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
|
|
transform: scale(1.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-20 11:26:02 +08:00
|
|
|
|
.date-number {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.week-day {
|
2025-09-22 20:47:13 +08:00
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 分页容器样式 */
|
|
|
|
|
|
.pagination-container {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 分页组件样式优化 */
|
|
|
|
|
|
:deep(.el-pagination) {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
2025-09-20 11:26:02 +08:00
|
|
|
|
</style>
|