2025-05-13 15:53:29 +08:00

757 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="fullHeight">
<headers :showBack="true" :themeType="'white'">
<view class="headerName"> 考勤打卡 </view>
</headers>
<!-- 打卡 -->
<view v-if="!tabActive">
<view class="box personInfoBox">
<image v-if="fieldAcquisitionUrl" :src="url_config + 'image/' + fieldAcquisitionUrl" class="profile_photo"></image>
<image v-else src="/static/profile_photo.png" class="profile_photo"></image>
<view class="info">
<view class="personName">
{{ realName }}
</view>
<view class="groupName"> {{ enterpriseName }}&nbsp;&nbsp;{{ teamName }} </view>
</view>
</view>
<view class="box" :style="{ height: screenHeight - statusBarHeight - 44 - 105 - 15 - 30 - 50 + 'px' }">
<view class="top">
<view class="item">
<view class="type"> 进场 </view>
<view class="time" v-if="attendenceData.enterTime">
<uni-icons2 class="backImg" type="checkbox-filled" size="14" color="#5181F6"></uni-icons2>
{{ enterTime }} 已打卡
</view>
<view class="time" v-else> 未打卡 </view>
</view>
<view class="item">
<view class="type"> 出场 </view>
<view class="time" v-if="attendenceData.outTime">
<uni-icons2 class="backImg" type="checkbox-filled" size="14" color="#5181F6"></uni-icons2>
{{ outTime }}已打卡
</view>
<view class="time" v-else> 未打卡 </view>
</view>
</view>
<view class="bottom">
<view class="">
<view class="circleBox" :class="canClick ? '' : 'grey'" @click="checkInOpen">
<view class="name"> 考勤打卡 </view>
<view class="time">
{{ nowTime }}
</view>
</view>
<view class="address" v-if="canClick">
<uni-icons2 class="backImg" type="checkbox-filled" size="14" color="#707070"></uni-icons2>已进入考勤范围:{{
electricFence.addr
}}
</view>
<view class="address" v-else>
<uni-icons2 class="backImg" type="location-filled" size="14" color="#707070"></uni-icons2>未进入考勤区域
</view>
</view>
</view>
</view>
</view>
<!-- 统计 -->
<view v-else>
<view class="box flex-c" :style="{ height: screenHeight - statusBarHeight - 44 - 15 - 30 - 50 + 'px' }">
<view class="personInfoBox" style="padding-bottom: 24px">
<image v-if="fieldAcquisitionUrl" :src="url_config + 'image/' + fieldAcquisitionUrl" class="profile_photo"></image>
<image v-else src="/static/profile_photo.png" class="profile_photo"></image>
<view class="info">
<view class="personName">
{{ realName }}
</view>
<view class="groupName"> {{ enterpriseName }}&nbsp;&nbsp;{{ teamName }} </view>
</view>
</view>
<view class="calendarBox">
<scroll-view scroll-y="true" style="height: 100%">
<view class="calendarStatus">
<view class="item" v-for="item in clockOptions">
<view class="circle" :style="{ backgroundColor: item.color }"></view>
<text class="name">{{ item.name }}</text>
</view>
</view>
<view :class="{ calendar: true, hide: openIcon === 'down' }">
<uni-calendar
class="uni-calendar--hook"
:selected="selected"
:showMonth="false"
@change="dayChange"
@monthSwitch="monthChange"
/>
</view>
<!-- 展开 收起 -->
<view class="open" @click="handleOpen">
<view class="line"></view>
<uni-icons2 class="ic" :type="openIcon" size="20" color="#CCCCCC"></uni-icons2>
<view class="line"></view>
</view>
<view class="detail">
<view class="info">
当日班次:
{{ attendenceRule.ruleName || '固定上下班' }}
{{ attendenceRule.startTime || '9:00' }}-{{ attendenceRule.endTime || '18:00' }}
</view>
<view class="info" v-if="attendanceInfo.length">出勤统计:打卡{{ attendanceInfo.length }}次</view>
<u-time-line>
<u-time-line-item v-for="item in attendanceInfo" :key="item.createTime">
<template v-slot:content>
<view class="u-wrap">
<view class="left">
<view class="u-order-time"> {{ item.passType === 1 ? '进场' : '出场' }} {{ item.createTime.split(' ')[1] }} </view>
<view class="u-order-desc">
<image class="img" src="@/static/checkIn/position.png" />
{{ item.address }}
</view>
</view>
<view class="u-image">
<image
class="image"
:src="url_config + 'image/' + item.imageUrl"
@click="previewImage(url_config + 'image/' + item.imageUrl)"
></image>
</view>
</view>
</template>
</u-time-line-item>
</u-time-line>
<view class="info" v-if="attendanceInfo.length"
>统计截至{{ attendanceInfo.length && attendanceInfo[attendanceInfo.length - 1].createTime }}</view
>
<view class="noData" v-if="!attendanceInfo.length">
<image src="@/static/checkIn/nodata.png"></image>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
<u-tabbar :value="tabActive" @change="handleChangeTab" :list="tabList" activeColor="#4496fc" inactiveColor="#707070"></u-tabbar>
<!-- 确认打卡弹框 -->
<u-popup v-model="checkInShow" :round="10" mode="bottom" @close="checkInClose" closeable>
<view class="popup_content">
<text class="popup_title">打卡备注</text>
<view class="form_item flex">
<view class="name">请选择:</view>
<u-radio-group v-model="checkInform.type">
<u-radio :name="1"> 进场 </u-radio>
<u-radio :name="2"> 出场 </u-radio>
</u-radio-group>
</view>
<view class="form_item flex">
<view class="name">{{ phoneClockImageType ? '人脸识别' : '上传图片' }}</view>
<image
class="p_image"
v-if="checkInform.photoUrl"
:src="url_config + 'image/' + checkInform.photoUrl"
@click="uploadImage"
></image>
<image class="p_image" v-else src="@/static/checkIn/camera.png" @click="uploadImage"></image>
</view>
<u-button type="primary" @click="clockIn">打卡</u-button>
</view>
</u-popup>
</view>
</template>
<script>
import { handleAuthLocation } from "@/common/permissionTips"
import uniIcons from "@/components/uni-icons/uni-icons.vue"
export default {
components:{uniIcons},
data() {
return {
statusBarHeight:0,
screenHeight:667,
personDetail:{
fieldAcquisitionUrl:''
},
canClick:false,
projectDetail:{},
electricFence:{},
attendenceData:{
enterTime: null,
outTime: null
},
currentTime:'',
photoUrl:'',
timer:null,
realName:'',
phoneClockImageType:0,
faceScore:0,
timer: '', //定义一个定时器
nowTime: '',
}
},
computed:{
enterTime:function(){
if(this.attendenceData.enterTime){
var a = this.attendenceData.enterTime.split(' ')[1]
return a
}else{
return null
}
},
outTime:function(){
if(this.attendenceData.outTime){
var a = this.attendenceData.outTime.split(' ')[1]
return a
}else{
return null
}
}
},
created() {
this.getTime()
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer)
}
},
onLoad() {
this.statusBarHeight=uni.getStorageSync('systemInfo').statusBarHeight
this.screenHeight=uni.getStorageSync('systemInfo').screenHeight
this.projectDetail=JSON.parse(uni.getStorageSync('projectDetail'))
var userInfo=JSON.parse(uni.getStorageSync('userInfo'))
this.workerId = userInfo.workerId
this.realName=userInfo.realName
this.GetLOcation()
this.getAttendenceData()
this.getNowTime()
this.getAttendenceTypeFn()
},
onShow(){
if(this.phoneClockImageType==1&&this.photoUrl){
this.addPhoneAttendanceFn()
}
},
methods: {
//判断是否需要人脸识别
getAttendenceTypeFn(){
var that = this
this.sendRequest({
url: 'xmgl/projectConfig/getProjectConfigList',
data: {projectSn:this.projectDetail.projectSn},
method: "POST",
success(res){
if(res.result){
that.phoneClockImageType=res.result[0].phoneClockImageType
that.faceScore=res.result[0].faceScore
}
}
})
},
getTime() {
this.timer = setInterval(() => {
let timeDate = new Date()
let year = timeDate.getFullYear()
let mounth = timeDate.getMonth() + 1
let day = timeDate.getDate()
let hours = timeDate.getHours()
hours = hours >= 10 ? hours : '0' + hours
let minutes = timeDate.getMinutes()
minutes = minutes >= 10 ? minutes : '0' + minutes
let seconds = timeDate.getSeconds()
seconds = seconds >= 10 ? seconds : '0' + seconds
let week = timeDate.getDay()
let weekArr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
this.nowTime = ` ${hours}:${minutes}:${seconds}`
}, 1000)
},
//获取考勤信息
getAttendenceData(){
var that = this
this.sendRequest({
url: 'xmgl/workerAttendance/getPersonAttendanceTime',
data: {workerId:this.workerId},
method: "POST",
success(res){
console.log('getAttendenceData',res)
that.attendenceData=res.result
}
})
},
GetLOcation: function() {
var that = this;
uni.getLocation({
type: 'gcj02',
success: (res) => {
console.log('success',res)
var latitude = parseFloat(res.latitude);
var longitude = parseFloat(res.longitude);
that.isInArea(longitude,latitude)
},
fail(res) {
console.log('fail',res)
// uni.showModal({
// title:'提示',
// content:res.errMsg
// })
handleAuthLocation('需要获取当前位置用于考勤打卡请允许此App获取此设备的位置信息!')
}
});
},
// 校验坐标位置是否在考勤区域中
isInArea(longitude,latitude){
var that = this
this.sendRequest({
url: 'xmgl/electricFence/checkLocation',
data: {longitude:longitude,projectSn:this.projectDetail.projectSn,latitude:latitude,workerId:this.workerId},
method: "POST",
hideLoading:true,
success(res){
console.log('isInArea',res)
if(res.result.checkType==0){
console.log('不在考勤范围内')
that.canClick=false
}else{
console.log('在考勤范围内')
that.canClick=true
that.electricFence=res.result.electricFence
}
}
})
},
//手机打卡
addPhoneAttendanceFn(){
var that = this
var type = 1
if(this.attendenceData.enterTime){
type=2
}
this.sendRequest({
url: 'xmgl/workerAttendance/addPhoneAttendance',
data: {workerId:this.workerId,photoUrl:this.photoUrl,type:type},
method: "POST",
success(res){
console.log('addPhoneAttendanceFn',res)
that.getAttendenceData()
}
})
},
clockIn(){
if(!this.canClick){
return
}
if(this.phoneClockImageType){
uni.navigateTo({
url:'./faceRecognition?faceScore='+this.faceScore
})
// var that = this
// this.$chooseImage({
// count: 1, //默认9
// sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
// sourceType: ['camera'], //从相册选择
// success: function (res) {
// console.log(JSON.stringify(res.tempFilePaths));
// const tempFilePaths = res.tempFilePaths;
// uni.uploadFile({
// url:that.url_config+'upload/image', //post请求的地址
// filePath:tempFilePaths[0],
// name:'files',
// // formData: {
// // 'username': this.userInfo.username //formData是指除了图片以外额外加的字段
// // },
// success: (uploadFileRes) => {
// //这里要注意uploadFileRes.data是个String类型要转对象的话需要JSON.parse一下
// var obj = JSON.parse(uploadFileRes.data);
// console.log('obj',obj)
// that.photoUrl=obj.data[0].imageUrl
// that.addPhoneAttendanceFn()
// }
// })
// }
// });
}else{
this.addPhoneAttendanceFn()
}
},
}
}
</script>
<style scoped lang="scss">
.profile_photo {
width: 45px;
height: 45px;
border-radius: 50%;
margin-right: 10px;
}
.box {
box-shadow: 0 4px 24px 0px rgba(212, 220, 236, 0.69);
border-radius: 8px;
margin: 15px;
padding: 15px;
}
.personInfoBox {
display: flex;
align-items: center;
flex-direction: row;
.personName {
color: rgba(72, 141, 236, 1);
font-size: 17px;
}
.groupName {
color: rgba(42, 43, 91, 0.8);
font-size: 13px;
}
}
.top {
display: flex;
align-items: center;
flex-direction: row;
.item {
background-color: #f3f4f8;
border-radius: 4px;
padding: 8px 12px;
flex: 1;
font-size: 15px;
// width: calc(50% - 29px);
// display: inline-flex;
&:first-child {
margin-right: 10px;
}
.time {
font-size: 13px;
color: rgba(38, 45, 71, 0.7);
display: flex;
align-items: center;
margin-top: 3px;
flex-direction: row;
}
}
}
.backImg {
margin-right: 3px;
}
.bottom {
text-align: center;
display: flex;
align-items: center;
justify-content: center;
height: calc(100% - 60px);
.address {
font-size: 13px;
margin-top: 20px;
color: rgba(38, 45, 71, 0.7);
flex-direction: row;
display: flex;
align-items: center;
justify-content: center;
}
}
.circleBox {
width: 140px;
height: 140px;
border-radius: 50%;
background-image: linear-gradient(#0093f9, #066af8);
color: white;
display: inline-block;
box-shadow: 0 4px 24px 0px rgba(81, 129, 246, 0.5);
&.grey {
background-image: linear-gradient(#f1f1f1, #c3c3c3);
box-shadow: 0 4px 24px 0px rgba(195, 195, 195, 0.5);
}
.name {
padding-top: 45px;
font-size: 20px;
}
.time {
font-size: 15px;
}
}
.profile_photo {
width: 48px;
height: 48px;
border-radius: 8px;
margin-right: 10px;
}
.box {
box-shadow: 0 4px 24px 0px rgba(212, 220, 236, 0.69);
border-radius: 8px;
margin: 15px;
padding: 15px;
box-sizing: border-box;
}
.personInfoBox {
display: flex;
align-items: center;
flex-direction: row;
.personName {
// color: rgba(72, 141, 236, 1);
// font-size: 17px;
font-weight: bold;
font-size: 16px;
color: #000000;
}
.groupName {
color: rgba(42, 43, 91, 0.8);
font-size: 13px;
}
}
.top {
display: flex;
align-items: center;
flex-direction: row;
.item {
background-color: #ededed;
border-radius: 12px;
padding: 8px 12px;
flex: 1;
font-size: 15px;
font-family: PingFang SC;
// width: calc(50% - 29px);
// display: inline-flex;
&:first-child {
margin-right: 10px;
}
.type {
font-size: 16px;
color: #000000;
}
.time {
font-size: 13px;
color: #707070;
display: flex;
align-items: center;
margin-top: 3px;
flex-direction: row;
}
}
}
.backImg {
margin-right: 3px;
}
.bottom {
text-align: center;
display: flex;
align-items: center;
justify-content: center;
height: calc(100% - 60px);
.address {
font-size: 13px;
margin-top: 20px;
color: #707070;
flex-direction: row;
display: flex;
align-items: baseline;
justify-content: center;
}
}
.circleBox {
width: 140px;
height: 140px;
border-radius: 50%;
background-image: linear-gradient(#0093f9, #066af8);
color: white;
display: inline-block;
box-shadow: 0 4px 24px 0px rgba(81, 129, 246, 0.5);
&.grey {
background-image: linear-gradient(#f1f1f1, #c3c3c3);
box-shadow: 0 4px 24px 0px rgba(195, 195, 195, 0.5);
}
.name {
padding-top: 45px;
font-size: 20px;
}
.time {
font-size: 15px;
color: rgba(255, 255, 255, 0.7);
}
}
.popup_content {
padding: 16px;
.popup_title {
font-size: 16px;
color: #171717;
font-weight: bold;
display: flex;
justify-content: center;
}
}
.flex {
display: flex;
align-items: center;
}
.flex-c {
display: flex;
flex-direction: column;
}
.form_item {
font-size: 30rpx;
line-height: 40px;
margin: 8px 0;
/* border-bottom: 1px solid rgba(194, 194, 194, 0.2); */
.p_image {
width: 52px;
height: 52px;
}
}
.form_item .name {
margin-right: 6px;
width: 185rpx;
text-align: left;
white-space: nowrap;
&.large {
width: 300rpx;
}
&.large-2 {
width: 240rpx;
}
}
.calendarBox {
width: 100%;
height: calc(100% - 78px - -24px);
// height: 100%;
.calendarTitle {
// background-color: #fff;
font-size: 18px;
font-weight: bold;
color: #000000;
padding: 10px 0;
}
.calendarStatus {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-bottom: 12px;
.item {
display: flex;
align-items: center;
flex-shrink: 0;
.circle {
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
}
.text {
font-size: 12px;
color: #707070;
}
}
}
.calendar {
width: 100%;
overflow: hidden;
height: auto;
transition: all 0.3s;
&.hide {
height: 150px;
}
}
}
.open {
display: flex;
align-items: center;
.line {
flex: 1;
height: 1px;
background: #dbdbdb;
}
.ic {
flex-shrink: 0;
margin: 0 2px;
width: 20px;
height: 20px;
}
}
.detail {
padding: 20px 4px;
.info {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #707070;
line-height: 16px;
text-align: left;
margin-bottom: 16px;
}
/deep/.u-dot {
background: #707070;
}
/deep/.u-time-axis::before {
border-left: 1px solid #707070;
}
.u-wrap {
display: flex;
justify-content: space-between;
align-items: flex-start;
.left {
flex: 1;
}
.u-image {
width: 42px;
height: 42px;
margin-left: 8px;
.image {
width: 42px;
height: 42px;
border-radius: 8px;
}
}
}
.u-order-time {
font-weight: 500;
font-size: 16px;
color: #000000;
line-height: 16px;
margin-bottom: 10px;
}
.u-order-desc {
font-weight: 400;
font-size: 13px;
color: #707070;
line-height: 16px;
display: flex;
align-items: center;
.img {
width: 32rpx;
height: 16px;
margin-right: 10px;
flex-shrink: 0;
}
}
.noData {
width: 100%;
height: 200px;
display: flex;
justify-content: center;
}
}
/deep/.uni-calendar-item__weeks-box-item {
width: auto;
}
</style>