最近在小程序做巡逻打卡功能,现在记录一下用到的api和实现的方式。
包括:可随时添加标记点,并且在地图上显示弹窗,包含textarea,在上面记录信息。然后实时显示出添加的标记,点击标记会弹出自定义弹窗,展示记录的内容。并且实时记录位置巡逻结束后会展示线路。
<template>
<view class="wrap">
<view class="page-body">
<view class="page-section page-section-gap">
<map
style="width: 100%; height: 100vh"
:latitude="latitude"
:longitude="longitude"
:markers="covers"
:circles="circles"
:polyline="polyline"
@markertap="showMark"
>
<!-- 显示弹窗,包含textarea, -->
<cover-view class="cover-modal" v-show="isShowModal">
<cover-view class="modal-title">
记录信息
<cover-view style="font-size: 24rpx; color: #666666"
>({{ inputLength ? inputLength : 0 }}/50)</cover-view
>
</cover-view>
<!--给modal-body加上background-color和border解决真机上 map里cover-view里不能改变textarea背景色和加border的限制-->
<cover-view class="modal-body">
<textarea
class="l-textarea lg"
:maxlength="50"
auto-height
:value="inputSerialNum"
@input="inputRecord"
placeholder="请输入文字...... "
></textarea>
</cover-view>
<cover-view class="textarea-btn-wrap df_hbt_vc">
<cover-view
class="df_hc_vc"
style="height: 107rpx; width: 49.5%"
@click="handleCancel"
>
<cover-view class="btn">取消 </cover-view>
<!--解决 cover-view不能对某一边的border设置圆角的限制-->
<cover-view
style="background: #ddd; height: 38px; width: 1%"
></cover-view>
</cover-view>
<cover-view
class="df_hc_vc"
style="height: 107rpx; width: 49.5%"
@click="handleConfirm"
>
<cover-view class="btn">确定 </cover-view>
</cover-view>
</cover-view>
</cover-view>
<!--点击标记点弹出自定义弹窗 -->
<cover-view
@click="controlModal"
class="marker-modal"
v-show="showMarkModal"
>
<!-- marker-modal-title 的样式解决 cover-view不能对某一边r设置border的限制 主要是给目标元素加上4个边的border,然后使其超出父元素 然后父元素overflow:hidden,这样看起来就只有一个边-->
<cover-view class="marker-modal-title">
{{ markerCtime | FormatTime }}
</cover-view>
<cover-view class="marker-modal-body">
{{ markerContent }}
</cover-view>
</cover-view>
// out-border-radius解决 cover-view不能对某一边的border设置圆角的限制 主要是在元素的外层套一个 <cover-view>然后把形状设置为长方形,然后对这个外层设置border-radius
<cover-view
v-if="xunluoing"
class="btn-wrap out-border-radius"
style="width: unset"
>
<cover-view class="df_hc_vc expand">
<cover-view
class="df_hc_vc sure"
@click="record"
style="background-color: #005ca3"
>
<cover-view>记录</cover-view>
</cover-view>
<cover-view
class="sure df_hc_vc"
:style="{ 'background-color': endColor }"
@click="end"
>
<cover-view>结束</cover-view>
</cover-view>
</cover-view>
</cover-view>
<cover-view v-else class="end-mes-wrap df_hc_vc">
<cover-image
:src="Icons.common_deal_gou"
class="gou-icon"
></cover-image>
<cover-view> 恭喜您,今日巡逻已结束 </cover-view>
</cover-view>
<!--地图上自制tabbar-->
<cover-view class="tab-bar">
<cover-view class="tab-bar-item df_hc_vc">
<cover-view style="width: 38rpx; height: 38rpx">
<cover-image
:src="Icons.common_deal_jrdk_a"
class="icon"
alt=""
/></cover-view>
<cover-view style="color: #005ca3">今日打卡</cover-view>
</cover-view>
<cover-view
class="tab-bar-item df_hc_vc"
@click="navigate('/pages/waiqing-baobei/index')"
>
<cover-view style="width: 38rpx; height: 38rpx">
<cover-image :src="Icons.common_deal_wqbb" class="icon" alt=""
/></cover-view>
<cover-view>外勤报备</cover-view>
</cover-view>
<cover-view
class="tab-bar-item df_hc_vc"
@click="navigate('/pages/punch-calender-xunluo/index')"
>
<cover-view style="width: 38rpx; height: 38rpx">
<cover-image :src="Icons.common_deal_kqrl" class="icon" alt=""
/></cover-view>
<cover-view>考勤日历</cover-view>
</cover-view>
<cover-view
class="tab-bar-item df_hc_vc"
@click="navigate('/pages/punch-setting-remind/index')"
>
<cover-view style="width: 38rpx; height: 38rpx">
<cover-image :src="Icons.common_deal_kqtx" class="icon" alt=""
/></cover-view>
<cover-view>考勤提醒</cover-view>
</cover-view>
</cover-view>
</map>
</view>
</view>
</view>
</template>
<script>
import { common } from "@/mixin";
import {
locationLegal,
getToday,
addPatrolRecord,
gePatrolRecord,
addMove,
getMove,
} from "@/services/punch";
import LModal from "@/components/common/Modal";
import dayjs from "dayjs";
export default {
mixins: [common],
components: {
LModal,
MainTabBar,
},
data() {
return {
isShowModal: false,
modalTitle: "记录信息",
xunluoing: true,
getStartInfo: true,
getEndInfo: true,
inputFocus: false, // input 框的focus状态
inputModel: "", // input 框的输入内容
inputInfo: "请输入搜索地址", // cover-view 显示的 input 的输入内容,初始值充当placeholder作用
id: "0", // 使用 marker点击事件 需要填写id
title: "map",
latitude: "",
longitude: "",
covers: [],
controls: [],
circles: [],
patrolRecords: [], //巡逻记录
myClock: "",
polyline: [],
todayInfo: "",
attendanceId: "",
inputSerialNum: "",
lock: false,
lock1: false,
endColor: "#b6c9d8",
markerCtime: "",
markerContent: "",
showMarkModal: false,
xunluoStatus: "started",
};
},
onShow() {
//进入首页就定位
this.useLocation().then((res) => {
this.circles[0] = {};
this.circles[0].latitude = res.latitude;
this.circles[0].longitude = res.longitude;
getToday().then((res) => {
this.todayInfo = { ...res };
this.circles[0].radius = res.shouldStartLocation.radius;
this.circles[0].color = "#428BCA88";
this.circles[0].fillColor = "#B6E1F248";
this.circles[0].strokeWidth = "#B6E1F248";
});
// 获取巡逻记录
this.get();
});
},
filters: {
FormatTime: function (val) {
if (!val) {
return "-";
}
return dayjs(val).format("HH:mm:ss");
},
},
onLoad(options) {
if (options.attendanceId) {
uni.setStorageSync("attendanceId", options.attendanceId);
}
this.activeGetLocation();
},
beforeDestroy() {
console.log("销毁了");
clearInterval(this.myClock);
},
computed: {
inputLength() {
return this.inputSerialNum.length;
},
},
methods: {
controlModal() {
this.showMarkModal = false;
},
navigate(url) {
uni.navigateTo({
url: url + "?xunluoStatus=" + this.xunluoStatus,
});
},
inputRecord(e) {
this.inputSerialNum = e.detail.value;
},
showModal() {
this.isShowModal = true;
},
handleCancel() {
this.isShowModal = false;
this.inputSerialNum = "";
},
handleConfirm() {
if (!this.lock) {
this.lock = true;
if (this.inputSerialNum.trim() == "") {
uni.showToast({
title: "请输入记录信息!",
icon: "none",
duration: 2000,
});
this.lock = false;
return;
}
this.me_addPatrolRecord(this.inputSerialNum);
} else {
}
},
//5秒获取添加一次定位
activeGetLocation() {
this.myClock = setInterval(() => {
console.log("我在ing");
this.me_addMove();
}, 5000);
},
me_addMove() {
this.getLocation().then(
(res) => {
var config = {};
config.attendanceId = uni.getStorageSync("attendanceId");
config.locations = [];
var location = {
longitude: res.longitude,
latitude: res.latitude,
};
config.locations.push(location);
//添加巡逻轨迹
addMove(config).then((res) => {});
//判断是否在考勤范围内
var config = {
longitude: res.longitude,
latitude: res.latitude,
type: this.todayInfo.start ? "WORK" : "QUIT",
};
locationLegal(config).then((legal) => {
if (legal) {
this.endColor = "#005CA3";
}
});
},
(err) => {}
);
},
useLocation() {
return new Promise((resolve, reject) => {
this.getLocation().then(
(res) => {
this.longitude = res.longitude;
this.latitude = res.latitude;
resolve(res);
},
(err) => {}
);
});
},
getLocation() {
return new Promise((resolve, reject) => {
uni.getLocation({
type: "wgs84",
success: function (res) {
resolve(res);
},
fail: function (res) {
uni.showToast({
title: "未获取到当前位置!",
icon: "none",
duration: 2000,
});
reject(res);
},
});
});
},
showMark(e) {
var coverId = e.detail.markerId;
var tabCover = this.patrolRecords.find((item, index) => {
//生产标记点
return coverId == item.id;
});
this.markerCtime = tabCover.createDate;
this.markerContent = tabCover.mark;
this.showMarkModal = !this.showMarkModal;
},
record() {
this.isShowModal = true;
this.showMarkModal = false;
},
end() {
if (this.endColor == "#b6c9d8") {
return;
}
var that = this;
uni.showModal({
title: "提示",
content: "确定结束吗",
success: function (res) {
if (!that.lock1) {
if (res.confirm) {
that.lock1 = true;
// 1,判断是否在考勤范围内 是才能结束
that.useLocation().then((res1) => {
var config = {
longitude: res1.longitude,
latitude: res1.latitude,
type: that.todayInfo.start ? "WORK" : "QUIT",
};
locationLegal(config).then((res) => {
if (res) {
clearInterval(that.myClock);
that.isShowModal = false;
//2,切换页面样式
that.xunluoing = false;
that.xunluoStatus = "end";
getMove({
attendanceId: uni.getStorageSync("attendanceId"),
}).then((res) => {
//3,展示巡逻轨迹
var points = res.map((item) => {
var obj = {};
obj.longitude = item.longitude;
obj.latitude = item.latitude;
return obj;
});
var pointsObj = {
//指定一系列坐标点,从数组第一项连线至最后一项
points: points,
color: "#005CA3", //线的颜色
width: 10, //线的宽度
dottedLine: true, //是否虚线
};
that.polyline.push(pointsObj);
//添加终点标记点
var cover = {
id: Date.parse(new Date()),
longitude: res1.longitude,
latitude: res1.latitude,
iconPath:
"http://60.255.160.15:31314/government-affair/image/2020/1216/efa9c778130c415d82e5a0ea7d19468f.png",
width: 24,
height: 33,
};
that.covers.push(cover);
that.lock1 = false;
});
} else {
uni.showToast({
title: "不在考勤范围内",
icon: "none",
duration: 2000,
});
that.lock1 = false;
}
});
});
} else if (res.cancel) {
that.lock1 = false;
}
}
},
});
},
//获取巡逻记录
get() {
gePatrolRecord({ attendanceId: uni.getStorageSync("attendanceId") }).then(
(res) => {
//获取巡逻记录
this.patrolRecords = [...res]; //获取所有巡逻记录
if (this.patrolRecords.length > 0) {
this.patrolRecords.map((item, index) => {
//生产标记点
var i = item.id;
if (this.patrolRecords.length <= 2) {
if (index == 0) {
//开始
iconPath =
"http://60.255.160.15:31314/government-affair/image/2020/1103/0573518208574b40851dac3a91dd5fe7.png";
}
if (index == 1) {
//结束
iconPath =
"http://60.255.160.15:31314/government-affair/image/2020/1216/14bdff0ac0594e20a11296d1a69ba89c.png";
}
} else {
var iconPath =
"http://60.255.160.15:31314/government-affair/image/2020/1216/14bdff0ac0594e20a11296d1a69ba89c.png";
if (index == 0) {
//开始
iconPath =
"http://60.255.160.15:31314/government-affair/image/2020/1103/0573518208574b40851dac3a91dd5fe7.png";
}
}
var cover = {
id: i,
latitude: item.locationLatitude,
longitude: item.locationLongitude,
iconPath: iconPath,
width: 24,
height: 33,
// callout: {
// // width: 286,
// // height: 141,
// // content: item.mark,
// // display: "BYCLICK",
// // bgColor: "#ffffff",
// // fontSize: "16",
// },
};
this.covers.push(cover);
});
} else {
this.covers[0] = {
id: 0,
latitude: res.latitude,
longitude: res.longitude,
iconPath:
"http://60.255.160.15:31314/government-affair/image/2020/1103/0573518208574b40851dac3a91dd5fe7.png",
width: 24,
height: 33,
};
}
}
);
},
//确定添加记录
me_addPatrolRecord(val) {
var config = {};
config.attendanceId = uni.getStorageSync("attendanceId");
config.content = val;
this.getLocation().then(
(res) => {
config.location = {
longitude: res.longitude,
latitude: res.latitude,
};
this.isShowModal = false;
this.lock = true;
//添加巡逻记录
addPatrolRecord(config).then((res) => {
// 然后再获取 再产生标记点
this.get();
});
},
(err) => {}
);
},
},
};
</script>
<style lang="scss" scoped>
.top-bar {
width: 100%;
height: 87rpx;
background: #ffffff;
box-shadow: 0px 9px 24px 0px rgba(0, 0, 0, 0.28);
.rule-wrap {
font-size: 28rpx;
font-family: PingFang SC;
font-weight: bold;
color: #333333;
margin-right: 30rpx;
.rule-icon {
margin-right: 14rpx;
width: 16rpx;
height: 32rpx;
}
}
}
.btn-wrap {
width: 420rpx;
height: 160rpx;
position: absolute;
right: 50%;
bottom: 12%;
transform: translate(50%, 0);
}
.out-border-radius {
border-radius: 80rpx;
}
.sure {
width: 210rpx;
height: 160rpx;
font-size: 32rpx;
color: #ffffff;
}
.end-mes-wrap {
width: 618rpx;
height: 78rpx;
background: #005ca3;
border-radius: 10rpx;
position: absolute;
right: 50%;
bottom: 12%;
transform: translate(50%, 0);
font-size: 32rpx;
color: #ffffff;
margin-left: 22rpx;
font-weight: bold;
}
.gou-icon {
width: 40rpx;
height: 33rpx;
margin-right: 22rpx;
}
.cover-modal {
position: absolute;
width: 572rpx;
height: 420rpx;
background: #ffffff;
border-radius: 20rpx;
top: 33rpx;
left: 50%;
transform: translate(-50%, 0);
overflow: hidden;
}
.modal-title {
padding-top: 41rpx;
padding-bottom: 40rpx;
padding-left: 40rpx;
font-size: 32rpx;
font-weight: bold;
color: #333333;
width: 574rpx;
border: 1rpx solid #eeeeee;
display: flex;
align-items: flex-end;
}
.modal-body {
border: 1px solid #eeeeee !important;
width: 490rpx;
height: 161rpx;
margin: 39rpx auto 0;
background-color: #fafafa !important;
}
.cancel {
background-color: #fff;
font-size: 28rpx;
font-weight: 500;
color: #999999;
}
.btn {
text-align: center;
width: 100%;
}
.textarea-btn-wrap {
height: 107rpx;
// padding: 35rpx 0;
width: 100%;
}
.l-textarea {
width: 490rpx;
height: 161rpx;
background-color: #fafafa !important;
border-radius: 5px;
position: relative;
font-size: 28rpx;
color: #999999;
padding: 20rpx 0 0 20rpx;
&.lg {
min-height: 161rpx;
}
}
.tab-bar {
/* 本身的样式 */
background-color: #f6f6f6;
height: 96rpx;
border-top: 1rpx solid #eee;
box-shadow: 0rpx -1rpx 1rpx rgba(150, 150, 150, 0.08);
/* 定位相关 */
position: fixed;
left: 0;
right: 0;
bottom: 0;
/* 利用flex进行布局 */
display: flex;
text-align: center;
justify-content: space-around;
}
.tab-bar-item {
display: flex;
flex-direction: column;
text-align: center;
justify-content: center;
}
.icon {
width: 38rpx;
height: 38rpx;
margin-bottom: 5rpx;
}
.marker-modal {
width: 572rpx;
min-height: 282rpx;
background: #ffffff;
border-radius: 20px;
position: absolute;
top: 33rpx;
left: 50%;
transform: translate(-50%, 0);
overflow: hidden;
}
.marker-modal-body {
width: 486rpx;
min-height: 128rpx;
margin: 20rpx auto 0;
font-size: 28rpx;
color: #666666;
}
.marker-modal-title {
padding-top: 44rpx;
padding-bottom: 23rpx;
padding-left: 42rpx;
font-size: 32rpx;
font-weight: bold;
color: #333333;
width: 574rpx;
border: 1rpx solid #eeeeee;
display: flex;
align-items: flex-end;
}
</style>