diff --git a/src/main/java/com/budwk/app/zhgh/activity/trainSignUp/service/impl/TrainSignUpActivityServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/trainSignUp/service/impl/TrainSignUpActivityServiceImpl.java index 1a2e265..3f1c83b 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/trainSignUp/service/impl/TrainSignUpActivityServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/activity/trainSignUp/service/impl/TrainSignUpActivityServiceImpl.java @@ -779,7 +779,10 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl signPoints = courses.stream().map(item -> NutMap.NEW().addv("courseId", item.getId()) .addv("courseName", item.getCourseName()).addv("courseLocation", item.getCourseLocation()) - .addv("signed", attended.stream().anyMatch(record -> Objects.equals(record.getCourseId(), item.getId())))) + .addv("signed", attended.stream().anyMatch(record -> Objects.equals(record.getCourseId(), item.getId()))) + // 同一课程可能有多个课次,展示该点位最早的有效签到时间,兼容历史空时间。 + .addv("attendTime", attended.stream().filter(record -> Objects.equals(record.getCourseId(), item.getId())) + .map(TrainSignUpUserCourse::getAttendTime).filter(Objects::nonNull).min(Date::compareTo).orElse(null))) .collect(Collectors.toList()); Map savedValues = new HashMap<>(); List signUsers = dao().query(TrainSignUpUser.class, diff --git a/src/main/resources/views/platform/zhghh5/activity/trainSignUp/scan/index.html b/src/main/resources/views/platform/zhghh5/activity/trainSignUp/scan/index.html index c85df36..83aec8b 100644 --- a/src/main/resources/views/platform/zhghh5/activity/trainSignUp/scan/index.html +++ b/src/main/resources/views/platform/zhghh5/activity/trainSignUp/scan/index.html @@ -1,82 +1,437 @@ - -
+
-
-
-
- {{locationMessage}} - {{mapReady ? '重新定位' : '重新加载地图'}} -
-
- 距签到地点约 {{Math.ceil(distance)}} 米,签到范围 {{signRadius}} 米 -
-
- -
-
- - + -
温馨提醒
{{legacyResult.msg || '正在获取签到信息'}}
-

- 确认签到 - 您已签到 -

