迁移嘉兴学院的签到定位页面移植

This commit is contained in:
2026-11-04 13:42:17 +08:00
parent 9bebd3af82
commit 0de8ee9232
4 changed files with 358 additions and 106 deletions
@@ -76,6 +76,7 @@ public class TrainsignUpMineController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"trainSignUp.mine", "h5.trainSignUp.mine"}, mode = SaMode.OR)
@SLog(tag = "品牌活动-活动签到", msg = "主动扫码签到")
// courseId为二维码点位ID,lat/lng为腾讯GCJ-02纬度/经度;Result返回签到结果及扩展进度。
public Result drivingScan(String courseId, Double lat, Double lng) {
TrainSignUpCourse extensionCourse = dao.fetch(TrainSignUpCourse.class, courseId);
TrainSignUpActivity extensionActivity = extensionCourse == null ? null
@@ -90,41 +91,12 @@ public class TrainsignUpMineController {
}
}
// 主动扫码签到是用户自己打开扫一扫,扫二维码签到
int count = dao.count(TrainSignUpUserCourse.class, Cnd.where("courseId", "=", courseId).and("userId", "=", SecurityUtil.getUserId()));
if (count == 0) {
return Result.error(99, "未查询到您的报名记录");
try {
activityService.handleLegacyScanCodeSign(courseId, SecurityUtil.getUserId(), lat, lng);
return Result.success("签到成功");
} catch (IllegalArgumentException e) {
return Result.error(99, e.getMessage());
}
TrainSignUpCourse course = dao.fetch(TrainSignUpCourse.class, courseId);
List<Double> coordinates = course.getTransPosition();
int minute = course.getSignUnit() != null ? course.getSignUnit() : 0;
int radius = course.getRadius() != null ? course.getRadius() : 100;
boolean flag = true;
// 获取现在的日期,并根据设置的数据
List<TrainSignUpUserCourse> timeList = dao.query(TrainSignUpUserCourse.class, Cnd.where("courseId", "=", courseId).and("userId", "=", SecurityUtil.getUserId()));
for (TrainSignUpUserCourse userCourse : timeList) {
DateTime startTime = DateUtil.offsetMinute(userCourse.getCourseStartTime(), -minute);
DateTime endTime = DateUtil.offsetMinute(userCourse.getCourseEndTime(), minute);
if(DateUtil.isIn(DateUtil.date(), startTime, endTime)) {
userCourse.setAttend(true);
userCourse.setAttendTime(DateUtil.date());
flag = false;
}
}
if(flag) {
return Result.error(99, "未到签到时间");
}
double distance = DistanceUtil.getDistance(coordinates.get(0), coordinates.get(1), lat, lng);
if(distance > radius) {
return Result.error(99, "签到失败,您距离签到点还有%.1f米".formatted(distance - radius));
}
dao.update(timeList);
return Result.success("签到成功");
}
/**
@@ -149,12 +149,22 @@ public interface TrainSignUpActivityService extends BaseService<TrainSignUpActiv
*
* @param courseId 扫码进入的课程点位ID
* @param userId 当前登录用户ID
* @param latitude 浏览器定位得到的WGS84纬度
* @param longitude 浏览器定位得到的WGS84经度
* @param latitude 腾讯前端定位组件返回的GCJ-02纬度
* @param longitude 腾讯前端定位组件返回的GCJ-02经度
* @return 最新签到进度及达标后的处理信息
*/
NutMap handleScanCodeSign(String courseId, String userId, Double latitude, Double longitude);
/**
* 普通活动扫码签到,沿用报名及时间限制,使用腾讯GCJ-02定位校验签到范围。
* @param courseId 二维码中的课程点位ID
* @param userId 当前登录用户ID
* @param latitude 本次GCJ-02纬度
* @param longitude 本次GCJ-02经度
* @return 无返回值,业务校验失败抛出IllegalArgumentException
*/
void handleLegacyScanCodeSign(String courseId, String userId, Double latitude, Double longitude);
/**
* 保存签到达标后填写的动态表单。
*
@@ -623,7 +623,7 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
lockScanCodeUser(userId);
TrainSignUpCourse course = requireScanCodeCourse(courseId, userId);
TrainSignUpActivity activity = dao().fetch(TrainSignUpActivity.class, course.getActivityId());
requireScanCodeLocation(course.getTransPosition(), latitude, longitude, effectiveRadius(course));
requireScanCodeLocation(course.getCourseLocationCoordinates(), latitude, longitude, effectiveRadius(course));
List<TrainSignUpUserCourse> records = dao().query(TrainSignUpUserCourse.class,
Cnd.where("activityId", "=", activity.getId()).and("courseId", "=", courseId)
@@ -846,12 +846,17 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
&& Double.isFinite(coordinates.get(0)) && Double.isFinite(coordinates.get(1));
}
/** 当前定位与transPosition均为WGS84坐标,沿用原签到距离计算口径。 */
/**
* coordinates为配置的GCJ-02[纬度,经度],latitude/longitude为本次腾讯定位值。
* 不与WGS84 transPosition混算;距离超出配置半径即拒绝签到。
*/
private void requireScanCodeLocation(List<Double> coordinates, Double latitude, Double longitude, int radius) {
if (!validCoordinates(coordinates)) {
if (!validCoordinates(coordinates) || Math.abs(coordinates.get(0)) > 90 || Math.abs(coordinates.get(1)) > 180
|| radius <= 0) {
throw new IllegalArgumentException("当前点位未配置有效签到坐标");
}
if (latitude == null || longitude == null || !Double.isFinite(latitude) || !Double.isFinite(longitude)) {
if (latitude == null || longitude == null || !Double.isFinite(latitude) || !Double.isFinite(longitude)
|| Math.abs(latitude) > 90 || Math.abs(longitude) > 180) {
throw new IllegalArgumentException("请先获取当前定位后再签到");
}
double distance = com.budwk.app.base.utils.DistanceUtil.getDistance(coordinates.get(0), coordinates.get(1), latitude, longitude);
@@ -860,6 +865,40 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
}
}
/** 普通扫码入口接收课程、用户及GCJ-02定位;报名、范围和时间全部通过后才更新签到记录。 */
@Override
@Aop(TransAop.READ_COMMITTED)
public void handleLegacyScanCodeSign(String courseId, String userId, Double latitude, Double longitude) {
lockScanCodeUser(userId);
TrainSignUpCourse course = dao().fetch(TrainSignUpCourse.class, courseId);
if (course == null) {
throw new IllegalArgumentException("未找到签到课程");
}
List<TrainSignUpUserCourse> records = dao().query(TrainSignUpUserCourse.class,
Cnd.where("courseId", "=", courseId).and("userId", "=", userId));
if (records.isEmpty()) {
throw new IllegalArgumentException("未查询到您的报名记录");
}
requireScanCodeLocation(course.getCourseLocationCoordinates(), latitude, longitude, effectiveRadius(course));
Date now = new Date();
int minute = course.getSignUnit() == null ? 0 : course.getSignUnit();
List<TrainSignUpUserCourse> available = records.stream().filter(record ->
record.getCourseStartTime() != null && record.getCourseEndTime() != null
&& !now.before(DateUtil.offsetMinute(record.getCourseStartTime(), -minute))
&& !now.after(DateUtil.offsetMinute(record.getCourseEndTime(), minute))).toList();
if (available.isEmpty()) {
throw new IllegalArgumentException("未到签到时间");
}
for (TrainSignUpUserCourse record : available) {
// 重复请求保留首次签到时间,不改写其他课次。
if (!record.isAttend()) {
record.setAttend(true);
record.setAttendTime(now);
dao().update(record);
}
}
}
private boolean isEmptyDynamicValue(Object value) {
return value == null || value instanceof String && StrUtil.isBlank((String) value)
|| value instanceof Collection && ((Collection<?>) value).isEmpty();
@@ -13,6 +13,9 @@ layout("/layouts/platform_h5.html"){
.sign-message.success { color: #07c160; }
.sign-message.error { color: #ee0a24; }
.sign-actions { display: grid; gap: 12px; }
.location-panel { padding: 12px 16px; background: #fff; }
.location-tools { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.location-message { color: #606773; font-size: 13px; overflow-wrap: anywhere; }
.point-list { max-height: 54vh; overflow-y: auto; }
.point-row { display: flex; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid #f0f1f3; }
.point-row small { display: block; margin-top: 4px; color: #969799; }
@@ -26,6 +29,17 @@ layout("/layouts/platform_h5.html"){
<div id="app" class="sign-page">
<van-nav-bar title="活动签到" left-text="返回" left-arrow @click-left="onNavBack" fixed placeholder></van-nav-bar>
<div id="mapContainer" class="sign-map"></div>
<div class="location-panel">
<div class="location-tools">
<span class="location-message" role="status">{{locationMessage}}</span>
<van-button size="small" plain type="info" icon="aim"
:loading="locating || mapLoading" :disabled="signLoading || courseLoading"
@click="refreshPosition">{{mapReady ? '重新定位' : '重新加载地图'}}</van-button>
</div>
<div v-if="distance !== null" class="location-message">
距签到地点约 {{Math.ceil(distance)}} 米,签到范围 {{signRadius}} 米
</div>
</div>
<template v-if="extensionEnabled">
<section class="sign-panel">
<h1 class="sign-title">{{signData.activityName || '活动签到'}}</h1>
@@ -34,7 +48,7 @@ layout("/layouts/platform_h5.html"){
<van-progress :percentage="progressPercentage" color="#07c160" track-color="#e9edf1" stroke-width="8"></van-progress>
<div class="sign-message" :class="messageType">{{statusMessage}}</div>
<div class="sign-actions">
<van-button type="primary" block :loading="signLoading" :disabled="signLoading || signData.currentPointSigned" @click="onSign">
<van-button type="primary" block :loading="signLoading" :disabled="signDisabled" @click="onSign">
{{signData.currentPointSigned ? '当前点位已签到' : '立即签到'}}
</van-button>
<van-button plain type="info" block @click="openPopup('points')">查看全部签到点位</van-button>
@@ -50,12 +64,12 @@ layout("/layouts/platform_h5.html"){
</div>
<div class="weui-msg__text-area"><div class="weui-msg__title">温馨提醒</div><div>{{legacyResult.msg || '正在获取签到信息'}}</div></div>
<div class="weui-msg__opr-area"><p class="weui-btn-area">
<a v-if="legacyResult.code !== 0" @click="onSign" class="weui-btn weui-btn_default">重新签到</a>
<van-button v-if="legacyResult.code !== 0" type="primary" block :loading="signLoading" :disabled="signDisabled" @click="onSign">确认签到</van-button>
<a v-else class="weui-btn weui-btn_primary">您已签到</a>
</p></div>
</div>
<van-popup v-model="pointPopup" position="bottom" round :close-on-click-overlay="false" :style="{height: '62vh'}">
<van-popup :value="pointPopup" @input="visible => { if (!visible) closePopup('points') }" position="bottom" round :close-on-click-overlay="false" :style="{height: '62vh'}">
<van-nav-bar title="签到点位" left-text="关闭" left-arrow @click-left="closePopup('points')"></van-nav-bar>
<div class="point-list">
<div class="point-row" v-for="point in signData.signPoints" :key="point.courseId">
@@ -64,7 +78,7 @@ layout("/layouts/platform_h5.html"){
</div>
</div>
</van-popup>
<van-popup v-model="formPopup" position="bottom" round :close-on-click-overlay="false" :style="{height: '78vh'}">
<van-popup :value="formPopup" @input="visible => { if (!visible) closePopup('form') }" position="bottom" round :close-on-click-overlay="false" :style="{height: '78vh'}">
<van-nav-bar title="填写签到信息" left-text="关闭" left-arrow @click-left="closePopup('form')"></van-nav-bar>
<div class="form-popup"><train-sign-dynamic-form ref="dynamicForm" :dynamic-data="signData.dynamicColumnsData || []"></train-sign-dynamic-form></div>
<div class="popup-footer"><van-button type="primary" block :loading="formLoading" :disabled="formLoading" @click="saveDynamicForm">保存</van-button></div>
@@ -72,6 +86,10 @@ layout("/layouts/platform_h5.html"){
</div>
<script nonce="${cspNonce!}">
// 腾讯实例与异步回调不进入Vue响应式系统,销毁时统一释放。
const scanRuntime = {map: null, markers: null, circle: null, locator: null,
destroyed: false, requestId: 0, cancelLocation: null, scripts: {},
nonce: document.currentScript && document.currentScript.nonce}
const vue = new Vue({
el: "#app",
store,
@@ -81,91 +99,297 @@ layout("/layouts/platform_h5.html"){
ownerId: "train-sign-scan-" + Date.now(), courseId: GetQueryString("courseId"), row: {}, signData: {},
extensionEnabled: false, legacyResult: {code: 1, msg: "正在获取签到信息"},
statusMessage: "请获取定位后完成签到", messageType: "", signLoading: false, formLoading: false,
pointPopup: false, formPopup: false, markerLayer: null, circle: null, map: null
pointPopup: false, formPopup: false,
mapReady: false, mapLoading: false, locating: false, courseLoading: true,
locationMessage: "正在加载签到地点", currentPosition: null, distance: null
}
},
computed: {
// 本项目点位为GCJ-02[纬度,经度],不能使用参考工程的[经度,纬度]下标。
sitePosition() {
const p = this.row.courseLocationCoordinates
return Array.isArray(p) && p.length === 2 && p.every(v => v !== null && v !== "" && Number.isFinite(Number(v)))
&& Math.abs(Number(p[0])) <= 90 && Math.abs(Number(p[1])) <= 180
? {lat: Number(p[0]), lng: Number(p[1])} : null
},
signRadius() {
const r = this.row.radius == null ? 100 : Number(this.row.radius)
return Number.isFinite(r) && r > 0 ? r : 0
},
signDisabled() {
return this.courseLoading || this.mapLoading || this.locating || this.signLoading
|| !this.mapReady || !this.currentPosition || this.distance === null
|| this.distance > this.signRadius || this.extensionEnabled && this.signData.currentPointSigned === true
},
progressPercentage() {
if (!this.signData.signPointCount) return 0
return Math.min(100, Math.round((this.signData.signCount || 0) * 100 / this.signData.signPointCount))
}
},
methods: {
initMap() {
if (this.map) return
if (!this.row.courseLocationCoordinates || this.row.courseLocationCoordinates.length < 2) {
this.statusMessage = "当前点位未设置地图坐标"; this.messageType = "error"; return
}
const position = this.row.courseLocationCoordinates
const center = new TMap.LatLng(position[0], position[1])
this.map = new TMap.Map("mapContainer", {zoom: 16, center: center})
this.markerLayer = new TMap.MultiMarker({
map: this.map,
styles: {
target: new TMap.MarkerStyle({width: 36, height: 48}),
current: new TMap.MarkerStyle({src: "https://mapapi.qq.com/web/bundles/lbs-home/prod/assets/markerActive-NhH9NoBV.png", width: 36, height: 48})
},
geometries: [{id: "target", styleId: "target", position: center}]
})
this.circle = new TMap.MultiCircle({map: this.map, geometries: [{center: center, radius: this.row.radius || this.signData.signRadius || 100}]})
},
getUserLocation() {
return new Promise((resolve) => {
if (!navigator.geolocation) { this.$toast("浏览器不支持定位"); resolve(null); return }
navigator.geolocation.getCurrentPosition((position) => {
const coords = {lat: position.coords.latitude, lng: position.coords.longitude}
if (this.markerLayer) {
this.markerLayer.remove(["current"])
const converted = this.$coordinateUtil.wgs84ToGcj02(coords.lng, coords.lat)
this.markerLayer.add([{id: "current", styleId: "current", position: new TMap.LatLng(converted[0], converted[1])}])
/**
* 复用布局中的腾讯地图Key;定位组件仅在扫码页按需加载。
* 返回SDK就绪Promise,失败时移除脚本,允许重试并处理页面销毁。
*/
loadTencentSdk() {
const mapScript = Array.from(document.scripts).find(item => item.src.indexOf("map.qq.com/api/gljs") >= 0)
const key = mapScript ? new URL(mapScript.src).searchParams.get("key") : ""
if (!key) return Promise.reject(new Error("未找到腾讯地图Key配置"))
const load = (name, src, ready) => {
if (ready()) return Promise.resolve()
if (scanRuntime.scripts[name]) return scanRuntime.scripts[name].promise
const state = {script: document.createElement("script"), promise: null, cancel: null}
scanRuntime.scripts[name] = state
state.promise = new Promise((resolve, reject) => {
let finished = false
let timer
const finish = error => {
if (finished) return
finished = true
window.clearTimeout(timer)
state.script.onload = null
state.script.onerror = null
state.cancel = null
if (error) {
state.script.remove()
delete scanRuntime.scripts[name]
reject(error)
} else resolve()
}
resolve(coords)
}, () => { this.$toast("定位失败,请检查定位权限后重试"); resolve(null) }, {enableHighAccuracy: true, timeout: 8000})
state.cancel = () => finish(new Error("页面已关闭"))
state.script.src = src
state.script.async = true
if (scanRuntime.nonce) state.script.nonce = scanRuntime.nonce
state.script.onload = () => finish(ready() ? null : new Error("腾讯定位组件未正确加载"))
state.script.onerror = () => finish(new Error("腾讯地图服务加载失败,请检查网络"))
timer = window.setTimeout(() => finish(new Error("腾讯地图服务加载超时")), 15000)
document.head.appendChild(state.script)
})
return state.promise
}
return Promise.all([
load("map", mapScript.src, () => window.TMap && TMap.Map && TMap.MultiMarker && TMap.LatLngBounds),
load("location", "https://mapapi.qq.com/web/lbs/h5-components/geolocation/geolocation.min.js",
() => window.LBS && LBS.WebComponent && LBS.WebComponent.Geolocation)
]).then(() => key)
},
/** 地图标记使用静态文字区分地点与本人,同点位时标签分别显示在上方与下方。 */
markerStyle(label, color, below) {
const y = below ? 12 : 36
const top = below ? 25 : 1
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="80" height="50">'
+ '<rect x="2" y="' + top + '" width="76" height="22" rx="8" fill="' + color + '"/>'
+ '<text x="40" y="' + (top + 15) + '" text-anchor="middle" font-family="sans-serif" font-size="12" fill="white">'
+ label + '</text><circle cx="40" cy="' + y + '" r="6" fill="' + color + '" stroke="white" stroke-width="2"/></svg>'
return new TMap.MarkerStyle({width: 80, height: 50, anchor: {x: 40, y: y},
src: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg)})
},
/** 只初始化地图及定位预览,不提交签到。SDK失败可由重新加载按钮重试。 */
initMap() {
if (scanRuntime.destroyed || this.mapLoading) return Promise.resolve()
if (this.mapReady) return this.refreshPosition()
if (!this.sitePosition || !this.signRadius) {
this.$set(this, "locationMessage", "当前点位未设置有效坐标或签到半径,请联系管理员")
return Promise.resolve()
}
this.$set(this, "mapLoading", true)
return this.loadTencentSdk().then(key => new Promise(resolve => this.$nextTick(() => resolve(key))))
.then(key => {
if (scanRuntime.destroyed) return
const p = this.sitePosition
const center = new TMap.LatLng(p.lat, p.lng)
scanRuntime.map = new TMap.Map("mapContainer", {zoom: 16, center: center})
scanRuntime.markers = new TMap.MultiMarker({
map: scanRuntime.map,
styles: {target: this.markerStyle("签到地点", "#ee0a24", false),
current: this.markerStyle("我的位置", "#1989fa", true)},
geometries: [{id: "target", styleId: "target", position: center}]
})
scanRuntime.circle = new TMap.MultiCircle({map: scanRuntime.map,
geometries: [{id: "range", center: center, radius: this.signRadius}]})
scanRuntime.locator = new LBS.WebComponent.Geolocation({key: key, referer: "zhgh_zjipc-trainSignUp"})
this.$set(this, "mapReady", true)
return this.refreshPosition()
}).catch(error => {
if (scanRuntime.destroyed) return
this.destroyMap()
this.$set(this, "locationMessage", error.message || "地图加载失败,请重新加载")
}).finally(() => {
if (!scanRuntime.destroyed) this.$set(this, "mapLoading", false)
})
},
/** lat/lng均为GCJ-02,本方法返回到签到地点的米数,与后端采用相同地球半径。 */
distanceFromSite(coords) {
const site = this.sitePosition
const rad = value => value * Math.PI / 180
const dlat = rad(coords.lat - site.lat)
const dlng = rad(coords.lng - site.lng)
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)))
},
/** 同时展示签到圈和本人位置,不改变任何签到记录。 */
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)
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))))
},
/**
* 返回本次GCJ-02 {lat,lng};严格高精度模式不允许IP兜底,失败清除旧位置。
* 超时、权限、额度及坐标转换错误给出提示;晚到回调和离页回调不更新页面。
*/
getUserLocation() {
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, "currentPosition", null)
this.$set(this, "distance", null)
this.$set(this, "locationMessage", "正在获取您的当前位置")
scanRuntime.markers.remove(["current"])
const requestId = ++scanRuntime.requestId
return new Promise((resolve, reject) => {
let completed = false
let timer
const finish = (error, result) => {
if (completed) return
completed = true
window.clearTimeout(timer)
scanRuntime.cancelLocation = null
if (scanRuntime.destroyed || requestId !== scanRuntime.requestId) {
reject(new Error("页面已关闭")); return
}
if (error) { reject(error); return }
try {
if (!result || result.type === "ip" || !Number.isFinite(result.lat) || !Number.isFinite(result.lng)
|| Math.abs(result.lat) > 90 || Math.abs(result.lng) > 180) {
throw new Error("未获得有效的高精度位置,请重新定位")
}
const coords = {lat: result.lat, lng: result.lng}
const distance = this.distanceFromSite(coords)
if (!Number.isFinite(distance)) throw new Error("距离计算失败,请重新定位")
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) }
}
scanRuntime.cancelLocation = () => finish(new Error("页面已关闭"))
if (window.isSecureContext === false) {
finish(new Error("当前页面不是安全环境,请使用HTTPS地址定位")); return
}
timer = window.setTimeout(() => finish(new Error("定位超时,请检查网络和定位权限后重试")), 12000)
try {
scanRuntime.locator.getLocation(result => finish(null, result), detail => {
const status = Number(detail && detail.status)
const message = status === 10101 ? "未获得定位权限,请在手机设置中允许定位"
: status === 10002 || status === 10103 ? "定位超时,请重新定位"
: "腾讯定位失败,请检查权限、网络及地图服务配置后重试"
// 只记录错误诊断,不记录本人经纬度。
console.warn("腾讯签到定位失败", {status: status, message: detail && detail.message, logs: detail && detail.logs})
finish(new Error(message))
}, {timeout: 10000, highAccuracy: true, maximumAge: 0})
} catch (error) { finish(new Error("腾讯定位调用失败,请重新加载地图")) }
}).catch(error => {
if (!scanRuntime.destroyed) this.$set(this, "locationMessage", error.message)
throw error
}).finally(() => {
if (!scanRuntime.destroyed) this.$set(this, "locating", false)
})
},
/** 重新定位仅更新当前位置和距离;地图尚未就绪时重新加载SDK。 */
refreshPosition() {
if (scanRuntime.destroyed || this.locating || this.signLoading) return Promise.resolve()
if (!this.mapReady) return this.initMap()
return this.getUserLocation().catch(() => {})
},
/** 释放WebGL地图及相关图层,失败重试或离页均调用,不触碰业务数据。 */
destroyMap() {
if (scanRuntime.map) scanRuntime.map.destroy()
scanRuntime.map = null
scanRuntime.markers = null
scanRuntime.circle = null
scanRuntime.locator = null
if (!scanRuntime.destroyed) this.$set(this, "mapReady", false)
},
loadCourse() {
this.$axios.post("/platform/trainSignUp/mine/fetchCourse", {courseId: this.courseId}).then((resp) => {
if (resp.code !== 0) { this.legacyResult = resp; return }
this.row = resp.data
this.extensionEnabled = resp.data.signExtensionEnabled === true
if (this.extensionEnabled) this.loadSignData()
else { this.initMap(); this.onSign() }
})
if (!this.courseId) {
this.$set(this, "courseLoading", false)
this.$set(this, "locationMessage", "未获取到签到点位,请重新扫描二维码")
return
}
return this.$axios.post("/platform/trainSignUp/mine/fetchCourse", {courseId: this.courseId})
.then(resp => {
if (scanRuntime.destroyed) return
if (resp.code !== 0) {
this.$set(this, "legacyResult", resp)
this.$set(this, "locationMessage", resp.msg)
return
}
this.$set(this, "row", resp.data)
this.$set(this, "extensionEnabled", resp.data.signExtensionEnabled === true)
// 普通活动同样只预览,禁止进入页面就自动写入签到。
if (this.extensionEnabled) return this.loadSignData()
this.$set(this, "legacyResult", {code: 1, msg: "到达点位后,请点击确认签到"})
return this.initMap()
}).catch(() => {
if (!scanRuntime.destroyed) this.$set(this, "locationMessage", "获取签到信息失败,请重新进入页面")
}).finally(() => {
if (!scanRuntime.destroyed) this.$set(this, "courseLoading", false)
})
},
loadSignData() {
this.$axios.post("/platform/trainSignUp/mine/getSignData", {courseId: this.courseId}).then((resp) => {
if (resp.code !== 0) { this.statusMessage = resp.msg; this.messageType = "error"; return }
this.signData = resp.data
this.statusMessage = resp.data.handleMsg || "请完成当前点位签到"
this.messageType = resp.data.isReachTarget ? "success" : ""
this.initMap()
return this.$axios.post("/platform/trainSignUp/mine/getSignData", {courseId: this.courseId}).then((resp) => {
if (scanRuntime.destroyed) return
if (resp.code !== 0) {
this.$set(this, "statusMessage", resp.msg)
this.$set(this, "messageType", "error")
return
}
this.$set(this, "signData", resp.data)
this.$set(this, "statusMessage", resp.data.handleMsg || "请完成当前点位签到")
this.$set(this, "messageType", resp.data.isReachTarget ? "success" : "")
return this.initMap()
})
},
onSign() {
if (this.signLoading) return
this.signLoading = true
this.getUserLocation().then((coords) => {
if (!coords) return null
if (this.signDisabled || scanRuntime.destroyed) return
this.$set(this, "signLoading", true)
// 预览坐标不作为提交依据,点击后重新获取GCJ-02位置,并由后端再次校验范围。
return this.getUserLocation().then((coords) => {
if (scanRuntime.destroyed) return null
if (this.distance > this.signRadius) throw new Error("尚未进入签到范围,请到达地点后重试")
return this.$axios.post("/platform/trainSignUp/mine/drivingScan", {courseId: this.courseId, lat: coords.lat, lng: coords.lng})
}).then((resp) => {
if (!resp) return
if (!this.extensionEnabled) { this.legacyResult = resp; return }
if (resp.code !== 0) { this.statusMessage = resp.msg; this.messageType = "error"; return }
this.signData = resp.data
this.statusMessage = resp.data.message || resp.data.handleMsg || resp.msg
this.messageType = "success"
if (!resp || scanRuntime.destroyed) return
if (!this.extensionEnabled) { this.$set(this, "legacyResult", resp); return }
if (resp.code !== 0) {
this.$set(this, "statusMessage", resp.msg)
this.$set(this, "messageType", "error")
return
}
this.$set(this, "signData", resp.data)
this.$set(this, "statusMessage", resp.data.message || resp.data.handleMsg || resp.msg)
this.$set(this, "messageType", "success")
if (resp.data.needDynamicForm) this.openPopup("form")
}).finally(() => { this.signLoading = false })
}).catch(error => {
if (!scanRuntime.destroyed) this.$toast(error.msg || error.message || "签到失败,请重试")
}).finally(() => {
if (!scanRuntime.destroyed) this.$set(this, "signLoading", false)
})
},
openPopup(name) {
if (name === "points") this.pointPopup = true
if (name === "form") this.formPopup = true
if (name === "points") this.$set(this, "pointPopup", true)
if (name === "form") this.$set(this, "formPopup", true)
window.mobilePopupHistory.open(this.ownerId, name)
},
closePopup(name) { window.mobilePopupHistory.close(this.ownerId, name) },
closePopupFromHistory(name) {
if (name === "points") this.pointPopup = false
if (name === "form") this.formPopup = false
if (name === "points") this.$set(this, "pointPopup", false)
if (name === "form") this.$set(this, "formPopup", false)
},
onNavBack() {
if (window.mobilePopupHistory.hasOpen(this.ownerId)) window.history.back()
@@ -174,13 +398,13 @@ layout("/layouts/platform_h5.html"){
saveDynamicForm() {
if (this.formLoading) return
this.$refs.dynamicForm.validate().then(() => {
this.formLoading = true
this.$axios.post("/platform/trainSignUp/mine/saveDynamicForm", {
this.$set(this, "formLoading", true)
return this.$axios.post("/platform/trainSignUp/mine/saveDynamicForm", {
courseId: this.courseId, mobileColumnsValue: JSON.stringify(this.signData.dynamicColumnsData || [])
}).then((resp) => {
if (resp.code === 0) { this.$toast.success(resp.msg || "保存成功"); this.closePopup("form"); this.loadSignData() }
else this.$toast.fail(resp.msg)
}).finally(() => { this.formLoading = false })
}).finally(() => { if (!scanRuntime.destroyed) this.$set(this, "formLoading", false) })
}).catch(() => {})
},
goWelfare() {
@@ -192,7 +416,14 @@ layout("/layouts/platform_h5.html"){
window.mobilePopupHistory.register(this.ownerId, (name) => this.closePopupFromHistory(name))
this.loadCourse()
},
beforeDestroy() { window.mobilePopupHistory.unregister(this.ownerId) }
beforeDestroy() {
window.mobilePopupHistory.unregister(this.ownerId)
scanRuntime.destroyed = true
scanRuntime.requestId += 1
if (scanRuntime.cancelLocation) scanRuntime.cancelLocation()
Object.values(scanRuntime.scripts).forEach(state => { if (state.cancel) state.cancel() })
this.destroyMap()
}
})
</script>
<!--#