| | |
| | | <el-card class="employee-info-card"> |
| | | <div class="employee-header"> |
| | | <div class="employee-basic"> |
| | | <el-avatar :size="60" :src="employeeInfo.avatar" class="employee-avatar"> |
| | | <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-department"> |
| | | {{ employeeInfo.department }} · {{ employeeInfo.position }} |
| | | </p> |
| | | <p class="employee-contact"> |
| | | <span>工号: {{ employeeInfo.employeeId }}</span> |
| | | <span>电话: {{ employeeInfo.phone }}</span> |
| | |
| | | <!-- 选项卡 --> |
| | | <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="calendar"> |
| | | <attendance-calendar |
| | | :attendance-data="attendanceData" |
| | | :business-trip-data="businessTripData" |
| | | /> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="统计报表" name="report"> |
| | | <personal-attendance-report |
| | | :stats="employeeStats" |
| | |
| | | </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' |
| | | 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', |
| | | name: "AttendanceDetail", |
| | | components: { |
| | | AttendanceCalendar, |
| | | PersonalAttendanceTable, |
| | |
| | | }, |
| | | data() { |
| | | return { |
| | | activeTab: 'calendar', |
| | | activeTab: "calendar", |
| | | loading: false, |
| | | employeeInfo: { |
| | | name: '张三', |
| | | department: 'OPO项目部', |
| | | position: '项目经理', |
| | | employeeId: 'OPO001', |
| | | phone: '138-1234-5678', |
| | | avatar: '' |
| | | name: "", |
| | | department: "", |
| | | position: "", |
| | | employeeId: "", |
| | | phone: "", |
| | | avatar: "" |
| | | }, |
| | | employeeStats: { |
| | | attendanceRate: 0, |
| | |
| | | }, |
| | | attendanceData: [], |
| | | businessTripData: [] |
| | | } |
| | | }; |
| | | }, |
| | | created() { |
| | | this.loadMockData() |
| | | |
| | | this.getEmployeeInfo() |
| | | this.loadAttendanceData() |
| | | this.getEmployeeInfo(); |
| | | this.loadAttendanceData(); |
| | | }, |
| | | methods: { |
| | | getEmployeeInfo() { |
| | | const { employeeId, employeeName } = this.$route.query |
| | | 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) |
| | | name: employeeName || "张三", |
| | | department: "OPO项目部", |
| | | position: "项目经理", |
| | | employeeId: employeeId || "OPO001", |
| | | phone: "138****1234", |
| | | avatar: "" |
| | | }; |
| | | }, |
| | | |
| | | 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 |
| | | this.loading = true; |
| | | try { |
| | | await new Promise(resolve => setTimeout(resolve, 500)) |
| | | await new Promise(resolve => setTimeout(resolve, 500)); |
| | | |
| | | // 生成个人考勤模拟数据 |
| | | this.attendanceData = this.generatePersonalAttendanceData() |
| | | this.businessTripData = this.generatePersonalBusinessTripData() |
| | | this.calculateStats() |
| | | this.attendanceData = this.generatePersonalAttendanceData(); |
| | | this.businessTripData = this.generatePersonalBusinessTripData(); |
| | | this.calculateStats(); |
| | | } catch (error) { |
| | | console.error('加载数据失败:', error) |
| | | console.error("加载数据失败:", error); |
| | | } finally { |
| | | this.loading = false |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | |
| | | generatePersonalAttendanceData() { |
| | | const data = [] |
| | | const currentMonth = 12 // 12月 |
| | | const data = []; |
| | | const currentMonth = 12; // 12月 |
| | | |
| | | for (let day = 1; day <= 31; day++) { |
| | | if (Math.random() > 0.2) { // 80%的出勤率 |
| | | 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 ? '正常' : '迟到', |
| | | 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 |
| | | return data; |
| | | }, |
| | | |
| | | generatePersonalBusinessTripData() { |
| | | return [ |
| | | { |
| | | id: 1, |
| | | tripNumber: 'BT202412001', |
| | | startCity: '北京', |
| | | endCity: '上海', |
| | | startDate: '2024-12-05', |
| | | endDate: '2024-12-08', |
| | | tripNumber: "BT202412001", |
| | | startCity: "北京", |
| | | endCity: "上海", |
| | | startDate: "2024-12-05", |
| | | endDate: "2024-12-08", |
| | | distance: 1200, |
| | | purpose: '客户会议', |
| | | status: '已完成' |
| | | purpose: "客户会议", |
| | | status: "已完成" |
| | | }, |
| | | { |
| | | id: 2, |
| | | tripNumber: 'BT202412002', |
| | | startCity: '北京', |
| | | endCity: '广州', |
| | | startDate: '2024-12-15', |
| | | endDate: '2024-12-18', |
| | | tripNumber: "BT202412002", |
| | | startCity: "北京", |
| | | endCity: "广州", |
| | | startDate: "2024-12-15", |
| | | endDate: "2024-12-18", |
| | | distance: 1900, |
| | | purpose: '项目调研', |
| | | status: '已完成' |
| | | purpose: "项目调研", |
| | | status: "已完成" |
| | | } |
| | | ] |
| | | ]; |
| | | }, |
| | | |
| | | calculateStats() { |
| | | const totalDays = 31; |
| | | const attendanceDays = this.attendanceData.length; |
| | | |
| | | this.employeeStats = { |
| | | attendanceRate: Math.round((attendanceDays / totalDays) * 100), |
| | | workHours: this.attendanceData |
| | | .reduce((sum, item) => sum + parseFloat(item.workHours), 0) |
| | | .toFixed(1), |
| | | businessTripDays: this.businessTripData.reduce((sum, item) => { |
| | | const start = new Date(item.startDate); |
| | | const end = new Date(item.endDate); |
| | | return sum + Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1; |
| | | }, 0), |
| | | lateTimes: this.attendanceData.filter(item => item.status === "迟到") |
| | | .length, |
| | | leaveEarlyTimes: this.attendanceData.filter( |
| | | item => item.status === "早退" |
| | | ).length |
| | | }; |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |