367 lines
7.9 KiB
Vue
367 lines
7.9 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="container">
|
|||
|
|
<!-- 标题和日期选择区 -->
|
|||
|
|
<view class="header">
|
|||
|
|
<view class="title-bar">
|
|||
|
|
<text class="title">人员签到表</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="date-picker-container">
|
|||
|
|
<picker
|
|||
|
|
mode="date"
|
|||
|
|
fields="month"
|
|||
|
|
:value="currentDate"
|
|||
|
|
@change="onDateChange"
|
|||
|
|
class="date-picker"
|
|||
|
|
>
|
|||
|
|
<view class="picker-view">
|
|||
|
|
<text>{{ formattedDate }}</text>
|
|||
|
|
</view>
|
|||
|
|
</picker>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 加载状态 -->
|
|||
|
|
<view v-if="loading" class="loading-view">
|
|||
|
|
<loading class="loading" color="#007aff" size="16"></loading>
|
|||
|
|
<text class="loading-text">加载中...</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 签到表区域(支持横向滚动) -->
|
|||
|
|
<scroll-view
|
|||
|
|
v-else
|
|||
|
|
scroll-x="true"
|
|||
|
|
class="table-scroll"
|
|||
|
|
@scroll="onScroll"
|
|||
|
|
>
|
|||
|
|
<view class="table-container">
|
|||
|
|
<!-- 表头 -->
|
|||
|
|
<view class="table-header">
|
|||
|
|
<view class="table-cell name-cell">姓名</view>
|
|||
|
|
<view
|
|||
|
|
class="table-cell day-cell"
|
|||
|
|
v-for="(day, index) in dateLength"
|
|||
|
|
:key="index"
|
|||
|
|
>
|
|||
|
|
{{ index + 1 }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 表格内容 -->
|
|||
|
|
<view class="table-body">
|
|||
|
|
<view
|
|||
|
|
class="table-row"
|
|||
|
|
v-for="(user, userIndex) in userList"
|
|||
|
|
:key="userIndex"
|
|||
|
|
:class="{ 'odd-row': userIndex % 2 === 1 }"
|
|||
|
|
>
|
|||
|
|
<view class="table-cell name-cell">{{ user.nickName }}</view>
|
|||
|
|
<view
|
|||
|
|
class="table-cell day-cell"
|
|||
|
|
v-for="(day, dayIndex) in dateLength"
|
|||
|
|
:key="dayIndex"
|
|||
|
|
:style="getCellStyle(user, dayIndex + 1)"
|
|||
|
|
>
|
|||
|
|
{{ getAttendanceStatusText(user, dayIndex + 1) }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
|
|||
|
|
<!-- 图例说明 -->
|
|||
|
|
<view class="legend-container">
|
|||
|
|
<view class="legend-item">
|
|||
|
|
<view class="legend-color" style="background-color: #fdf6e4;"></view>
|
|||
|
|
<text class="legend-text">出差</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="legend-item">
|
|||
|
|
<view class="legend-color" style="background-color: #e3f2fd;"></view>
|
|||
|
|
<text class="legend-text">请假</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { getDateLength, listOaAttendance } from "@/api/oa/oaAttendance";
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
// 用户列表数据
|
|||
|
|
userList: [],
|
|||
|
|
// 当前月份天数
|
|||
|
|
dateLength: 31,
|
|||
|
|
// 当前选中日期
|
|||
|
|
currentDate: '',
|
|||
|
|
// 格式化显示的日期
|
|||
|
|
formattedDate: '',
|
|||
|
|
// 加载状态
|
|||
|
|
loading: true,
|
|||
|
|
// 查询参数
|
|||
|
|
queryParams: {
|
|||
|
|
selectTime: ''
|
|||
|
|
},
|
|||
|
|
// 滚动位置记录
|
|||
|
|
scrollLeft: 0
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onLoad() {
|
|||
|
|
// 初始化日期为当前月份
|
|||
|
|
const now = new Date();
|
|||
|
|
const year = now.getFullYear();
|
|||
|
|
const month = now.getMonth() + 1;
|
|||
|
|
this.currentDate = `${year}-${month.toString().padStart(2, '0')}-01`;
|
|||
|
|
this.formattedDate = `${year}年${month}月`;
|
|||
|
|
this.queryParams.selectTime = this.currentDate;
|
|||
|
|
|
|||
|
|
// 获取数据
|
|||
|
|
this.getDateCount();
|
|||
|
|
this.getAttendanceList();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
methods: {
|
|||
|
|
// 获取当月天数
|
|||
|
|
getDateCount() {
|
|||
|
|
getDateLength().then(res => {
|
|||
|
|
this.dateLength = res.data;
|
|||
|
|
}).catch(err => {
|
|||
|
|
console.error('获取日期长度失败', err);
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取签到列表数据
|
|||
|
|
getAttendanceList() {
|
|||
|
|
this.loading = true;
|
|||
|
|
listOaAttendance(this.queryParams).then(res => {
|
|||
|
|
this.userList = res.rows || [];
|
|||
|
|
this.loading = false;
|
|||
|
|
}).catch(err => {
|
|||
|
|
console.error('获取签到数据失败', err);
|
|||
|
|
this.loading = false;
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 日期选择变化
|
|||
|
|
onDateChange(e) {
|
|||
|
|
const value = e.detail.value;
|
|||
|
|
this.currentDate = value;
|
|||
|
|
|
|||
|
|
// 格式化显示
|
|||
|
|
const [year, month] = value.split('-');
|
|||
|
|
this.formattedDate = `${year}年${month}月`;
|
|||
|
|
|
|||
|
|
// 更新查询参数并重新加载数据
|
|||
|
|
this.queryParams.selectTime = value + '-01';
|
|||
|
|
this.getAttendanceList();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理滚动事件
|
|||
|
|
onScroll(e) {
|
|||
|
|
this.scrollLeft = e.detail.scrollLeft;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取单元格样式
|
|||
|
|
getCellStyle(user, dayIndex) {
|
|||
|
|
// 查找对应日期的考勤记录
|
|||
|
|
const attendance = user.attendances
|
|||
|
|
? user.attendances.find(item => item.attendanceDay === dayIndex)
|
|||
|
|
: null;
|
|||
|
|
|
|||
|
|
if (attendance) {
|
|||
|
|
// 根据不同状态返回不同样式
|
|||
|
|
if (attendance.projectId === 0) {
|
|||
|
|
return { backgroundColor: '#fdf6e4' }; // 出差
|
|||
|
|
} else if (attendance.projectId === 1) {
|
|||
|
|
return { backgroundColor: '#e3f2fd' }; // 请假
|
|||
|
|
} else if (attendance.color) {
|
|||
|
|
return { backgroundColor: attendance.color }; // 其他项目颜色
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 默认样式
|
|||
|
|
return { backgroundColor: '#ffffff' };
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取考勤状态文本
|
|||
|
|
getAttendanceStatusText(user, dayIndex) {
|
|||
|
|
if (!user.attendances || user.attendances.length === 0) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const attendance = user.attendances.find(item => item.attendanceDay === dayIndex);
|
|||
|
|
if (!attendance) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据项目ID返回对应文本
|
|||
|
|
if (attendance.projectId === 0) {
|
|||
|
|
return '出差';
|
|||
|
|
} else if (attendance.projectId === 1) {
|
|||
|
|
return '请假';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.container {
|
|||
|
|
flex: 1;
|
|||
|
|
padding: 16rpx;
|
|||
|
|
background-color: #f5f5f5;
|
|||
|
|
min-height: 100vh;
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 头部样式 */
|
|||
|
|
.header {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 16rpx;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title-bar {
|
|||
|
|
padding: 10rpx 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-size: 34rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.date-picker-container {
|
|||
|
|
background-color: #fff;
|
|||
|
|
border-radius: 8rpx;
|
|||
|
|
padding: 16rpx;
|
|||
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.date-picker {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.picker-view {
|
|||
|
|
text-align: center;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: #007aff;
|
|||
|
|
padding: 8rpx 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 加载状态样式 */
|
|||
|
|
.loading-view {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
padding: 60rpx 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.loading {
|
|||
|
|
margin-bottom: 16rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.loading-text {
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 表格样式 */
|
|||
|
|
.table-scroll {
|
|||
|
|
width: 100%;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
background-color: #fff;
|
|||
|
|
border-radius: 10rpx;
|
|||
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.table-container {
|
|||
|
|
min-width: 100%;
|
|||
|
|
display: inline-block;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.table-header {
|
|||
|
|
display: flex;
|
|||
|
|
background-color: #f8f9fa;
|
|||
|
|
border-bottom: 1px solid #eee;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.table-body {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.table-row {
|
|||
|
|
display: flex;
|
|||
|
|
border-bottom: 1px solid #eee;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.table-row:last-child {
|
|||
|
|
border-bottom: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.odd-row {
|
|||
|
|
background-color: #fafafa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.table-cell {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
padding: 20rpx 0;
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
border-right: 1px solid #eee;
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.table-cell:last-child {
|
|||
|
|
border-right: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.name-cell {
|
|||
|
|
min-width: 140rpx;
|
|||
|
|
flex: 0 0 140rpx;
|
|||
|
|
background-color: #f0f0f0;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.day-cell {
|
|||
|
|
min-width: 80rpx;
|
|||
|
|
flex: 0 0 80rpx;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 图例样式 */
|
|||
|
|
.legend-container {
|
|||
|
|
display: flex;
|
|||
|
|
padding: 16rpx;
|
|||
|
|
background-color: #fff;
|
|||
|
|
border-radius: 10rpx;
|
|||
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.legend-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-right: 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.legend-color {
|
|||
|
|
width: 24rpx;
|
|||
|
|
height: 24rpx;
|
|||
|
|
margin-right: 10rpx;
|
|||
|
|
border-radius: 4rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.legend-text {
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
</style>
|