1.新增排班时间管理页面及其对接接口
2.对接排班人员列表接口 3.修改部分样式
This commit is contained in:
@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<div class="chart-header">
|
||||
<TitleComponent title="审批" :font-level="2" />
|
||||
<span>更多</span>
|
||||
</div>
|
||||
<div class="approval-content">
|
||||
<div
|
||||
v-for="(item, index) in approvalData"
|
||||
:key="index"
|
||||
class="approval-item"
|
||||
>
|
||||
<div class="approval-left">
|
||||
<div class="approval-icon">
|
||||
<img :src="item.iconPath" :alt="item.type">
|
||||
</div>
|
||||
<div class="approval-info">
|
||||
<div class="info">
|
||||
<div class="type">{{ item.type }}</div>
|
||||
<div class="day">{{ item.days }}天</div>
|
||||
</div>
|
||||
<div class="info1">
|
||||
<div class="time">
|
||||
<img src="@/assets/demo/time.png" alt="时间">
|
||||
<span>{{ item.timeRange }}</span>
|
||||
</div>
|
||||
<div class="people">
|
||||
<img src="@/assets/demo/people.png" alt="人员">
|
||||
<span>{{ item.people }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="approval-tag">
|
||||
<el-tag :type="item.statusType">{{ item.status }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import TitleComponent from '@/components/TitleComponent/index.vue';
|
||||
|
||||
// 接收从父组件传入的数据
|
||||
const props = defineProps({
|
||||
approvalData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.chart-header span {
|
||||
color: #186DF5;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.approval-content {
|
||||
background-color: white;
|
||||
.approval-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: #F2F8FC;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #F2F3F5;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.approval-item:hover {
|
||||
border-color: #E4E6EB;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.approval-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.approval-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #186DF5;
|
||||
}
|
||||
|
||||
.approval-icon img {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.approval-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.info .type {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info .day {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.info1 .time,
|
||||
.info1 .people {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: rgba(113, 128, 150, 1);
|
||||
}
|
||||
|
||||
.info1 img {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.approval-tag {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.approval-tag .el-tag {
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<div class="chart-header">
|
||||
<TitleComponent title="日历" :font-level="2" />
|
||||
</div>
|
||||
<div class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
<el-button size="small" type="text" @click="prevMonth">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
</el-button>
|
||||
<span class="current-month">{{ currentYear }} {{ currentMonthName }}</span>
|
||||
<el-button size="small" type="text" @click="nextMonth">
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="calendar-weekdays">
|
||||
<span v-for="day in weekdays" :key="day" class="weekday">{{ day }}</span>
|
||||
</div>
|
||||
|
||||
<div class="calendar-days">
|
||||
<!-- 上月剩余天数 -->
|
||||
<div v-for="(day, index) in prevMonthDays" :key="'prev-' + index" class="day prev-month-day">
|
||||
{{ day }}
|
||||
</div>
|
||||
|
||||
<!-- 当月天数 -->
|
||||
<div
|
||||
v-for="day in currentMonthDays"
|
||||
:key="day"
|
||||
class="day current-month-day"
|
||||
:class="{
|
||||
'current-day': isCurrentDay(day),
|
||||
'selected-day': isSelectedDay(day)
|
||||
}"
|
||||
@click="selectDay(day)"
|
||||
>
|
||||
{{ day }}
|
||||
<!-- 今天有红点标记 -->
|
||||
<span v-if="isToday(day)" class="today-marker"></span>
|
||||
<!-- 考勤状态标记 -->
|
||||
<span v-if="getAttendanceStatus(day)" class="attendance-marker" :class="getAttendanceStatus(day)"></span>
|
||||
</div>
|
||||
|
||||
<!-- 下月开始天数 -->
|
||||
<div v-for="(day, index) in nextMonthDays" :key="'next-' + index" class="day next-month-day">
|
||||
{{ day }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TitleComponent from '@/components/TitleComponent/index.vue';
|
||||
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
// 接收从父组件传入的数据
|
||||
const props = defineProps({
|
||||
calendarData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
// 初始化当前日期
|
||||
today: new Date(),
|
||||
currentDate: new Date(2025, 8, 27), // 2025年9月27日,截图中显示的日期
|
||||
selectedDate: new Date(2025, 8, 27),
|
||||
|
||||
// 模拟考勤数据
|
||||
attendanceData: {
|
||||
2025: {
|
||||
9: {
|
||||
1: 'normal',
|
||||
4: 'late',
|
||||
8: 'absent',
|
||||
10: 'leave',
|
||||
15: 'normal',
|
||||
20: 'normal',
|
||||
25: 'late',
|
||||
27: 'normal'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化当前日期
|
||||
const today = ref(props.calendarData.today);
|
||||
const currentDate = ref(props.calendarData.currentDate);
|
||||
const selectedDate = ref(props.calendarData.selectedDate);
|
||||
|
||||
// 模拟考勤数据
|
||||
const attendanceData = ref(props.calendarData.attendanceData);
|
||||
|
||||
// 星期几的显示
|
||||
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
|
||||
// 计算属性
|
||||
const currentYear = computed(() => currentDate.value.getFullYear());
|
||||
const currentMonth = computed(() => currentDate.value.getMonth());
|
||||
const currentMonthName = computed(() => monthNames[currentMonth.value]);
|
||||
|
||||
// 获取当月的天数
|
||||
const currentMonthDays = computed(() => {
|
||||
return new Date(currentYear.value, currentMonth.value + 1, 0).getDate();
|
||||
});
|
||||
|
||||
// 获取当月第一天是星期几(0-6,0是星期日)
|
||||
const firstDayOfMonth = computed(() => {
|
||||
return new Date(currentYear.value, currentMonth.value, 1).getDay();
|
||||
});
|
||||
|
||||
// 获取上月剩余天数
|
||||
const prevMonthDays = computed(() => {
|
||||
const days = [];
|
||||
const prevMonth = new Date(currentYear.value, currentMonth.value, 0).getDate(); // 上月最后一天
|
||||
|
||||
for (let i = firstDayOfMonth.value - 1; i >= 0; i--) {
|
||||
days.push(prevMonth - i);
|
||||
}
|
||||
|
||||
return days;
|
||||
});
|
||||
|
||||
// 获取下月开始天数
|
||||
const nextMonthDays = computed(() => {
|
||||
const days = [];
|
||||
const totalDays = prevMonthDays.value.length + currentMonthDays.value;
|
||||
const nextDays = 35 - totalDays; // 显示5周,共35天
|
||||
|
||||
for (let i = 1; i <= nextDays; i++) {
|
||||
days.push(i);
|
||||
}
|
||||
|
||||
return days;
|
||||
});
|
||||
|
||||
// 方法
|
||||
const prevMonth = () => {
|
||||
currentDate.value = new Date(currentYear.value, currentMonth.value - 1, 1);
|
||||
};
|
||||
|
||||
const nextMonth = () => {
|
||||
currentDate.value = new Date(currentYear.value, currentMonth.value + 1, 1);
|
||||
};
|
||||
|
||||
const selectDay = (day) => {
|
||||
selectedDate.value = new Date(currentYear.value, currentMonth.value, day);
|
||||
|
||||
// 显示选择的日期和考勤状态
|
||||
let message = `Selected: ${currentMonthName.value} ${day}, ${currentYear.value}`;
|
||||
const status = getAttendanceStatus(day);
|
||||
if (status) {
|
||||
const statusMap = {
|
||||
normal: '正常',
|
||||
late: '迟到',
|
||||
absent: '缺勤',
|
||||
leave: '请假'
|
||||
};
|
||||
message += ` - 考勤状态: ${statusMap[status] || '未知'}`;
|
||||
}
|
||||
ElMessage.success(message);
|
||||
};
|
||||
|
||||
// 获取考勤状态
|
||||
const getAttendanceStatus = (day) => {
|
||||
if (attendanceData.value[currentYear.value] &&
|
||||
attendanceData.value[currentYear.value][currentMonth.value + 1] &&
|
||||
attendanceData.value[currentYear.value][currentMonth.value + 1][day]) {
|
||||
return attendanceData.value[currentYear.value][currentMonth.value + 1][day];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const isToday = (day) => {
|
||||
return day === today.value.getDate() &&
|
||||
currentMonth.value === today.value.getMonth() &&
|
||||
currentYear.value === today.value.getFullYear();
|
||||
};
|
||||
|
||||
const isCurrentDay = (day) => {
|
||||
return day === today.value.getDate() &&
|
||||
currentMonth.value === today.value.getMonth() &&
|
||||
currentYear.value === today.value.getFullYear();
|
||||
};
|
||||
|
||||
const isSelectedDay = (day) => {
|
||||
return day === selectedDate.value.getDate() &&
|
||||
currentMonth.value === selectedDate.value.getMonth() &&
|
||||
currentYear.value === selectedDate.value.getFullYear();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.chart-header span {
|
||||
color: #186DF5;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
// border: 1px solid #F2F3F5;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.current-month {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.calendar-weekdays {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.weekday {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.calendar-days {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 8px;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.day {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.prev-month-day,
|
||||
.next-month-day {
|
||||
color: #C0C4CC;
|
||||
}
|
||||
|
||||
.current-month-day {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.current-month-day:hover {
|
||||
background-color: #ECF5FF;
|
||||
color: #186DF5;
|
||||
}
|
||||
|
||||
.current-day {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.today-marker {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: #FF6B3B;
|
||||
}
|
||||
|
||||
.selected-day {
|
||||
background-color: #186DF5;
|
||||
color: white !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.selected-day:hover {
|
||||
background-color: #4096ff;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
// 考勤状态标记样式
|
||||
.attendance-marker {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.attendance-marker.normal {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.attendance-marker.late {
|
||||
background-color: #faad14;
|
||||
}
|
||||
|
||||
.attendance-marker.absent {
|
||||
background-color: #f5222d;
|
||||
}
|
||||
|
||||
.attendance-marker.leave {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<TitleComponent title="今日出勤" :font-level="2" />
|
||||
<div class="todayAttend">
|
||||
<div class="todayAttendItem">
|
||||
<img :src="props.todayAttendData.attendance.icon" alt="" width="30px" height="30px">
|
||||
<div class="todayAttendItemInfo">
|
||||
<span class="todayAttendItemTitle">出勤</span>
|
||||
<span class="todayAttendItemNum">{{ props.todayAttendData.attendance.count }}人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="todayAttendItem">
|
||||
<img :src="props.todayAttendData.late.icon" alt="" width="30px" height="30px">
|
||||
<div class="todayAttendItemInfo">
|
||||
<span class="todayAttendItemTitle">迟到</span>
|
||||
<span class="todayAttendItemNum">{{ props.todayAttendData.late.count }}人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="todayAttendItem">
|
||||
<img :src="props.todayAttendData.earlyLeave.icon" alt="" width="30px" height="30px">
|
||||
<div class="todayAttendItemInfo">
|
||||
<span class="todayAttendItemTitle">早退</span>
|
||||
<span class="todayAttendItemNum">{{ props.todayAttendData.earlyLeave.count }}人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="todayAttendItem">
|
||||
<img :src="props.todayAttendData.absent.icon" alt="" width="30px" height="30px">
|
||||
<div class="todayAttendItemInfo">
|
||||
<span class="todayAttendItemTitle">缺勤</span>
|
||||
<span class="todayAttendItemNum">{{ props.todayAttendData.absent.count }}人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import TitleComponent from '@/components/TitleComponent/index.vue';
|
||||
|
||||
// 接收从父组件传入的数据
|
||||
const props = defineProps({
|
||||
todayAttendData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.todayAttend {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.todayAttendItem {
|
||||
width: 110px;
|
||||
height: 100px;
|
||||
background: #E5F0FF;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
.todayAttendItemInfo{
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
.todayAttendItemTitle {
|
||||
color: rgba(113, 128, 150, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.todayAttendItemNum {
|
||||
font-size: 16px;
|
||||
color: rgba(0, 30, 59, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user