- - -
-
-
{{point.courseName}}{{point.courseLocation || '未填写地点'}}
- {{point.signed ? '已签到' : '未签到'}} + + +
+ +
+
+
+
{{point.courseName || '点位名称未填写'}}
+ 当前点位 +
+ + {{point.signed === true ? '已打卡' : '未打卡'}} + +
+
地点:{{point.courseLocation || '未填写地点名称'}}
+
+ 签到时间 + {{formatAttendTime(point.attendTime)}} +
+
+ {{signData.isReachTarget ? '活动已达标,仍可继续打卡其他点位' : '完成 ' + (signData.signPointCount || 0) + ' 个不同点位打卡即可达标'}} +
@@ -101,10 +456,34 @@ layout("/layouts/platform_h5.html"){ statusMessage: "请获取定位后完成签到", messageType: "", signLoading: false, formLoading: false, pointPopup: false, formPopup: false, mapReady: false, mapLoading: false, locating: false, courseLoading: true, - locationMessage: "正在加载签到地点", currentPosition: null, distance: null + locationMessage: "正在加载签到地点", locationFailed: false, currentPosition: null, distance: null } }, computed: { + currentPointSigned() { + return this.extensionEnabled ? this.signData.currentPointSigned === true : this.legacyResult.code === 0 + }, + showSignButton() { + return !this.courseLoading && !this.currentPointSigned + }, + showDynamicFormButton() { + return this.signData.isReachTarget === true && this.signData.needDynamicForm === true + }, + isInRange() { + return this.distance !== null && this.distance <= this.signRadius + }, + // 达标与当前点位签到分别提示;错误优先,保留接口返回的处理失败原因。 + progressMessage() { + if (this.messageType === "error") return this.statusMessage + if (this.signData.isReachTarget && !this.currentPointSigned) return "已达标,可继续打卡其他点位。" + if (this.signData.isReachTarget) return this.statusMessage + return this.currentPointSigned ? "请继续完成其他点位签到。" : "到达点位后,点击确认签到。" + }, + // 未打卡项优先,组内保留接口中的点位序号,不能改动原数组。 + displaySignPoints() { + const points = Array.isArray(this.signData.signPoints) ? this.signData.signPoints : [] + return points.filter(point => point.signed !== true).concat(points.filter(point => point.signed === true)) + }, // 本项目点位为GCJ-02[纬度,经度],不能使用参考工程的[经度,纬度]下标。 sitePosition() { const p = this.row.courseLocationCoordinates @@ -127,6 +506,11 @@ layout("/layouts/platform_h5.html"){ } }, methods: { + /** attendTime为首次签到时间;返回可读时间,历史数据缺失时显示未记录。 */ + formatAttendTime(attendTime) { + const time = attendTime ? this.$moment(attendTime) : null + return time && time.isValid() ? time.format("YYYY-MM-DD HH:mm:ss") : "未记录" + }, /** * 复用布局中的腾讯地图Key;定位组件仅在扫码页按需加载。 * 返回SDK就绪Promise,失败时移除脚本,允许重试并处理页面销毁。 @@ -173,7 +557,7 @@ layout("/layouts/platform_h5.html"){ () => window.LBS && LBS.WebComponent && LBS.WebComponent.Geolocation) ]).then(() => key) }, - /** 地图标记使用静态文字区分地点与本人,同点位时标签分别显示在上方与下方。 */ + /** 地图标记使用静态文字区分地点与本人,below决定标签显示在坐标下方或上方。 */ markerStyle(label, color, below) { const y = below ? 12 : 36 const top = below ? 25 : 1 @@ -189,6 +573,7 @@ layout("/layouts/platform_h5.html"){ if (scanRuntime.destroyed || this.mapLoading) return Promise.resolve() if (this.mapReady) return this.refreshPosition() if (!this.sitePosition || !this.signRadius) { + this.$set(this, "locationFailed", true) this.$set(this, "locationMessage", "当前点位未设置有效坐标或签到半径,请联系管理员") return Promise.resolve() } @@ -202,7 +587,9 @@ layout("/layouts/platform_h5.html"){ scanRuntime.markers = new TMap.MultiMarker({ map: scanRuntime.map, styles: {target: this.markerStyle("签到地点", "#ee0a24", false), - current: this.markerStyle("我的位置", "#1989fa", true)}, + targetBelow: this.markerStyle("签到地点", "#ee0a24", true), + current: this.markerStyle("我的位置", "#1989fa", true), + currentAbove: this.markerStyle("我的位置", "#1989fa", false)}, geometries: [{id: "target", styleId: "target", position: center}] }) scanRuntime.circle = new TMap.MultiCircle({map: scanRuntime.map, @@ -213,6 +600,7 @@ layout("/layouts/platform_h5.html"){ }).catch(error => { if (scanRuntime.destroyed) return this.destroyMap() + this.$set(this, "locationFailed", true) this.$set(this, "locationMessage", error.message || "地图加载失败,请重新加载") }).finally(() => { if (!scanRuntime.destroyed) this.$set(this, "mapLoading", false) @@ -227,11 +615,20 @@ layout("/layouts/platform_h5.html"){ const a = Math.sin(dlat / 2) ** 2 + Math.cos(rad(site.lat)) * Math.cos(rad(coords.lat)) * Math.sin(dlng / 2) ** 2 return 6371000 * 2 * Math.atan2(Math.sqrt(Math.min(1, a)), Math.sqrt(Math.max(0, 1 - a))) }, - /** 同时展示签到圈和本人位置,不改变任何签到记录。 */ + /** coords为GCJ-02 {lat,lng};更新两处标记并适配视野,不改变任何签到记录。 */ fitMap(coords) { const p = this.sitePosition - const latPad = Math.max(this.signRadius / 111320, 0.0003) - const lngPad = latPad / Math.max(Math.cos(p.lat * Math.PI / 180), 0.000001) + // 北侧点的标签向上,南侧点向下,避免近距离签到时两个名称互相遮挡。 + const currentAbove = coords.lat > p.lat + scanRuntime.markers.remove(["target", "current"]) + scanRuntime.markers.add([ + {id: "target", styleId: currentAbove ? "targetBelow" : "target", position: new TMap.LatLng(p.lat, p.lng)}, + {id: "current", styleId: currentAbove ? "currentAbove" : "current", position: new TMap.LatLng(coords.lat, coords.lng)} + ]) + // 两点较远时也保留边距,避免本人位置和签到地点标签被地图边缘裁切。 + const latPad = Math.max(this.signRadius / 111320, Math.abs(p.lat - coords.lat) * 0.2, 0.0003) + const lngPad = Math.max(latPad / Math.max(Math.cos(p.lat * Math.PI / 180), 0.000001), + Math.abs(p.lng - coords.lng) * 0.2) scanRuntime.map.fitBounds(new TMap.LatLngBounds( new TMap.LatLng(Math.max(-90, Math.min(p.lat, coords.lat) - latPad), Math.max(-180, Math.min(p.lng, coords.lng) - lngPad)), new TMap.LatLng(Math.min(90, Math.max(p.lat, coords.lat) + latPad), Math.min(180, Math.max(p.lng, coords.lng) + lngPad)))) @@ -244,6 +641,7 @@ layout("/layouts/platform_h5.html"){ if (!this.mapReady || !scanRuntime.locator) return Promise.reject(new Error("定位组件尚未就绪")) if (this.locating) return Promise.reject(new Error("正在定位,请稍候")) this.$set(this, "locating", true) + this.$set(this, "locationFailed", false) this.$set(this, "currentPosition", null) this.$set(this, "distance", null) this.$set(this, "locationMessage", "正在获取您的当前位置") @@ -272,7 +670,6 @@ layout("/layouts/platform_h5.html"){ this.$set(this, "currentPosition", coords) this.$set(this, "distance", distance) this.$set(this, "locationMessage", distance <= this.signRadius ? "已进入签到范围" : "尚未进入签到范围") - scanRuntime.markers.add([{id: "current", styleId: "current", position: new TMap.LatLng(coords.lat, coords.lng)}]) this.fitMap(coords) resolve(coords) } catch (err) { reject(err) } @@ -294,7 +691,10 @@ layout("/layouts/platform_h5.html"){ }, {timeout: 10000, highAccuracy: true, maximumAge: 0}) } catch (error) { finish(new Error("腾讯定位调用失败,请重新加载地图")) } }).catch(error => { - if (!scanRuntime.destroyed) this.$set(this, "locationMessage", error.message) + if (!scanRuntime.destroyed) { + this.$set(this, "locationFailed", true) + this.$set(this, "locationMessage", error.message) + } throw error }).finally(() => { if (!scanRuntime.destroyed) this.$set(this, "locating", false) @@ -385,6 +785,9 @@ layout("/layouts/platform_h5.html"){ if (name === "points") this.$set(this, "pointPopup", true) if (name === "form") this.$set(this, "formPopup", true) window.mobilePopupHistory.open(this.ownerId, name) + if (name === "points") { + this.$nextTick(() => { if (this.$refs.signPointsList) this.$refs.signPointsList.scrollTop = 0 }) + } }, closePopup(name) { window.mobilePopupHistory.close(this.ownerId, name) }, closePopupFromHistory(name) {