|
|
|
|
|
|
|
|
* @param {number} lng1 经度1 |
|
|
* @param {number} lng1 经度1 |
|
|
* @param {number} lat2 纬度2 |
|
|
* @param {number} lat2 纬度2 |
|
|
* @param {number} lng2 经度2 |
|
|
* @param {number} lng2 经度2 |
|
|
* @returns {number} 距离,单位为公里 |
|
|
|
|
|
|
|
|
* @returns {number} 距离,米 |
|
|
*/ |
|
|
*/ |
|
|
static calculateDistance(lat1, lng1, lat2, lng2) { |
|
|
static calculateDistance(lat1, lng1, lat2, lng2) { |
|
|
const R = 6371 // 地球半径 |
|
|
|
|
|
|
|
|
const R = 6371000 // 地球半径,单位:米 |
|
|
const dLat = (lat2 - lat1) * (Math.PI / 180) |
|
|
const dLat = (lat2 - lat1) * (Math.PI / 180) |
|
|
const dLng = (lng2 - lng1) * (Math.PI / 180) |
|
|
const dLng = (lng2 - lng1) * (Math.PI / 180) |
|
|
|
|
|
|
|
|
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + |
|
|
|
|
|
Math.cos(lat1 * (Math.PI / 180)) * |
|
|
|
|
|
Math.cos(lat2 * (Math.PI / 180)) * |
|
|
|
|
|
Math.sin(dLng / 2) * Math.sin(dLng / 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const a = |
|
|
|
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) + |
|
|
|
|
|
Math.cos(lat1 * (Math.PI / 180)) * |
|
|
|
|
|
Math.cos(lat2 * (Math.PI / 180)) * |
|
|
|
|
|
Math.sin(dLng / 2) * |
|
|
|
|
|
Math.sin(dLng / 2) |
|
|
|
|
|
|
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) |
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) |
|
|
return R * c |
|
|
|
|
|
|
|
|
return R * c // 返回单位:米 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
|
|
|
|
|
|
static isInAttendanceRange(userLocation, attendanceGroup) { |
|
|
static isInAttendanceRange(userLocation, attendanceGroup) { |
|
|
const { latitude, longitude } = userLocation |
|
|
const { latitude, longitude } = userLocation |
|
|
const { lat, lng, radius } = attendanceGroup |
|
|
const { lat, lng, radius } = attendanceGroup |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!latitude || !longitude || !lat || !lng) { |
|
|
if (!latitude || !longitude || !lat || !lng) { |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const distance = this.calculateDistance(latitude, longitude, lat, lng) |
|
|
const distance = this.calculateDistance(latitude, longitude, lat, lng) |
|
|
return distance <= radius |
|
|
return distance <= radius |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static compareTimeWithSchedule(scheduleTime) { |
|
|
static compareTimeWithSchedule(scheduleTime) { |
|
|
const now = new Date() |
|
|
const now = new Date() |
|
|
const [scheduleHour, scheduleMinute] = scheduleTime.split(':').map(Number) |
|
|
const [scheduleHour, scheduleMinute] = scheduleTime.split(':').map(Number) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const currentMinutes = now.getHours() * 60 + now.getMinutes() |
|
|
const currentMinutes = now.getHours() * 60 + now.getMinutes() |
|
|
const scheduleMinutes = scheduleHour * 60 + scheduleMinute |
|
|
const scheduleMinutes = scheduleHour * 60 + scheduleMinute |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Math.sign(currentMinutes - scheduleMinutes) |
|
|
return Math.sign(currentMinutes - scheduleMinutes) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[PUNCH_STATUS.EARLY_OUT]: '早退打卡', |
|
|
[PUNCH_STATUS.EARLY_OUT]: '早退打卡', |
|
|
[PUNCH_STATUS.UPDATE_OUT]: '更新打卡' |
|
|
[PUNCH_STATUS.UPDATE_OUT]: '更新打卡' |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return statusTexts[status] || '未打卡' |
|
|
return statusTexts[status] || '未打卡' |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
*/ |
|
|
static buildPunchData(params) { |
|
|
static buildPunchData(params) { |
|
|
const { type, userInfo, userLocation, remark } = params |
|
|
const { type, userInfo, userLocation, remark } = params |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const statusMap = { |
|
|
const statusMap = { |
|
|
morning: PUNCH_STATUS.CHECKED_IN, |
|
|
morning: PUNCH_STATUS.CHECKED_IN, |
|
|
evening: PUNCH_STATUS.CHECKED_OUT, |
|
|
evening: PUNCH_STATUS.CHECKED_OUT, |
|
|
morning_late: PUNCH_STATUS.LATE_IN, |
|
|
morning_late: PUNCH_STATUS.LATE_IN, |
|
|
evening_early: PUNCH_STATUS.EARLY_OUT |
|
|
evening_early: PUNCH_STATUS.EARLY_OUT |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isClockIn = type.includes('morning') |
|
|
const isClockIn = type.includes('morning') |
|
|
const currentTime = this.formatDateTime(new Date()) |
|
|
const currentTime = this.formatDateTime(new Date()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
userId: userInfo.userId, |
|
|
userId: userInfo.userId, |
|
|
userName: userInfo.nickName, |
|
|
userName: userInfo.nickName, |
|
|
|
|
|
|
|
|
clockInStatus: PUNCH_STATUS.NOT_CHECKED, |
|
|
clockInStatus: PUNCH_STATUS.NOT_CHECKED, |
|
|
clockOutStatus: PUNCH_STATUS.NOT_CHECKED |
|
|
clockOutStatus: PUNCH_STATUS.NOT_CHECKED |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
records.forEach(record => { |
|
|
|
|
|
if (record.checkInType === PUNCH_TYPE.CLOCK_IN) { |
|
|
|
|
|
attendanceStatus.clockInTime = record.checkInTime |
|
|
|
|
|
attendanceStatus.clockInStatus = parseInt(record.checkInStatus) |
|
|
|
|
|
} else if (record.checkInType === PUNCH_TYPE.CLOCK_OUT) { |
|
|
|
|
|
attendanceStatus.clockOutTime = record.checkInTime |
|
|
|
|
|
attendanceStatus.clockOutStatus = parseInt(record.checkInStatus) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
records.forEach((record) => { |
|
|
|
|
|
attendanceStatus.clockInTime = record.clockIn |
|
|
|
|
|
attendanceStatus.clockInStatus = parseInt(record.clockInStatus) |
|
|
|
|
|
attendanceStatus.clockOutTime = record.clockOut |
|
|
|
|
|
attendanceStatus.clockOutStatus = parseInt(record.clockOutStatus || 0) |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return attendanceStatus |
|
|
return attendanceStatus |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
*/ |
|
|
static async getAttendanceGroup(userId) { |
|
|
static async getAttendanceGroup(userId) { |
|
|
const response = await queryAttendanceGroupByUserId({ userId }) |
|
|
const response = await queryAttendanceGroupByUserId({ userId }) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (response.code !== 200) { |
|
|
if (response.code !== 200) { |
|
|
throw new Error(response.msg || '获取考勤组信息失败') |
|
|
throw new Error(response.msg || '获取考勤组信息失败') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.data) { |
|
|
if (!response.data) { |
|
|
throw new Error('未配置考勤组,请联系管理员') |
|
|
throw new Error('未配置考勤组,请联系管理员') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response.data |
|
|
return response.data |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
*/ |
|
|
static async getTodayAttendance(userId) { |
|
|
static async getTodayAttendance(userId) { |
|
|
const response = await getCurrentDayRecord({ userId }) |
|
|
const response = await getCurrentDayRecord({ userId }) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (response.code !== 200) { |
|
|
if (response.code !== 200) { |
|
|
throw new Error(response.msg || '获取考勤状态失败') |
|
|
throw new Error(response.msg || '获取考勤状态失败') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response.data || [] |
|
|
return response.data || [] |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* @returns {Promise<Object>} API响应 |
|
|
* @returns {Promise<Object>} API响应 |
|
|
*/ |
|
|
*/ |
|
|
static async submitPunch(punchData) { |
|
|
static async submitPunch(punchData) { |
|
|
const apiCall = punchData.checkInType === PUNCH_TYPE.CLOCK_IN ? checkIn : checkOut |
|
|
|
|
|
|
|
|
const apiCall = |
|
|
|
|
|
punchData.checkInType === PUNCH_TYPE.CLOCK_IN ? checkIn : checkOut |
|
|
const response = await apiCall(punchData) |
|
|
const response = await apiCall(punchData) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (response.code !== 200) { |
|
|
if (response.code !== 200) { |
|
|
throw new Error(response.msg || '打卡失败') |
|
|
throw new Error(response.msg || '打卡失败') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response |
|
|
return response |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |