437 lines
13 KiB
Vue
437 lines
13 KiB
Vue
<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>
|