uniapp+高德地圖實現打卡簽到、打卡日歷

一、注冊高德地圖。

? ? ? ?應用管理創建應用,分別添加Andriod平臺、Web服務、Web端、微信小程序四種類型的key。

二、考勤規則

打卡地點選擇位置代碼:

<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watchEffect } from "vue";
import AMapLoader from "@amap/amap-jsapi-loader";const emit = defineEmits(["submitMarker"]);const props = defineProps({markForm: {type: Object,default: null}
});const fieldNames = { label: "name", value: "id" };
const loading = ref<boolean>(false);
const searchKey = ref<any>();
const options = ref<any>([]);
const autoComplete = ref<any>();
// 標記點
const marker = ref<any>();
// 位置信息
const form = reactive({lng: "",lat: "",address: "",//詳細地址simpleAddress: "",//地址簡稱//地區編碼adcode: "",addressId: ""
});
const geoCoder = ref<any>();
const aMap = ref<any>();const map = ref<any>();watchEffect(() => {if (props.markForm && map.value) {form.lng = props.markForm.attendanceLongitude;form.lat = props.markForm.attendanceLatitude;form.address = props.markForm.attendanceAddress;form.simpleAddress = props.markForm.simpleAddress;form.addressId = props.markForm.attendanceAddressId;// 清除點removeMarker();// 標記點setMapMarker();}
});onMounted(() => {window._AMapSecurityConfig = {securityJsCode: "3a09232a7b75996c571a2a233211"};AMapLoader.load({key: "6a352ddjnewewb2814bebf80091wewec38e9", // 申請好的Web端開發者Key,首次調用 load 時必填version: "2.0", // 指定要加載的 JSAPI 的版本,缺省時默認為 1.4.15plugins: ["AMap.Scale", "AMap.ToolBar", "AMap.Geolocation", "AMap.PlaceSearch", "AMap.Geocoder", "AMap.AutoComplete"] // 需要使用的的插件列表,如比例尺'AMap.Scale'等}).then((AMap) => {aMap.value = AMap;map.value = new AMap.Map("container", {// 設置地圖容器idviewMode: "3D", // 是否為3D地圖模式zoom: 11, // 初始化地圖級別center: [116.397428, 39.90923] // 初始化地圖中心點位置});geoCoder.value = new AMap.Geocoder({city: "010", //城市設為北京,默認:“全國”radius: 1000 //范圍,默認:500});// 搜索提示插件autoComplete.value = new AMap.AutoComplete({ city: "全國" });}).catch((e) => {console.log(e);});
});// 標記點
function setMapMarker() {// 自動適應顯示想顯示的范圍區域map.value.setFitView();marker.value = new aMap.value.Marker({map: map.value,position: [form.lng, form.lat]});map.value.setFitView();map.value.add(marker.value);
}// 逆解析地址
function toGeoCoder() {let lnglat = [form.lng, form.lat];geoCoder.value.getAddress(lnglat, (status, result) => {if (status === "complete" && result.regeocode) {form.address = result.regeocode.formattedAddress;}});
}// 清除點
function removeMarker() {if (marker.value) {map.value.remove(marker.value);}
}// 搜索
function handleChange(value) {if (value) {loading.value = true;setTimeout(() => {loading.value = false;autoComplete.value.search(value, (status, result) => {options.value = result.tips;});}, 200);} else {options.value = [];}
}function currentSelect(val, option) {if (!val) {return;}form.lng = option.location.lng;form.lat = option.location.lat;form.address = option.district + option.name;form.simpleAddress = option.name;form.adcode = option.adcode;form.addressId = option.id;// 清除點removeMarker();// 標記點setMapMarker();emit("submitMarker", form);
}onUnmounted(() => {map.value?.destroy();
});
</script><template><a-selectv-model:value="searchKey":options="options":filter-option="false"label-in-valueshow-search@search="handleChange"placeholder="請輸入關鍵詞":fieldNames="fieldNames"@change="currentSelect"></a-select><div v-if="form.simpleAddress" class="address">已選位置:<Icon icon="ant-design:environment-outlined"></Icon>{{ form.simpleAddress }}</div><div id="container"></div>
</template><style scoped>
#container {width: 100%;height: 500px;margin-top: 10px;
}.address {margin: 10px 0;color: #FFA500;display: flex;align-items: center
}
</style>

安裝高德地圖Web端(JS API):

npm install @amap/amap-jsapi-loader
securityJsCode:使用自己的安全密鑰。key換為自己的。

三、uniapp 小程序

? ? ? ?(1)獲取打卡規則:上下班打卡時間、當前位置標記、考勤打卡范圍,加載打卡記錄,打卡距離計算是否外勤打卡,上班打卡還是下班打卡,上班是正常打卡或遲到打卡,下班是正常打卡或早退打卡等計算。打卡提交和更新打卡。節假日、工作日計算是否需要打卡。

? ? ? ? 非工作日也允許打卡,不做遲到和早退標記,時間自由,不做缺卡標記。

? ? ? ? (2)微信小程序端申請開通獲取位置接口。

????????配置文件勾選位置接口,填寫接口申請原因。

? ? ? ? 確保配置文件中包含以下配置。

? ? ? ? (3) 微信小程序打卡頁面uni.getSetting獲取定位權限,沒有授權會彈窗授權。

? ? ? ? (4)打卡

?

? ? ? ? ? 上述頁面代碼如下:?

<template><view class="container"><StatusBar :offset="10" /><view class="user-info"><!-- 用戶信息卡片 --><view class="user-card"><view class="avatar">{{userInfo.realname}}</view><view class="user-info"><view class="name">{{userInfo.realname}}</view><view class="desc">{{currentDepartName}}</view></view><view class="statistics" @click="viewCalendar"><view class="flex align-center"><image src="/static/statistics.png" class='statistics-image' mode='aspectFit'></image><text class="statistics-text">打卡統計</text></view></view></view><!-- 打卡信息 --><view class="attendance-card"><view class="attendance-row"><view class="attendance-item"><view class="title">上班{{attendanceRule.startWorkTime || ''}}</view><view class="text"><text class="tag" v-if="startWorkRecord && startWorkRecord.inoutsideType ==2">外勤</text></view><view class="status"><view class="checked" v-if="startWorkRecord && startWorkRecord.attendanceTime"><image src="/static/checked.png" class='recommend-image' mode='aspectFit'></image><view class="margin-lf">{{startWorkRecord.attendanceTime.substring(11,16) || ''}}已打卡</view></view><text class="not-checked" v-else>未打卡</text><u-tag text="缺卡" type="warning" shape="circle" size="mini"v-if="attendanceInfo && attendanceInfo.isStartMiss"></u-tag></view></view><view class="attendance-item"><view class="title">下班{{attendanceRule.endWorkTime || ''}}</view><view class="text"><text class="tag" v-if="endWorkRecord && endWorkRecord.inoutsideType ==2">外勤</text></view><view class="status"><view class="checked" v-if="endWorkRecord && endWorkRecord.attendanceTime"><image src="/static/checked.png" class='recommend-image' mode='aspectFit'></image><view class="margin-lf">{{endWorkRecord.attendanceTime.substring(11,16) || ''}}已打卡</view></view><view class="not-checked" v-else>未打卡</view><u-tag text="缺卡" type="warning" shape="circle" size="mini"v-if="attendanceInfo && attendanceInfo.isEndMiss"></u-tag></view></view></view><view class="margin-top-sm" v-if="!isNeedAttendance"><u-alert description="今日休息" type="primary" show-icon></u-alert></view></view></view><view><map-positioning-punch :clock-in-area="clockInArea" :refresh-timeout="refreshTimeout"@clockInClick="clockIn" :is-report="true" @change="locationChange" v-if="clockInArea.length > 0"></map-positioning-punch><u-modal :show="showConfirm" @confirm="saveAttendance" title="提示" @cancel="showConfirm=false" ref="uModal":asyncClose="true" :showCancelButton="true" content="確定要早退打卡嗎?"></u-modal></view></view>
</template><script>import {apiGetAttendanceRule,apiSaveAttendance,apiIsLeaveEarly,apiGetCurrentDept,apiListTodayAttendance} from "@/common/http.api.js"import {mapState,mapActions} from 'vuex'export default {data() {return {attendanceTypeInfo: null,showConfirm: false,// 打卡區域設置clockInArea: [],// 刷新打卡區域頻率refreshTimeout: 15000,params: {},attendanceRule: {},currentDepartName: "",attendanceInfo: null,startWorkRecord: null,endWorkRecord: null,remark: "",showRemark: false,isNeedAttendance: true}},computed: {...mapState(['loginState', 'userInfo']),},async onShow() {const res = await apiGetAttendanceRule()this.attendanceRule = resthis.clockInArea.push({longitude: res.attendanceLongitude,latitude: res.attendanceLatitude,distance: res.allowCheckinRange,})if (!!this.loginState) {const data = await apiGetCurrentDept()this.currentDepartName = data.orgCodeTxt;// #ifdef MP-WEIXINuni.getSetting({success(res) {if (!res.authSetting['scope.userLocation']) {uni.authorize({scope: 'scope.userLocation',success() {},fail() {console.log('用戶未授權');}});}}});// #endif//獲取今天的打卡數據this.listTodayAttendance();}},methods: {//獲取今天的打卡數據async listTodayAttendance() {this.attendanceInfo = await apiListTodayAttendance()if (this.attendanceInfo) {this.startWorkRecord = this.attendanceInfo.start;this.endWorkRecord = this.attendanceInfo.end;this.isNeedAttendance = this.attendanceInfo.isNeedAttendance}},//提交打卡數據async saveAttendance() {this.showConfirm = falsethis.loading = truetry {await apiSaveAttendance(this.params)uni.showToast({icon: 'success',title: '打卡成功'})//獲取今天的打卡數據this.listTodayAttendance();} finally {this.loading = false}},// 位置變化locationChange({location,areaLocation,distance}) {},// 打卡回調事件// location 當前位置,attendanceTypeInfo 考勤信息async clockIn({location,attendanceTypeInfo,addressName}) {this.attendanceTypeInfo = attendanceTypeInfothis.params = {attendanceAddress: addressName,attendanceLongitude: location.longitude,attendanceLatitude: location.latitude,inoutsideType: attendanceTypeInfo.inoutsideType.code,attendanceType: attendanceTypeInfo.attendanceType.code,workType: attendanceTypeInfo.workType.code}if (attendanceTypeInfo.workType.code == 2) { //下班卡const data = await apiIsLeaveEarly() //判斷是否早退打卡if (data) {this.showConfirm = true} else {await this.saveAttendance()}} else {await this.saveAttendance()}},viewCalendar() {uni.navigateTo({url: '/subpages/attendancecalendar/attendancecalendar'})}}}
</script><style scoped>.container {background: #f7fafd;min-height: 100vh;}.user-info {padding: 20rpx;}.user-card {position: relative;display: flex;align-items: center;background: #fff;border-radius: 20rpx;padding: 30rpx 20rpx;margin-bottom: 30rpx;}.avatar {width: 80rpx;height: 80rpx;background: #4a90e2;color: #fff;border-radius: 20rpx;display: flex;align-items: center;justify-content: center;font-size: 32rpx;font-weight: bold;margin-right: 20rpx;}.user-info .name {font-size: 32rpx;font-weight: bold;}.user-info .desc {font-size: 24rpx;color: #888;}.attendance-card {background: #fff;border-radius: 20rpx;margin-bottom: 10rpx;}.attendance-row {display: flex;justify-content: space-between;}.attendance-item {position: relative;width: 49%;background-color: #EBEBEB;padding: 30rpx 20rpx;border-radius: 20rpx;}.title {font-size: 32rpx;margin-bottom: 10rpx;display: flex;align-items: center;justify-content: space-between;}.tag {background: #4ec6a4;color: #fff;font-size: 24rpx;border-radius: 8rpx;padding: 5rpx 10rpx;}.status {display: flex;align-items: center;justify-content: space-between;font-size: 26rpx;}.checked {display: flex;align-items: center;color: #4a90e2;margin-right: 10rpx;}.not-checked {color: #747A7B;}.recommend-image {width: 30rpx;height: 30rpx;}.margin-lf {margin-left: 10rpx;}.text {position: absolute;top: 0;right: 0;z-index: 9;color: white;font-size: 10px;padding: 5px;}.statistics {position: absolute;top: 0;right: 10px;z-index: 9;padding: 10px;}.statistics-image {width: 40rpx;height: 40rpx;}.statistics-text {color: black;font-size: 28rpx;margin-left: 10rpx;}
</style>

? ? ? ? ?(5)打卡日歷

????????根據考勤規則標記需要打卡的星期,是否自動過濾節假日。工作日缺卡紅色標記,上下班均不缺卡做藍色標記。選擇日期后統計打卡次數,獲取打卡時間和位置,外勤打卡標記。

?

? ? ? ? ? 上述頁面代碼如下:

<template><view class="calendar"><StatusBar :offset="10" /><ren-calendar ref='ren' :markDays='markDays' :markBlueDays="markBlueDays" :headerBar='true'@onDayClick='onDayClick' @onMonthClick="onMonthClick"></ren-calendar><view class="attendance-container"><view class="shift-info"><view>當日班次:<text>固定上下班 行政班{{attendanceRule.startWorkTime || ''}}-{{attendanceRule.endWorkTime || ''}}</text></view><view>出勤統計:打卡{{checkinCount || 0}}次</view></view><view class="margin-top-sm margin-bottom-sm" v-if="currentAttendance && !currentAttendance.isWorkDay"><u-alert description="休息日" type="primary" show-icon></u-alert></view><view class="timeline"><view class="timeline-item" v-for="(item, idx) in records" :key="idx"><view class="dot"></view><view><view class="time-row"><text class="title">{{ item.workType == 1?'上班':'下班' }}</text><text v-if="item.note" class="margin-right-sm">{{ item.note || '' }}</text><u-tag text="缺卡" plain size="mini" type="warning"v-if="item.workType == 1 && item.isStartMiss"></u-tag><u-tag text="缺卡" plain size="mini" type="warning"v-if="item.workType == 2 && item.isEndMiss"></u-tag><text class="time"v-if="item.attendanceTime">{{ item.attendanceTime.substring(11,16) || '' }}</text><text class="tag" v-if="item.inoutsideType ==2">外勤</text></view><view class="location" v-if="item.attendanceAddress"><image src="/static/position.png" class="pos-image"></text>{{ item.attendanceAddress }}</view></view></view></view></view></view>
</template><script>import RenCalendar from "@/subpages/components/ren-calendar/ren-calendar.vue"import {apiGetAttendanceCalendar,apiGetAttendanceRule} from "@/common/http.api.js"export default {components: {RenCalendar},data() {return {attendanceRule: [],records: [],curDate: '',curMonth: '',markDays: [], //標記為紅色的點markBlueDays: [], //標記為藍色的點attendanceCalendar: [],checkinCount: 0, //打卡次數 currentAttendance: null}},async onReady() {let today = this.$refs.ren.getToday().date;this.curDate = today;this.curMonth = today.substring(0, 7)//獲取標記點this.getMarkPoints();this.getAttendanceRule();},methods: {async getAttendanceRule() {this.attendanceRule = await apiGetAttendanceRule()},async getMarkPoints() {this.markDays = []this.markBlueDays = []this.attendanceCalendar = await apiGetAttendanceCalendar(this.curMonth)if (this.attendanceCalendar && this.attendanceCalendar.missList?.length > 0) {this.markDays = [...this.attendanceCalendar.missList];}if (this.attendanceCalendar && this.attendanceCalendar.noMissList?.length > 0) {this.markBlueDays = [...this.attendanceCalendar.noMissList];}this.getAttendanceData();},onDayClick(data) {this.curDate = data.date;this.getAttendanceData()},onMonthClick(data) {this.curMonth = data;this.getMarkPoints();},//獲取打卡日期數據getAttendanceData() {this.records = []this.checkinCount = 0;let data;if (this.attendanceCalendar && this.attendanceCalendar.attendanceList?.length > 0) {data = this.attendanceCalendar.attendanceList.filter(val => val.date == this.curDate)[0]this.currentAttendance = data}if (data?.start) {this.checkinCount++;this.records.push(data.start)} else {this.records.push({workType: 1,note: "未打卡",attendanceTime: '',attendanceType: '',attendanceAddress: '',isStartMiss: data?.isStartMiss})}if (data?.end) {this.checkinCount++;this.records.push(data.end)} else {this.records.push({workType: 2,note: "未打卡",attendanceTime: '',attendanceType: '',attendanceAddress: '',isEndMiss: data?.isEndMiss})}}}}
</script><style scoped>.calendar {height: 100vh;background-color: #FFF;}.attendance-container {padding: 32rpx 40rpx;border-radius: 24rpx;}.shift-info {margin-bottom: 40rpx;color: #666;font-size: 30rpx;line-height: 50rpx;}.bold {font-weight: bold;color: #222;}.timeline {border-left: 4rpx solid #e0e0e0;margin-left: 16rpx;padding-left: 36rpx;}.timeline-item {position: relative;margin-bottom: 54rpx;}.timeline-item:last-child {margin-bottom: 0;}.dot {position: absolute;left: -47rpx;top: 16rpx;width: 20rpx;height: 20rpx;background: #AEAEAE;border: 4rpx solid #b3b3b3;border-radius: 50%;}.content {margin-left: 0;}.time-row {display: flex;align-items: center;margin-bottom: 20rpx;}.title {font-size: 32rpx;margin-right: 12rpx;}.time {font-size: 32rpx;color: #222;margin-right: 12rpx;}.tag {background: #e6f7ff;color: #1890ff;border-radius: 8rpx;padding: 4rpx 16rpx;font-size: 24rpx;margin-left: 8rpx;}.location {color: #666;font-size: 28rpx;margin-bottom: 4rpx;display: flex;align-items: center;}.icon {margin-right: 8rpx;}.type {color: #888;font-size: 26rpx;}.pos-image {width: 40rpx;height: 40rpx;margin-right: 10rpx;}
</style>

????????后端接口代碼:

package com.ynfy.buss.attendance.attendance.service.impl;import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ynfy.buss.attendance.attendance.entity.Attendance;
import com.ynfy.buss.attendance.attendance.entity.dto.AttendanceDTO;
import com.ynfy.buss.attendance.attendance.entity.vo.AttendanceCalendarVO;
import com.ynfy.buss.attendance.attendance.entity.vo.AttendanceVO;
import com.ynfy.buss.attendance.attendance.entity.vo.TodayAttendanceVO;
import com.ynfy.buss.attendance.attendance.enums.AttendanceType;
import com.ynfy.buss.attendance.attendance.enums.InoutsideType;
import com.ynfy.buss.attendance.attendance.enums.WorkType;
import com.ynfy.buss.attendance.attendance.mapper.AttendanceMapper;
import com.ynfy.buss.attendance.attendance.service.IAttendanceService;
import com.ynfy.buss.attendance.attendancerule.entity.AttendanceRule;
import com.ynfy.buss.attendance.attendancerule.service.IAttendanceRuleService;
import com.ynfy.buss.attendance.calendarholiday.entity.CalendarHoliday;
import com.ynfy.buss.attendance.calendarholiday.service.ICalendarHolidayService;
import com.ynfy.common.utils.GPSUtil;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;/*** @Description: 考勤打卡* @Author: jeecg-boot* @Date: 2025-07-16* @Version: V1.0*/
@Service
public class AttendanceServiceImpl extends ServiceImpl<AttendanceMapper, Attendance> implements IAttendanceService {@Autowiredprivate IAttendanceRuleService attendanceRuleService;@Autowiredprivate AttendanceMapper attendanceMapper;@Autowiredprivate ICalendarHolidayService calendarHolidayService;/*** 保存** @return*/@Overridepublic synchronized void saveAttendance(AttendanceDTO dto, String userId) {if (Objects.isNull(dto.getInoutsideType()) || Objects.isNull(dto.getAttendanceType()) || Objects.isNull(dto.getWorkType())) {throw new JeecgBootException("參數異常");}//獲取考勤規則AttendanceRule attendanceRule = attendanceRuleService.getAttendanceRule();if (Objects.isNull(attendanceRule)) {throw new JeecgBootException("未找到考勤規則");}if (Objects.isNull(attendanceRule.getAllowOutside()) || !attendanceRule.getAllowOutside()) {throw new JeecgBootException("管理員未開啟外勤打卡權限,請聯系管理員。");}//獲取當天打卡數據List<Attendance> attendanceList = listAttendanceRecord(DateUtil.formatDate(new Date()), userId, dto.getWorkType());if (!CollectionUtils.isEmpty(attendanceList)) {if (CommonConstant.ATTENDANCE_WORK_START.equals(dto.getWorkType())) {throw new JeecgBootException("上班打卡以最早打卡為準");}attendanceList.forEach(val -> {BeanUtils.copyProperties(dto, val);val.setAttendanceTime(new Date());updateById(val);});} else {Attendance attendance = new Attendance();BeanUtils.copyProperties(dto, attendance);attendance.setUserId(userId);attendance.setAttendanceTime(new Date());save(attendance);}}@Overridepublic AttendanceVO getAttendanceType(double lng, double lat, String userId) {AttendanceVO attendanceVO = new AttendanceVO();//獲取考勤規則AttendanceRule attendanceRule = attendanceRuleService.getAttendanceRule();if (isLegwork(attendanceRule, lng, lat)) {attendanceVO.setInoutsideType(InoutsideType.toJson(InoutsideType.OUTSIDE));} else {attendanceVO.setInoutsideType(InoutsideType.toJson(InoutsideType.NORMAL));}if (StringUtils.isNotBlank(userId)) {generateAttendanceType(attendanceRule, userId, attendanceVO);}return attendanceVO;}/*** 獲取最大遲到打卡時間** @param attendanceRule* @return*/public Date getMaxLateTime(AttendanceRule attendanceRule) {//開始打卡時間String startTime = DateUtil.formatDate(new Date()) + " " + attendanceRule.getStartWorkTime() + ":00";//遲到打卡最大時間attendanceRule.setAllowLateTime(!Objects.isNull(attendanceRule.getAllowLateTime()) ? attendanceRule.getAllowLateTime() : 0);return DateUtil.offsetMinute(DateUtil.parseDateTime(startTime), attendanceRule.getAllowLateTime());}public void generateAttendanceType(AttendanceRule attendanceRule, String userId, AttendanceVO attendanceVO) {AttendanceType attendanceType = null;Date maxLateTime = getMaxLateTime(attendanceRule);if (calendarHolidayService.isWorkDay(attendanceRule, DateUtil.formatDate(new Date()))) { //如果是工作日//獲取當天打卡數據List<Attendance> attendanceList = listAttendanceRecord(DateUtil.formatDate(new Date()), userId, null);//沒有簽到數據,并且沒有超過最大允許遲到時間,則為上班打卡if (CollectionUtils.isEmpty(attendanceList) && DateUtil.compare(maxLateTime, new Date()) >= 0) {if (isLatework(attendanceRule)) {//遲到打卡attendanceType = AttendanceType.LATE;} else { //正常打卡attendanceType = AttendanceType.NORMAL;}attendanceVO.setWorkType(WorkType.toJson(WorkType.START_WORD));} else {//下班打卡if (isLeaveEarly()) {//早退打卡attendanceType = AttendanceType.EARLY;} else {attendanceType = AttendanceType.NORMAL;}attendanceVO.setWorkType(WorkType.toJson(WorkType.END_WORD));}attendanceVO.setAttendanceType(AttendanceType.toJson(attendanceType));attendanceVO.setIsWorkDay(true);} else { //不是工作日attendanceVO.setAttendanceType(AttendanceType.toJson(AttendanceType.NORMAL));List<Attendance> attendanceList = listAttendanceRecord(DateUtil.formatDate(new Date()), userId, null);if (!CollectionUtils.isEmpty(attendanceList)) {Attendance start = attendanceList.stream().filter(attendance -> CommonConstant.ATTENDANCE_WORK_START.equals(attendance.getWorkType())).findFirst().orElse(null);if (!Objects.isNull(start)) { //已經打過上班卡attendanceVO.setWorkType(WorkType.toJson(WorkType.END_WORD));} else {attendanceVO.setWorkType(WorkType.toJson(WorkType.START_WORD));}} else {attendanceVO.setWorkType(WorkType.toJson(WorkType.START_WORD));}attendanceVO.setIsWorkDay(false);}}/*** 是否外勤打卡** @return*/public boolean isLegwork(AttendanceRule attendanceRule, double lng, double lat) {if (GPSUtil.getDistance(Double.parseDouble(attendanceRule.getAttendanceLongitude()), Double.parseDouble(attendanceRule.getAttendanceLatitude()), lng, lat) > Double.parseDouble(attendanceRule.getAllowCheckinRange())) {return true;}return false;}/*** 是否遲到打卡** @return*/public boolean isLatework(AttendanceRule attendanceRule) {String startTime = DateUtil.formatDate(new Date()) + " " + attendanceRule.getStartWorkTime() + ":00";return DateUtil.compare(new Date(), DateUtil.parseDateTime(startTime)) > 0;}/*** 是否早退打卡** @return*/@Overridepublic Boolean isLeaveEarly() {AttendanceRule attendanceRule = attendanceRuleService.getAttendanceRule();if (calendarHolidayService.isWorkDay(attendanceRule, DateUtil.formatDate(new Date()))) {if (!Objects.isNull(attendanceRule) && StringUtils.isNotEmpty(attendanceRule.getEndWorkTime())) {String endTime = DateUtil.formatDate(new Date()) + " " + attendanceRule.getEndWorkTime() + ":00";return DateUtil.compare(DateUtil.parseDateTime(endTime), new Date()) > 0;}}return false;}/*** 查詢打卡記錄** @param date* @param userId* @param workType* @return*/@Overridepublic List<Attendance> listAttendanceRecord(String date, String userId, Integer workType) {return attendanceMapper.listAttendanceRecord(date, userId, workType);}@Overridepublic TodayAttendanceVO listTodayAttendance(String userId) {TodayAttendanceVO todayAttendance = new TodayAttendanceVO();AttendanceRule attendanceRule = attendanceRuleService.getAttendanceRule();Date maxLateTime = getMaxLateTime(attendanceRule);List<Attendance> attendanceList = listAttendanceRecord(DateUtil.formatDate(new Date()), userId, null);if (!calendarHolidayService.isWorkDay(attendanceRule, DateUtil.formatDate(new Date()))) { //如果不是工作日,無需打卡todayAttendance.setIsNeedAttendance(false);} else {todayAttendance.setIsNeedAttendance(true);}if (!CollectionUtils.isEmpty(attendanceList)) {Attendance start = attendanceList.stream().filter(attendance -> CommonConstant.ATTENDANCE_WORK_START.equals(attendance.getWorkType())).findFirst().orElse(null);Attendance end = attendanceList.stream().filter(attendance -> CommonConstant.ATTENDANCE_WORK_END.equals(attendance.getWorkType())).findFirst().orElse(null);if (!Objects.isNull(start)) {todayAttendance.setStart(start);todayAttendance.setIsStartMiss(false);} else if (DateUtil.compare(new Date(), maxLateTime) > 0) {todayAttendance.setIsStartMiss(todayAttendance.getIsNeedAttendance() ? true : null);}if (!Objects.isNull(end)) {todayAttendance.setEnd(end);todayAttendance.setIsEndMiss(false);}} else if (DateUtil.compare(new Date(), maxLateTime) > 0) {todayAttendance.setIsStartMiss(todayAttendance.getIsNeedAttendance() ? true : null);}return todayAttendance;}@Overridepublic AttendanceCalendarVO getAttendanceCalendar(String date, String userId) {AttendanceCalendarVO attendanceCalendar = new AttendanceCalendarVO();List<CalendarHoliday> calendarHolidayList = calendarHolidayService.getAttendanceCalendar(date);//按月查詢考勤記錄List<Attendance> attendanceList = listMonthAttendance(date, userId);List<TodayAttendanceVO> todayAttendanceList = new ArrayList<>();if (!CollectionUtils.isEmpty(calendarHolidayList)) {AttendanceRule attendanceRule = attendanceRuleService.getAttendanceRule();calendarHolidayList.forEach(calendarHoliday -> {if (DateUtil.compare(DateUtil.parseDate(DateUtil.formatDate(new Date())), calendarHoliday.getCurrentDay()) >= 0) {if (calendarHolidayService.isWorkDay(attendanceRule, DateUtil.formatDate(calendarHoliday.getCurrentDay()))) {TodayAttendanceVO todayAttendance = new TodayAttendanceVO();todayAttendance.setDate(DateUtil.formatDate(calendarHoliday.getCurrentDay()));if (!CollectionUtils.isEmpty(attendanceList)) {Attendance start = attendanceList.stream().filter(attendance -> DateUtil.formatDate(calendarHoliday.getCurrentDay()).equals(DateUtil.formatDate(attendance.getAttendanceTime())) && CommonConstant.ATTENDANCE_WORK_START.equals(attendance.getWorkType())).findFirst().orElse(null);Attendance end = attendanceList.stream().filter(attendance -> DateUtil.formatDate(calendarHoliday.getCurrentDay()).equals(DateUtil.formatDate(attendance.getAttendanceTime())) && CommonConstant.ATTENDANCE_WORK_END.equals(attendance.getWorkType())).findFirst().orElse(null);if (!Objects.isNull(start)) {todayAttendance.setStart(start);todayAttendance.setIsStartMiss(false);} else {todayAttendance.setIsStartMiss(true);}if (!Objects.isNull(end)) {todayAttendance.setEnd(end);todayAttendance.setIsEndMiss(false);} else {if (DateUtil.compare(DateUtil.parseDate(DateUtil.formatDate(new Date())), calendarHoliday.getCurrentDay()) > 0) {todayAttendance.setIsEndMiss(true);}}} else {todayAttendance.setIsStartMiss(true);if (DateUtil.compare(DateUtil.parseDate(DateUtil.formatDate(new Date())), calendarHoliday.getCurrentDay()) > 0) {todayAttendance.setIsEndMiss(true);}}if (todayAttendance.getIsStartMiss() || (!Objects.isNull(todayAttendance.getIsEndMiss()) && todayAttendance.getIsEndMiss())) {todayAttendance.setHasMiss(true);} else {todayAttendance.setHasMiss(false);}todayAttendance.setIsWorkDay(true);todayAttendanceList.add(todayAttendance);} else { //休息日打卡記錄List<Attendance> tmpList = attendanceList.stream().filter(attendance -> DateUtil.formatDate(calendarHoliday.getCurrentDay()).equals(DateUtil.formatDate(attendance.getAttendanceTime()))).collect(Collectors.toList());if (!CollectionUtils.isEmpty(tmpList)) {TodayAttendanceVO todayAttendance = new TodayAttendanceVO();todayAttendance.setDate(DateUtil.formatDate(calendarHoliday.getCurrentDay()));Attendance start = tmpList.stream().filter(attendance -> CommonConstant.ATTENDANCE_WORK_START.equals(attendance.getWorkType())).findFirst().orElse(null);if (!Objects.isNull(start)) {todayAttendance.setStart(start);}Attendance end = tmpList.stream().filter(attendance -> CommonConstant.ATTENDANCE_WORK_END.equals(attendance.getWorkType())).findFirst().orElse(null);if (!Objects.isNull(end)) {todayAttendance.setEnd(end);}todayAttendance.setHasMiss(false);todayAttendance.setIsWorkDay(false);todayAttendanceList.add(todayAttendance);}}}});}attendanceCalendar.setAttendanceList(todayAttendanceList);attendanceCalendar.setMissList(todayAttendanceList.stream().filter(TodayAttendanceVO::getHasMiss).map(TodayAttendanceVO::getDate).collect(Collectors.toList()));attendanceCalendar.setNoMissList(todayAttendanceList.stream().filter(todayAttendance -> !todayAttendance.getHasMiss()).map(TodayAttendanceVO::getDate).collect(Collectors.toList()));return attendanceCalendar;}@Overridepublic List<Attendance> listMonthAttendance(String date, String userId) {return attendanceMapper.listMonthAttendance(date, userId);}}

?????????每年一月一日定時任務生成節假日日歷數據。?

package com.ynfy.buss.attendance.calendarholiday.service.impl;import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ynfy.buss.attendance.attendancerule.entity.AttendanceRule;
import com.ynfy.buss.attendance.calendarholiday.entity.CalendarHoliday;
import com.ynfy.buss.attendance.calendarholiday.mapper.CalendarHolidayMapper;
import com.ynfy.buss.attendance.calendarholiday.service.ICalendarHolidayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;/*** @Description: 節假日日歷* @Author: jeecg-boot* @Date: 2025-07-18* @Version: V1.0*/
@Service
public class CalendarHolidayServiceImpl extends ServiceImpl<CalendarHolidayMapper, CalendarHoliday> implements ICalendarHolidayService {@Autowiredprivate CalendarHolidayMapper calendarHolidayMapper;@Value("${calendar.holiday.api}")private String calendarHolidayApi;@Value("${calendar.holiday.key}")private String key;/*** 生成節假日數據** @param year* @param type*/@Transactional(rollbackFor = Exception.class)@Overridepublic void generateHoliday(Integer year, Integer type) {List<CalendarHoliday> holidayList = new ArrayList<>();for (int i = 0; i < 12; i++) {int month = i + 1;String date;if (month < 10) {date = year + "-0" + month;} else {date = year + "-" + month;}JSONObject resultData = JSON.parseObject(HttpUtil.get(calendarHolidayApi + "?key=" + key + "&date=" + date + "&type=" + type, CharsetUtil.CHARSET_UTF_8));if (!Objects.isNull(resultData)) {JSONObject result = resultData.getJSONObject("result");JSONArray array = result.getJSONArray("list");if (!Objects.isNull(array) && !array.isEmpty()) {for (int index = 0; index < array.size(); index++) {JSONObject jsonObject = array.getJSONObject(index);CalendarHoliday holiday = new CalendarHoliday();holiday.setCurrentYear(String.valueOf(year));holiday.setCurrentDay(jsonObject.getDate("date"));holiday.setIsWorkDay(!jsonObject.getBoolean("isnotwork"));holiday.setJsonData(jsonObject.toJSONString());holidayList.add(holiday);}}}}remove(new LambdaQueryWrapper<CalendarHoliday>().eq(CalendarHoliday::getCurrentYear, String.valueOf(year)));if (!CollectionUtils.isEmpty(holidayList)) {saveBatch(holidayList);}}/*** 是否需要上班*/@Overridepublic boolean isWorkDay(AttendanceRule attendanceRule, String date) {CalendarHoliday calendarHoliday = calendarHolidayMapper.getByDate(date);if (Objects.isNull(calendarHoliday)) {return false;}Boolean needWork;List<String> weekList = Arrays.asList(attendanceRule.getAttendanceWeek().split(","));if (!CollectionUtils.isEmpty(weekList)) {JSONObject jsonObject = JSONObject.parseObject(calendarHoliday.getJsonData());String weekday = jsonObject.getString("weekday");if (weekList.contains(weekday)) {//日期需要打卡needWork = true;} else {needWork = false;}if (!Objects.isNull(attendanceRule.getIsAutoStatutoryHoliday()) && attendanceRule.getIsAutoStatutoryHoliday()) { //法定節假日自動排休needWork = calendarHoliday.getIsWorkDay();}} else {needWork = false;}return needWork;}@Overridepublic List<CalendarHoliday> getAttendanceCalendar(String date) {return calendarHolidayMapper.getAttendanceCalendar(date);}
}

? ? ? ? ?是否需要上班接口需要獲取考勤規則中的打卡星期,如果在打卡星期范圍內,則默認為上班日,不在范圍內則為休息。如果配置法定節假日自動排休,則法定節假日休息日對應需要將打卡日期改為休息,法定節假日補班日(上班日)對應需要將打卡日期改為上班。

? ? ? ? (6)安卓app端打卡配置

? ? ? ? 開通定位權限,填寫高德地圖申請的key。

????????Map地圖模塊。

?

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/90738.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/90738.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/90738.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CentOS 7.9 + GCC9 離線安裝 IWYU(Include What You Use)

本教程適用于 離線環境下在 CentOS 7.9 系統中使用 GCC 9 離線安裝 IWYU 的完整步驟&#xff0c;涵蓋 Clang 11.1.0 編譯、IWYU 構建以及頭文件自動優化流程。&#x1f4e5; 一、準備安裝包請提前下載以下源碼包&#xff08;可通過在線機器提前下載&#xff0c;再傳輸到離線環境…

基于Dapr Sidecar的微服務通信框架設計與性能優化實踐

基于Dapr Sidecar的微服務通信框架設計與性能優化實踐 一、技術背景與應用場景 隨著微服務架構的廣泛應用&#xff0c;分布式系統中服務間通信、可觀察性、可靠性等問題日益凸顯。Dapr&#xff08;Distributed Application Runtime&#xff09;作為一個開源的微服務運行時&…

Claude Code 超詳細完整指南(2025最新版)

&#x1f680; 終端AI編程助手 | 高頻使用點 生態工具 完整命令參考 最新MCP配置 &#x1f4cb; 目錄 &#x1f3af; 快速開始&#xff08;5分鐘上手&#xff09;&#x1f4e6; 詳細安裝指南 系統要求Windows安裝&#xff08;WSL方案&#xff09;macOS安裝Linux安裝安裝驗…

【lucene】SegmentReader初始化過程概述

readers[i] new SegmentReader(sis.info(i), sis.getIndexCreatedVersionMajor(), IOContext.READ); 這個方法已經把所有的文件都讀完了么&#xff1f;沒有“讀完”&#xff0c;但已經**全部“打開”**了。| 動作 | 是否發生 | |---|---| | **打開文件句柄 / mmap** | ? 立即完…

通俗理解主機的BIOS和UEFI啟動方式

“對于 22.04 版本&#xff0c;這些操作說明應適用于通過 BIOS 或 UEFI 兩種方式創建和運行啟動盤。”我們來詳細解釋一下這句話的含義&#xff0c;這句話的核心意思是&#xff1a;你按照這個教程制作出來的 Ubuntu U 盤&#xff0c;將擁有極佳的兼容性&#xff0c;無論是在老電…

Canal 1.1.7的安裝

數據庫操作的準備 1、開啟 Binlog 寫入功能&#xff0c;配置 binlog-format 為 ROW 模式&#xff0c;my.cnf 中配置如下: vi /etc/my.cnf [mysqld] log-binmysql-bin # 開啟 binlog binlog-formatROW # 選擇 ROW 模式 server_id1 # 配置 MySQL replaction 需要定義&#xff0c;…

python---類型轉換

文章目錄1. 基本類型轉換函數int() - 轉換為整數float() - 轉換為浮點數str() - 轉換為字符串bool() - 轉換為布爾值2. 其他類型轉換list() - 轉換為列表tuple() - 轉換為元組set() - 轉換為集合&#xff08;去重&#xff09;dict() - 轉換為字典3. 注意事項1. 兼容性&#xff…

JVM terminated. Exit code=1

出現JVM terminated. Exit code1錯誤通常是因為 Eclipse 所需的 Java 版本與系統中配置的 Java 版本不匹配。從錯誤信息中可以看到關鍵線索&#xff1a;-Dosgi.requiredJavaVersion21&#xff0c;表示此 Eclipse 版本需要 Java 21 或更高版本&#xff0c;但系統當前使用的是 Ja…

20250727-1-Kubernetes 網絡-Ingress介紹,部署Ingres_筆記

一、NodePort存在的不足 ?1. 四層負載均衡 ?? 實現技術: 基于iptables和ipvs實現 OSI層級: 位于傳輸層(第四層) 轉發依據: 基于IP地址和端口進行轉發 特點: 只能看到IP和端口信息 無法識別應用層協議內容 配置簡單但功能有限 2. 七層負載均衡 ?1)七層負載均衡的概念 ?…

Javaweb————HTTP的九種請求方法介紹

??????一.HTTP1.0定義的三種請求方式介紹 &#x1f3cd;?&#x1f3cd;?&#x1f3cd;?&#xff08;1&#xff09;GET請求 作用&#xff1a;向服務器獲取資源&#xff0c;比如常見的查詢請求 應用場景&#xff1a;絕大多數場景&#xff0c;比如我們訪問商城首頁查看圖…

C++day06(練習題)

循序漸進-基礎訓練 格式化輸入輸出 【描述】格式化輸入輸出練習輸入三個整數和一個浮點數&#xff0c;浮點數需要保留的不同小數點后面的數字。 【輸入描述】三個正整數以及以一個浮點數 【輸出描述】三個整數以及保留不同位數的浮點數 【樣例輸入】 1 2 3 9.12345678 【樣例輸…

基于大模型的預訓練、量化、微調等完整流程解析

隨著大語言模型&#xff08;LLM&#xff09;的飛速發展&#xff0c;模型的訓練、部署與優化成為了AI工程領域的重要課題。本文將從 預訓練、量化、微調 等關鍵步驟出發&#xff0c;詳細介紹大模型的完整技術流程及相關實踐。1. 預訓練&#xff08;Pre-training&#xff09; 1.1…

AI入門學習-模型評估示例講解

from sklearn.metrics import classification_report, confusion_matrix from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import make_classification# 生成示例分類數據 # n_samples: 樣本…

Python編程:初入Python魔法世界

一、常量表達式在編程中&#xff0c;常量指的是在程序執行期間其值不會改變的數據項。雖然 Python 并沒有專門的語法來定義常量&#xff08;不像某些其他語言如 Java 中有 final 關鍵字&#xff09;&#xff0c;但在實踐中&#xff0c;我們通常通過約定俗成的方式來表示一個變量…

Android WorkManager 詳解:高效管理后臺任務

引言在現代移動應用開發中&#xff0c;后臺任務處理是一個至關重要的功能。從同步數據到定期備份&#xff0c;從發送通知到處理耗時操作&#xff0c;后臺任務無處不在。然而&#xff0c;Android系統對后臺任務的限制越來越嚴格&#xff0c;開發者需要找到既高效又符合系統規范的…

MCU(微控制器)中的高電平與低電平?

MCU&#xff08;微控制器&#xff09;中的高電平與低電平&#xff1f; 在數字電路和MCU&#xff08;微控制器&#xff09;中&#xff0c;**高電平&#xff08;High Level&#xff09;和低電平&#xff08;Low Level&#xff09;**是兩種基本的邏輯狀態&#xff0c;用于表示二進…

前端項目下載發票pdf文件要求改文件名筆記

1、a鏈接&#xff08;修改失敗&#xff0c;存在跨域&#xff09;<el-table-columnalignrightlabel"下載地址"width"200"><template slot-scope"{row}"><a :href"row.dataUrl" download"文件名">下載</…

Kotlin 數據容器 - List(List 概述、創建 List、List 核心特性、List 元素訪問、List 遍歷)

一、List 概述List 是一個不可變的有序集合&#xff0c;一旦創建就不能修改其內容&#xff0c;即不能添加、刪除、更改元素List 提供了豐富的操作函數來處理數據二、創建 List 1、基礎創建 通過 listOf 函數創建&#xff08;推薦&#xff09; // 創建一個 List&#xff0c;包含…

HarmonyOS NEXT 系列之規范開發三方共享包

規范開發三方共享包〇、前言一、了解評分規則二、規范開發共享包1、規范開源協議名稱寫法2、將 oh-package.json5 文件補充完整3、補充 example 目錄4、基本的 README 和 CHANGELOG三、ohpm 包的源碼隔離特性〇、前言 對于開發者來說&#xff0c;對外發布代碼制品&#xff0c;…

[電網備考]計算機組成與原理

計算機系統概述 計算機發展歷程 從數據表示: 計算機可以分為數字計算機與模擬計算機 1946 第一臺電子數字計算機 ENIAC 在賓夕法尼亞大學誕生,標志進入電子計算機時代時間計算機發展階段1946-1958電子管計算機時代1958-1964晶體管計算機時代1964-1971集成電路計算機時代1971-至…