<template>
|
<div class="attendance-detail">
|
<!-- 员工基本信息 -->
|
<el-card class="employee-info-card">
|
<div class="employee-header">
|
<div class="employee-basic">
|
<el-avatar :size="60" :src="employeeInfo.avatar" class="employee-avatar">
|
{{ employeeInfo.name.charAt(0) }}
|
</el-avatar>
|
<div class="employee-details">
|
<h3>{{ employeeInfo.name }}</h3>
|
<p class="employee-department">{{ employeeInfo.department }} · {{ employeeInfo.position }}</p>
|
<p class="employee-contact">
|
<span>工号: {{ employeeInfo.employeeId }}</span>
|
<span>电话: {{ employeeInfo.phone }}</span>
|
</p>
|
</div>
|
</div>
|
<div class="employee-stats">
|
<div class="stat-item">
|
<div class="stat-value">{{ employeeStats.attendanceRate }}%</div>
|
<div class="stat-label">本月出勤率</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-value">{{ employeeStats.workHours }}h</div>
|
<div class="stat-label">总工作时长</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-value">{{ employeeStats.businessTripDays }}</div>
|
<div class="stat-label">出差天数</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
|
<!-- 选项卡 -->
|
<el-card>
|
<el-tabs v-model="activeTab">
|
<el-tab-pane label="日历视图" name="calendar">
|
<attendance-calendar
|
:attendance-data="attendanceData"
|
:business-trip-data="businessTripData"
|
/>
|
</el-tab-pane>
|
|
<el-tab-pane label="出勤记录" name="attendanceList">
|
<personal-attendance-table
|
:data="attendanceData"
|
:loading="loading"
|
/>
|
</el-tab-pane>
|
|
<el-tab-pane label="出差记录" name="businessTripList">
|
<personal-business-trip-table
|
:data="businessTripData"
|
:loading="loading"
|
/>
|
</el-tab-pane>
|
|
<el-tab-pane label="统计报表" name="report">
|
<personal-attendance-report
|
:stats="employeeStats"
|
:attendance-data="attendanceData"
|
/>
|
</el-tab-pane>
|
</el-tabs>
|
</el-card>
|
</div>
|
</template>
|
|
<script>
|
import { generateMockData } from './mockData'
|
|
import AttendanceCalendar from './components/AttendanceCalendar.vue'
|
import PersonalAttendanceTable from './components/PersonalAttendanceTable.vue'
|
import PersonBusiness from './components/PersonBusiness.vue'
|
import PersonalAttendanceReport from './components/PersonalAttendanceReport.vue'
|
|
export default {
|
name: 'AttendanceDetail',
|
components: {
|
AttendanceCalendar,
|
PersonalAttendanceTable,
|
PersonBusiness,
|
PersonalAttendanceReport
|
},
|
data() {
|
return {
|
activeTab: 'calendar',
|
loading: false,
|
employeeInfo: {
|
name: '张三',
|
department: 'OPO项目部',
|
position: '项目经理',
|
employeeId: 'OPO001',
|
phone: '138-1234-5678',
|
avatar: ''
|
},
|
employeeStats: {
|
attendanceRate: 0,
|
workHours: 0,
|
businessTripDays: 0,
|
lateTimes: 0,
|
leaveEarlyTimes: 0
|
},
|
attendanceData: [],
|
businessTripData: []
|
}
|
},
|
created() {
|
this.loadMockData()
|
|
this.getEmployeeInfo()
|
this.loadAttendanceData()
|
},
|
methods: {
|
getEmployeeInfo() {
|
const { employeeId, employeeName } = this.$route.query
|
// 模拟员工信息
|
this.employeeInfo = {
|
name: employeeName || '张三',
|
department: 'OPO项目部',
|
position: '项目经理',
|
employeeId: employeeId || 'OPO001',
|
phone: '138****1234',
|
avatar: ''
|
}
|
},
|
loadMockData() {
|
this.loading = true
|
|
// 模拟异步加载
|
setTimeout(() => {
|
const mockData = generateMockData()
|
this.attendanceData = mockData.attendanceData
|
this.businessTripData = mockData.businessTripData
|
this.calculateStats()
|
this.loading = false
|
}, 500)
|
},
|
|
calculateStats() {
|
const totalDays = 31 // 12月有31天
|
const attendanceDays = this.attendanceData.filter(item =>
|
item.status === 'present' || item.status === 'late'
|
).length
|
|
const lateTimes = this.attendanceData.filter(item =>
|
item.status === 'late'
|
).length
|
|
// 计算出差总天数
|
const businessTripDays = this.businessTripData.reduce((total, trip) => {
|
const start = new Date(trip.startDate)
|
const end = new Date(trip.endDate)
|
const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1
|
return total + days
|
}, 0)
|
|
// 计算总工作时长
|
const totalWorkHours = this.attendanceData.reduce((total, item) => {
|
return total + (item.workHours || 0)
|
}, 0)
|
|
this.employeeStats = {
|
attendanceRate: Math.round((attendanceDays / totalDays) * 100),
|
workHours: totalWorkHours.toFixed(1),
|
businessTripDays: businessTripDays,
|
lateTimes: lateTimes,
|
leaveEarlyTimes: this.attendanceData.filter(item =>
|
item.status === 'leaveEarly'
|
).length
|
}
|
},
|
async loadAttendanceData() {
|
this.loading = true
|
try {
|
await new Promise(resolve => setTimeout(resolve, 500))
|
|
// 生成个人考勤模拟数据
|
this.attendanceData = this.generatePersonalAttendanceData()
|
this.businessTripData = this.generatePersonalBusinessTripData()
|
this.calculateStats()
|
} catch (error) {
|
console.error('加载数据失败:', error)
|
} finally {
|
this.loading = false
|
}
|
},
|
|
generatePersonalAttendanceData() {
|
const data = []
|
const currentMonth = 12 // 12月
|
|
for (let day = 1; day <= 31; day++) {
|
if (Math.random() > 0.2) { // 80%的出勤率
|
data.push({
|
id: day,
|
date: `2024-${currentMonth.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`,
|
checkIn: `08:${String(Math.floor(Math.random() * 30)).padStart(2, '0')}`,
|
checkOut: `18:${String(Math.floor(Math.random() * 30)).padStart(2, '0')}`,
|
status: Math.random() > 0.1 ? '正常' : '迟到',
|
workHours: (8 + Math.random() * 2).toFixed(1)
|
})
|
}
|
}
|
return data
|
},
|
|
generatePersonalBusinessTripData() {
|
return [
|
{
|
id: 1,
|
tripNumber: 'BT202412001',
|
startCity: '北京',
|
endCity: '上海',
|
startDate: '2024-12-05',
|
endDate: '2024-12-08',
|
distance: 1200,
|
purpose: '客户会议',
|
status: '已完成'
|
},
|
{
|
id: 2,
|
tripNumber: 'BT202412002',
|
startCity: '北京',
|
endCity: '广州',
|
startDate: '2024-12-15',
|
endDate: '2024-12-18',
|
distance: 1900,
|
purpose: '项目调研',
|
status: '已完成'
|
}
|
]
|
},
|
|
}
|
}
|
</script>
|
|
<style scoped>
|
.attendance-detail {
|
padding: 20px;
|
}
|
|
.employee-info-card {
|
margin-bottom: 20px;
|
}
|
|
.employee-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.employee-basic {
|
display: flex;
|
align-items: center;
|
}
|
|
.employee-avatar {
|
margin-right: 16px;
|
background-color: #409eff;
|
}
|
|
.employee-details h3 {
|
margin: 0 0 8px 0;
|
font-size: 24px;
|
color: #303133;
|
}
|
|
.employee-department {
|
margin: 0 0 8px 0;
|
color: #606266;
|
}
|
|
.employee-contact {
|
margin: 0;
|
color: #909399;
|
font-size: 14px;
|
}
|
|
.employee-contact span {
|
margin-right: 16px;
|
}
|
|
.employee-stats {
|
display: flex;
|
gap: 30px;
|
}
|
|
.stat-item {
|
text-align: center;
|
}
|
|
.stat-value {
|
font-size: 28px;
|
font-weight: bold;
|
color: #409eff;
|
margin-bottom: 4px;
|
}
|
|
.stat-label {
|
color: #909399;
|
font-size: 14px;
|
}
|
</style>
|