nxdt-uniapp/pages/attendanceManagement/index.vue

437 lines
13 KiB
Vue
Raw Normal View History

2025-01-16 17:36:46 +08:00
<template>
<view>
<Navbar title="考勤打卡" :showBack="false" />
<div class="content">
<ProSelect v-if="this.userType != '00'" @selectPro="selectPro" />
<u-row :gutter="20">
<u-col span="12">
<div class="top-cont">
<div class="name">{{ name }}</div>
<div class="calendar" @click="handleCalendar">
<u-icon name="calendar" color="#2979ff" size="21"></u-icon>
<span>{{ isShow ? '考勤统计' : '考勤打卡' }}</span>
</div>
</div>
</u-col>
</u-row>
<div v-if="isShow">
<u-row :gutter="20" justify="center">
<u-col span="6">
<div class="col-item">
<div>上班 {{ attendanceTime }}</div>
<div class="time" v-if="clockIn">
<u-icon name="checkmark-circle-fill" color="#2979ff" style="margin-right: 6px"></u-icon>
{{ clockIn }}
</div>
<div class="time" v-else>未打卡</div>
</div>
</u-col>
<u-col span="6">
<div class="col-item">
<div>下班 {{ arriveTime }}</div>
<div class="time" v-if="clockOut">
<u-icon name="checkmark-circle-fill" color="#2979ff" style="margin-right: 6px"></u-icon>
{{ clockOut }}
</div>
<div class="time" v-else>未打卡</div>
</div>
</u-col>
</u-row>
<div class="clock-in-item" :class="{ isInLocation: isInLocation }" @click="isConfirm">
<div v-if="isInLocation">考勤打卡</div>
<div v-else>不在打卡范围内</div>
<div class="now-time">{{ nowTime }}</div>
</div>
<div class="location">
<u-icon name="map"></u-icon>
{{ locationSuccess ? location : '获取位置失败, 请开启系统权限后再尝试' }}
</div>
</div>
<div v-else>
<ren-calendar ref="ren" :markDays="markDays" @onDayClick="onDayClick"></ren-calendar>
<div style="margin: 20px 0; display: flex">
上班打卡{{ clockIn }}
<u-text v-if="!clockIn && isInClockTime" type="error" text="未打卡"></u-text>
</div>
<div style="display: flex">
下班打卡{{ clockOut }}
<u-text v-if="!clockOut && isInClockTime" type="error" text="未打卡"></u-text>
</div>
</div>
</div>
<Tabbar />
</view>
</template>
<script>
import RenCalendar from '@/components/ren-calendar/ren-calendar.vue'
import ProSelect from 'pages/component/Proselect'
import { getAttendanceTime, attendancePunch } from '@/api/checkInAdmin'
export default {
components: {
RenCalendar,
ProSelect
},
data() {
return {
userId: uni.getStorageSync('userInfo').constructionId,
userType: uni.getStorageSync('userInfo').userType,
proId: '',
isShow: true,
name: uni.getStorageSync('userInfo').nickName,
// type: uni.getStorageSync('phone-platform') == 'ios' ? 'wgs84' : 'gcj02',
attendanceTime: '-', // 上班时间
arriveTime: '-', // 下班时间
// 上班卡时间戳限制 14:00:00前都是上班卡 获取14:00:00前的时间戳
clockInTimeLimit: new Date(new Date().toLocaleDateString()).getTime() + 14 * 60 * 60 * 1000,
// 当前时间的时间戳
nowTimeLimit: new Date().getTime(),
clockIn: '', //上班打卡时间
clockOut: '', //下班打卡时间
clockInStatus: '',
nowTime: '', //当前时间
location: '', //当前位置
isInLocation: false, //是否在打卡范围内
lat: '', //纬度
lon: '', //经度
// 打卡规定地址范围
clockInLocation: [],
clockInDistance: 3000, // 考勤距离
currentId: '',
curDate: '', //当前日期
today: '', //今天日期
isInClockTime: true, // 是否在打卡时间内
markDays: [], //标记日期
// 获取位置成功
locationSuccess: false
}
},
created() {
this.getNowTime()
this.getToday()
this.updateNowTime()
this.getLocation()
console.log('时间戳-上班', this.clockInTimeLimit)
console.log('时间戳-当前', this.nowTimeLimit)
},
methods: {
// 获取打卡信息
async getAttendanceInfo() {
const params = {
time: this.today,
userId: this.userId,
proId: this.proId
}
console.log('参数', params)
const res = await getAttendanceTime(params)
console.log('getAttendanceTime', res)
if (res.data) {
this.clockInLocation = res.data.addressList || '-'
this.attendanceTime = res.data.upperWorkTime || '-'
this.arriveTime = res.data.belowWorkTime
this.clockIn = res.data.goWorkTime || ''
this.clockOut = res.data.offWorkTime || ''
setTimeout(() => {
this.isInClockInLocation()
}, 200)
} else {
this.clockInLocation = []
this.isInLocation = false
}
},
// 选择工程
selectPro(pro) {
console.log('🚀 ~ selectPro ~ 选择工程:', pro)
this.proId = pro.proId
setTimeout(() => {
this.getAttendanceInfo()
}, 200)
},
// 获取当前时间
getNowTime() {
const date = new Date()
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
const second = String(date.getSeconds()).padStart(2, '0')
this.nowTime = `${hour}:${minute}:${second}`
},
// 获取今天日期
getToday() {
const date = new Date()
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
this.today = `${year}-${month}-${day}`
},
// 更新当前时间
updateNowTime() {
setInterval(() => {
this.getNowTime()
}, 1000)
},
// 获取当前位置
getLocation() {
uni.getLocation({
// type: this.type,
type: 'wgs84', // type: wgs84 返回 gps 坐标gcj02
isHighAccuracy: true,
altitude: true,
success: res => {
this.locationSuccess = true
console.log('getLocation', res)
this.lat = res.latitude
this.lon = res.longitude
// if (res.address) {
// this.location = res.address.province + res.address.city + res.address.district + res.address.street
// }
this.handleLocation()
},
fail: err => {
this.locationSuccess = false
console.log('getLocation', err, this.locationSuccess)
uni.showToast({
title: '获取位置失败',
icon: 'none'
})
}
})
},
// 处理位置
handleLocation() {
uni.request({
url: `https://api.map.baidu.com/reverse_geocoding/v3/?ak=PM43nB8eDNTBrXkQwGrTQFcmOni3Z9nO&output=json&coordtype=wgs84ll&location=${this.lat},${this.lon}`,
method: 'GET',
success: res => {
console.log('解析的地址', res)
this.location = res.data.result.formatted_address
}
})
},
// 计算 当前位置 是否在clockInLocation[] 中任意一项的打卡范围内 默认范围为 500m
isInClockInLocation() {
if (this.clockInLocation.length == 0) return
this.clockInLocation.forEach(item => {
let distance = this.getDistance(this.lat, this.lon, item.lat, item.lon)
console.log('🚀 ~ isInClockInLocation ~ distance:', distance)
if (this.type == 'gcj02') {
distance = distance - 700
} else {
distance = distance - 1000
}
console.log('🚀 ~ isInClockInLocation ~ distance-计算后:', distance)
if (distance <= this.clockInDistance) {
if (this.distance < 0) this.distance = 0
this.isInLocation = true
this.currentId = item.id
return
}
})
},
// 计算两点之间的距离
getDistance(lat1, lon1, lat2, lon2) {
const radLat1 = this.deg2rad(lat1)
const radLat2 = this.deg2rad(lat2)
const a = radLat1 - radLat2
const b = this.deg2rad(lon1) - this.deg2rad(lon2)
let s =
2 *
Math.asin(
Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))
)
s = s * 6378.137 // 地球半径(单位:公里)
s = Math.round(s * 10000) / 10 // 保留一位小数
return s
},
deg2rad(deg) {
return (deg * Math.PI) / 180
},
// 切换考勤打卡和考勤统计
handleCalendar() {
console.log('跳转到考勤统计页面')
this.isShow = !this.isShow
this.getToday()
this.getAttendanceInfo()
if (!this.isShow) {
setTimeout(() => {
this.markDays = []
let today = this.$refs.ren.getToday().date
this.curDate = today
this.markDays.push(today)
}, 300)
}
},
onDayClick(data) {
console.log('🚀 ~ onDayClick ~ data:', data)
if (new Date(data.date).getTime() > new Date().getTime()) {
this.isInClockTime = false
} else {
this.isInClockTime = true
}
this.curDate = data.date
this.today = data.date
this.getAttendanceInfo()
},
// 是否确认打下班卡
parseTime(timeStr) {
const [hours, minutes, seconds] = timeStr.split(':').map(Number)
const date = new Date()
date.setHours(hours, minutes, seconds, 0)
return date
},
isConfirm() {
if (!this.isInLocation) {
uni.showToast({
title: '不在打卡范围内',
icon: 'none'
})
return
}
if (this.clockIn) {
const now = this.parseTime(this.nowTime)
const clockIn = this.parseTime(this.clockIn)
const sixtyMinutesLater = new Date(clockIn.getTime() + 60 * 60000) // 考勤时间加上 60 分钟
if (now < sixtyMinutesLater) {
uni.showModal({
title: '提示',
content: '是否确认打下班卡?',
showCancel: true,
cancelText: '取消',
confirmText: '确认',
success: res => {
if (res.confirm) {
console.log('用户点击确认')
this.handleClockIn()
} else {
console.log('用户点击取消')
}
}
})
} else {
this.handleClockIn()
}
} else {
this.handleClockIn()
}
},
// 打卡
async handleClockIn() {
try {
let attendanceType = ''
if (this.nowTimeLimit < this.clockInTimeLimit && !this.clockIn) {
attendanceType = '1'
} else if (this.nowTimeLimit < this.clockInTimeLimit && this.clockIn) {
attendanceType = '2'
} else if (this.nowTimeLimit > this.clockInTimeLimit) {
attendanceType = '2'
}
console.log('打卡')
// 开启loading
uni.showLoading({
title: '打卡中...',
icon: 'loading', // loading icon有哪几种: 1. none 2. success 3. loading
mask: true // 是否显示透明蒙层,防止触摸穿透
})
const params = {
userId: this.userId,
proId: this.proId,
attendanceType,
time: this.nowTime,
punchLocation: this.currentId
}
console.log('🚀 ~ 打卡参数 ~ params:', params)
const res = await attendancePunch(params)
console.log('🚀 ~ handleClockIn ~ res:', res)
this.getAttendanceInfo()
uni.hideLoading()
uni.showToast({
title: '打卡成功',
icon: 'success',
duration: 1500
})
} catch (error) {
console.log('🚀 ~ handleClockIn ~ error:', error)
}
}
}
}
</script>
<style lang="scss">
.content {
padding: 0 20px;
white-space: pre-wrap;
word-wrap: break-word;
}
.top-cont {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
padding: 15px;
background-color: #f5f5f5;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
.name {
display: flex;
align-items: center;
font-size: 19px;
font-weight: 800;
color: #333;
}
.calendar {
font-size: 12px;
display: flex;
flex-direction: column;
align-items: center;
}
}
.col-item {
padding: 10px;
background-color: #f5f5f5;
border-radius: 10px;
font-size: 15px;
line-height: 1.8;
color: #333;
.time {
display: flex;
font-size: 12px;
color: #999;
}
}
.clock-in-item {
margin: 36% auto 30px;
width: 100px;
height: 100px;
padding: 10px;
background: linear-gradient(135deg, #a4c1fa, #adebe3);
border-radius: 50%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
font-size: 16px;
font-weight: 800;
line-height: 1.8;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.now-time {
font-size: 12px;
}
// 不在打卡范围内 背景置灰
&:not(.isInLocation) {
background: #909399;
font-size: 14px;
}
}
.location {
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
color: #333;
}
</style>