Compare commits

2 Commits

Author SHA1 Message Date
jiangyanshan
a0f783fc5a Merge branch 'develop' of http://123.57.20.168:4000/admin/ys-app into develop 2025-12-17 14:18:55 +08:00
jiangyanshan
7b0a2472a3 修改签到打卡 2025-12-17 14:17:10 +08:00
2 changed files with 497 additions and 292 deletions

View File

@@ -10,23 +10,34 @@ export function addStartMap(data) {
}); });
} }
//出差打卡接口 // //出差打卡接口
export function businessTripClockIn(data) { // export function businessTripClockIn(data) {
return request.post({ // return request.post({
url: '/crm/app/appVisistMap/businessTripClockIn', // url: '/crm/app/appVisistMap/businessTripClockIn',
data // data
},{isTransformResponse:false}); // },{isTransformResponse:false});
} // }
// //新增地图开始打卡接口
// export function addStartMapForClockIn(data) {
// return request.post({
// url: "/crm/app/appVisistMap/StartaddForClockIn",
// data,
// },{
// isTransformResponse:false
// });
// }
//出差打卡接口
export function businessTripClockIn(data) {
return request('/app/appVisistMap/businessTripClockIn', data, 'POST', 'application/json;charset=UTF-8');
}
//新增地图开始打卡接口
export function addStartMapForClockIn(data) {
return request('/app/appVisistMap/StartaddForClockIn', data, 'POST', 'application/json;charset=UTF-8');
}
//新增地图开始打卡接口
export function addStartMapForClockIn(data) {
return request.post({
url: "/crm/app/appVisistMap/StartaddForClockIn",
data,
},{
isTransformResponse:false
});
}
//打卡信息查看 //打卡信息查看
export function CheckInInformationViewing(data) { export function CheckInInformationViewing(data) {

View File

@@ -1,344 +1,538 @@
<template> <template>
<view class="con-body"> <view class="con-body">
<view class="con-bg"> <view class="content">
<!-- 头部 --> <view class="pub-add-box1" @click="addInsertMapForSignIn">
<customHeader ref="customHeaderRef" :title="'签到打卡'" :leftFlag="true" :rightFlag="false"></customHeader> <view style="color: aliceblue; font-size: 22px;">签到</view>
</view>
<!-- 高度来避免头部遮挡 --> <view class="pub-add-box" @click="addInsertMapClockIn">
<view class="top-height"></view> <view style="color: aliceblue; font-size: 22px;">打卡</view>
</view>
<!-- 正文内容 --> <!-- 自定义打卡确认弹窗 -->
<view class="white-bg"> <view class="clock-in-modal" v-if="showClockInModal" @click="cancelClockIn">
<image src="../../../../static/images/business/btn-qd.png" class="btn-image" @click="handleCheckIn"/> <view class="modal-content" @click.stop>
<!-- <image src="../../../../static/images/business/btn-dk.png" class="btn-image" @click="handleClockIn"/>--> <view class="modal-header">
<image src="../../../../static/images/business/btn-dk.png" class="btn-image" @click="handleClick"/> <text class="modal-title">请选择工作状态</text>
<view class="check-desc"> </view>
业务人员可通过 <view class="modal-body">
<text class="font-orange">签到</text> <text class="address-text">{{ clockInAddress }}</text>
</view>
<text <view class="modal-footer">
class="font-blue">打卡 <view class="btn-group">
</text> <button class="cancel-btn" @click="businessTripClockIn">出差</button>
进行行为记录该时间会和走访报告中的时间进行关联便于查看 <button class="confirm-btn" @click="confirmClockIn">未出差</button>
</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<closeable-modal v-model="modalVisible"
title="确认在此处打卡?"
:content="form.addressForStart"
closeTip="轻触空白处关闭"
cancelText="出差"
confirmText="打卡"
contentAlign="center"
@confirm="handleConfirm"
@cancel="handleCancel"/>
<!-- 打卡遮罩层 -->
<!-- <view class="check-con" v-if="checkFlag">
<view class="check-in">
<view class="check-tip">打卡</view>
<view class="check-title">确定要在此处打卡吗</view>
<view class="check-location">
<uni-icons type="location-filled" size="30" color="#0395E0"></uni-icons> 亚洲金融大厦
</view>
<view class="check-address">北京市朝阳区天辰东路1号院</view>
<view class="check-footer">
<button class="btn-default" type="default" @click="handleCancel" size="mini"> </button>
<button class="btn-primary" type="primary" @click="handleSubmit" size="mini"> </button>
</view>
</view>
</view> -->
</view> </view>
</template> </template>
<script setup> <script>
import {ref, onMounted, reactive} from 'vue' import { addStartMapForClockIn, businessTripClockIn } from '@/api/crm/activity/map.js';
import customHeader from '@/components/customHeader.vue'
import {addStartMapForClockIn, businessTripClockIn} from '../../../../api/crm/activity/map';
import CloseableModal from "@/components/closeableModal.vue";
import {MapApiConfig} from "../../../../constants/mapApiConstants";
let form = reactive({ export default {
addressForStart: null, data() {
addressForEnd: null, return {
createId: null, arrUrl: null,
staffName: null, mescroll: undefined,
visistCode: null, // 打卡相关数据
visistId: null, showClockInModal: false,
remark: null, clockInAddress: '',
mapId: null clockInData: null,
}) downOption: {
// 签到 auto: false //是否在初始化后,自动执行downCallback; 默认true
let handleCheckIn = () => { },
uni.navigateTo({ upOption: {
url: './addRearkSignIn' onScroll: true,
}) use: true, // 是否启用上拉加载; 默认true
} auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
// 打卡 page: {
// let handleClockIn = () => { num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
// uni.getLocation({ size: 10 // 每页数据的数量,默认10
// type: 'wgs84', },
// success: (res) => { noMoreSize: 1, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
// const latiude = res.latitude; empty: {
// const longitude = res.longitude; tip: '暂无相关数据'
// //进行解析 }
// inverseGeocoding(latiude, longitude); },
// }, dataList: [],
// fail: function (err) { dzList: [],
// console.log("获取地址失败" + err) searchContent: '',
// } form: {
// }) addressForStart: null,
// } addressForEnd: null,
let modalVisible = ref(false); createId: null,
let handleClick = () => { staffName: null,
uni.getLocation({ visistCode: null,
type: 'wgs84', visistId: null,
success: (res) => { mapId: null,
const latitude = res.latitude; }
const longitude = res.longitude;
console.log('纬度:',latitude,',经度:', longitude);
inverseGeocoding(latitude, longitude);
},
fail: function (err) {
console.log("获取地址失败" + err)
} }
}) },
onLoad() {
// 修改顶部导航背景色
} uni.setNavigationBarColor({
// 反馈提示 frontColor: '#ffffff',
const feedback = (res, callback)=>{ backgroundColor: '#29abe2',
if(res.code===200){ animation: {
if(callback) callback(); duration: 400,
uni.showToast({ timingFunc: 'easeIn'
title: MapApiConfig.OTHER.typeName === form.addressForStart ? MapApiConfig.OTHER.typeName : res.msg, }
icon: 'success'
}) })
setTimeout(()=>{ let date = new Date();
uni.navigateBack(1); this.form.createTime = this.dateFormat("YYYY-mm-dd", date);
}, 500) },
} onShow() {
else{ this.arrUrl = uni.getStorageSync("avatar");
uni.showToast({ },
icon: 'none', methods: {
title: res.msg addInsertMap() {
}) uni.navigateTo({
} url: './insertMap'
} })
// 出差 },
const handleCancel = () => { //签到
businessTripClockIn(form).then(res=>{ addInsertMapForSignIn() {
feedback(res,()=>console.log('出差打卡成功'));
}).catch(e=>{
console.log(e);
})
}
// 打卡
let handleConfirm = () => {
addStartMapForClockIn(form).then(res=>{
feedback(res, ()=>console.log('打卡成功'));
}).catch(e=>{
console.log(e)
})
}
// 判定是否在范围内 uni.navigateTo({
function isWithinRange(lat, lon, centerLat, centerLon, rangeKm) { url: './addRearkSignIn'
const distance = haversineDistance(centerLat, centerLon, lat, lon); })
return distance <= rangeKm;
}
function haversineDistance(lat1, lon1, lat2, lon2, radius = 6371) { },
const dLat = degToRad(lat2 - lat1); //打卡
const dLon = degToRad(lon2 - lon1); addInsertMapClockIn() {
const a = var that = this;
Math.sin(dLat / 2) * Math.sin(dLat / 2) + uni.getLocation({
Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) * type: 'wgs84',
Math.sin(dLon / 2) * Math.sin(dLon / 2); success: (res) => {
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const latiude = res.latitude;
return radius * c; // 距离,单位:公里 const longitude = res.longitude;
} //进行解析
this.inverseGeocoding(latiude, longitude);
// 角度转弧度 },
function degToRad(deg) { fail: function (err) {
return deg * (Math.PI / 180); console.log("获取地址失败" + err)
} }
})
//解析地址 },
function inverseGeocoding(latitude, longitude) { // 显示打卡确认弹窗
uni.showLoading(); showClockInConfirm(address, data) {
const apiURL = MapApiConfig.URL; this.clockInAddress = address;
const params = { this.clockInData = data;
lat: latitude, this.showClockInModal = true;
},
// 取消打卡(点击遮罩层)
cancelClockIn() {
this.showClockInModal = false;
this.clockInAddress = '';
this.clockInData = null;
},
// 确认打卡
confirmClockIn() {
const that = this;
addStartMapForClockIn(this.clockInData).then(res => {
if (res.code == 200) {
uni.showToast({
icon: 'success',
title: res.msg,
duration: 1500,
});
setTimeout(() => {
uni.navigateBack(1)
}, 500)
} else {
uni.showToast({
icon: 'none',
title: res.msg,
});
}
that.cancelClockIn(); // 关闭弹窗
})
},
// 出差打卡
businessTripClockIn() {
const that = this;
businessTripClockIn(this.clockInData).then(res => {
if (res.code == 200) {
uni.showToast({
icon: 'success',
title: res.msg,
duration: 1500,
});
setTimeout(() => {
uni.navigateBack(1)
}, 500)
} else {
uni.showToast({
icon: 'none',
title: res.msg,
});
}
that.cancelClockIn(); // 关闭弹窗
})
},
isWithinRange(lat, lon, centerLat, centerLon, rangeKm) {
const distance = this.haversineDistance(centerLat, centerLon, lat, lon);
return distance <= rangeKm;
},
dateFormat(fmt, date) {
let ret;
const opt = {
"Y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"H+": date.getHours().toString(), // 时
"M+": date.getMinutes().toString(), // 分
"S+": date.getSeconds().toString() // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
};
};
return fmt;
},
haversineDistance(lat1, lon1, lat2, lon2, radius = 6371) {
const dLat = this.degToRad(lat2 - lat1);
const dLon = this.degToRad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.degToRad(lat1)) * Math.cos(this.degToRad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return radius * c; // 距离,单位:公里
},
degToRad(deg) {
return deg * (Math.PI / 180);
},
inverseGeocoding(latiude, longitude) {
var that = this;
let points = longitude + ',' + latiude
const apiURL = 'https://tiles.geovisearth.com/geo/v1/geocode/regeo';
const params = {
lat: latiude,
lng: longitude, lng: longitude,
radius: 1000, radius: 1000,
pageSize: 1, pageSize: 1,
currentPage: 1, currentPage: 1,
//classify: 220100 };
}; const token = '66c87c897f0251295afdc794e4fbf73046a070338a726fe04f06cece6cb1ffdf';
const token = MapApiConfig.token; uni.request({
uni.request({
url: apiURL, url: apiURL,
method: 'GET', method: 'GET',
data: params, data: params,
header: { header: {
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token
}, },
success: (res) => { success: (res) => {
modalVisible.value=true; if (res.statusCode == 200 && res.data.status == 200) {
console.log(res, "经纬度解析成功") let resdata = res.data.data.rows[0].address
if (res.statusCode === 200 && res.data.status === 200) { if (resdata == null) {
let resdata = res.data.data.rows[0].address const latiude1 = 34.1360;
if (resdata == null) { const longitude1 = 108.9126;
console.log(resdata.srcLat) if (this.isWithinRange(latiude1, longitude1, latiude, longitude, 1)) {
if (isWithinRange(latitude1, longitude1, latiude, longitude, 1)) { this.form.addressForStart = "西安办事处位置打卡"
form.addressForStart = MapApiConfig.XI_AN_BAN.typeName; let data = {
form.path = MapApiConfig.XI_AN_BAN.longitude + ',' + MapApiConfig.XI_AN_BAN.latitude ; mapId: that.form.mapId,
} addressForStart: that.form.addressForStart,
} cusName: that.form.cusName,
else { cusId: that.form.cusId,
form.addressForStart = resdata; remark: that.form.remark,
form.path = longitude + ',' + latitude; // 经度,纬度 path: longitude1 + "," + latiude1
} }
that.showClockInConfirm(that.form.addressForStart, data);
}
} else { } else {
form.addressForStart = MapApiConfig.OTHER.typeName; this.form.addressForStart = resdata;
form.path = longitude + ',' + latitude; // 经度,纬度 let data = {
mapId: that.form.mapId,
addressForStart: that.form.addressForStart,
cusName: that.form.cusName,
cusId: that.form.cusId,
path: points
}
that.showClockInConfirm(that.form.addressForStart, data);
} }
uni.hideLoading(); } else {
this.form.addressForStart = "第三方维护打卡"
let data = {
mapId: that.form.mapId,
addressForStart: that.form.addressForStart,
cusName: that.form.cusName,
cusId: that.form.cusId,
path: points
}
that.showClockInConfirm(that.form.addressForStart, data);
}
}, },
fail(e) { fail(e) {
console.log("获取位置失败", e) console.log("获取位置失败", e)
uni.hideLoading(); }
}, })
}) },
fail: function (err) {
console.log("调用失败" + err)
}
}
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.white-bg { .bg-green {
width: 650rpx; background-color: #29abe2;
margin: 0;
border-radius: 8px 8px 0 0;
padding: 50rpx;
/* #ifdef APP-PLUS */
height: calc(100vh - 125px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 98px);
/* #endif */
} }
.btn-image { .avatar {
width: 340rpx; align-items: center;
height: 340rpx; overflow: hidden;
margin: 30rpx auto 60rpx; width: 50rpx;
display: block; height: 60rpx;
border-radius: 50%;
margin: 0 auto;
margin-right: 10px;
} }
.check-desc { .pub-add-box {
background-color: #F5F5F5; z-index: 9999;
padding: 40rpx 50rpx; width: 320upx;
font-size: 28rpx; height: 320upx;
border-radius: 10px; border-radius: 50%;
margin-top: 100rpx; background: #13579E;
position: fixed;
right: 205upx;
bottom: 200upx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0px 0px 80upx #13579E;
.img {
width: 40upx;
height: 40upx;
}
} }
.check-desc .font-orange { .pub-add-box1 {
color: #F5813A; z-index: 9999;
font-size: 32rpx; width: 320upx;
font-weight: bold; height: 320upx;
border-radius: 50%;
background: #EA8021;
position: fixed;
right: 205upx;
top: 100upx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0px 0px 80upx #EA8021;
.img {
width: 40upx;
height: 40upx;
}
} }
.check-desc .font-blue { .avatarInner {
color: #2CBAEF; align-items: center;
font-size: 32rpx; overflow: hidden;
font-weight: bold; width: 50rpx;
height: 60rpx;
border-radius: 50%;
margin: 0 auto;
margin-right: 10px;
} }
/* 弹窗处理 */ .info-item-flex2 {
.check-con { width: 100rpx;
}
.info-item1 {
align-items: center;
// height: 100rpx;
}
.scroolView {
width: 630rpx;
white-space: nowrap;
display: flex;
align-items: baseline;
}
// 筛选
// .screen-cont{
// height: 80upx;
// background: #fff;
// margin-top: 100upx;
// }
// 客户列表
.cus-list {
background: #fff;
.item {
padding: 20upx 40upx;
border-bottom: 1px solid #f7f7f7;
&:last-child {
border-bottom: none;
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
.text {
font-size: 32upx;
color: #000;
}
.btn-box {
.deal-btn {
width: 100upx;
text-align: center;
padding: 10upx 0;
font-size: 24upx;
border-radius: 4upx;
&.deal {
background: #29abe2;
color: #fff;
}
&.un-deal {
background: #EFEFEF;
color: #BDBDBD;
}
}
.nameContent {
font-size: 17upx;
}
}
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
.name {
font-size: 32upx;
color: #000;
}
}
.cus-info {
.info-item {
&.flex {
display: flex;
align-items: center;
justify-content: space-between;
.img-box {
display: flex;
align-items: center;
.img {
width: 40upx;
height: 40upx;
margin-left: 10upx;
}
}
}
.left {
font-size: 28upx;
color: #737373;
}
}
}
}
}
/* 自定义打卡弹窗样式 */
.clock-in-modal {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; width: 100%;
bottom: 0; height: 100%;
background-color: rgba(0, 0, 0, 0.4); background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
z-index: 999; justify-content: center;
} }
/* 遮罩层内容样式 */ .modal-content {
.check-in { width: 80%;
max-width: 500rpx;
background-color: #fff; background-color: #fff;
padding: 40rpx 30rpx 60rpx; border-radius: 20rpx;
border-radius: 10px; overflow: hidden;
/* width: 620rpx; */ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.3);
width: 560rpx; }
position: absolute;
top: 48%; .modal-header {
left: 50%; padding: 40rpx 30rpx 20rpx;
transform: translate(-50%, -50%); text-align: center;
border-bottom: 1rpx solid #f0f0f0;
}
.modal-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.modal-body {
padding: 30rpx;
text-align: center; text-align: center;
} }
.check-in .check-tip { .address-text {
font-weight: bold; font-size: 28rpx;
color: #666;
line-height: 1.6;
} }
.check-in .check-title { .modal-footer {
font-size: 32rpx; padding: 20rpx 30rpx 30rpx;
padding: 20rpx 0;
} }
.check-in .check-location { .btn-group {
color: #0395E0;
font-size: 42rpx;
font-weight: bold;
display: flex; display: flex;
align-items: center; gap: 20rpx;
justify-content: center;
margin: 30rpx 0;
} }
.check-in .check-location .uniui-location-filled { .cancel-btn,
font-weight: normal; .confirm-btn {
margin-right: 20rpx; flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 40rpx;
font-size: 28rpx;
border: none;
} }
.check-in .check-address { .cancel-btn {
color: #919191; background-color: #f5f5f5;
font-size: 32rpx; color: #666;
margin-bottom: 80rpx;
} }
.check-in .check-footer { .confirm-btn {
display: flex; background-color: #13579E;
}
.check-in .check-footer .btn-default,
.check-in .check-footer .btn-primary {
background-color: #fff;
border: 1px solid #05A3F4;
color: #05A3F4;
border-radius: 25px;
padding: 0rpx 80rpx;
font-size: 34rpx;
/* margin-left: 0;
margin-right: 20rpx; */
}
.check-in .check-footer .btn-primary {
background-color: #05A3F4;
border: 1px solid #05A3F4;
color: #fff; color: #fff;
/* padding: 0rpx 60rpx; */
} }
</style> </style>