迁移嘉兴学院的签到定位页面移植-1
This commit is contained in:
+4
-1
@@ -779,7 +779,10 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
|
||||
Cnd.where("activityId", "=", activity.getId()).asc("orderNum"));
|
||||
List<NutMap> 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<String, Object> savedValues = new HashMap<>();
|
||||
List<TrainSignUpUser> signUsers = dao().query(TrainSignUpUser.class,
|
||||
|
||||
@@ -1,82 +1,437 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style scoped>
|
||||
.sign-page { min-height: 100vh; background: #f5f6f8; }
|
||||
.sign-map { width: 100%; height: 42vh; background: #e8edf2; }
|
||||
.sign-panel { padding: 18px 16px 28px; background: #fff; }
|
||||
.sign-title { margin: 0 0 6px; font-size: 20px; font-weight: 600; text-align: center; }
|
||||
.sign-location { color: #7d8490; font-size: 13px; text-align: center; }
|
||||
.sign-progress { margin: 20px 0 8px; display: flex; align-items: center; justify-content: space-between; }
|
||||
.sign-progress strong { color: #0878d1; font-size: 18px; }
|
||||
.sign-message { min-height: 24px; margin: 16px 0; color: #606773; text-align: center; }
|
||||
.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; }
|
||||
.point-state { color: #969799; }
|
||||
.point-state.signed { color: #07c160; }
|
||||
.form-popup { max-height: 62vh; overflow-y: auto; }
|
||||
.popup-footer { padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); }
|
||||
.legacy-result { min-height: 52vh; padding-top: 36px; background: #fff; }
|
||||
<style>
|
||||
/* 沿用点位签到组件的纵向布局:地图伸展,信息和操作保留自身高度,小屏可滚动。 */
|
||||
#app.brand-train-sign-page {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background: #f4f7fc;
|
||||
}
|
||||
|
||||
.brand-train-sign-page > .van-nav-bar__placeholder {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 仅本页沿用参考签到页的亮蓝主题,不改变其他业务页面的全局 Vant 配色。 */
|
||||
.brand-train-sign-page .van-nav-bar__text,
|
||||
.brand-train-sign-page .van-nav-bar .van-icon,
|
||||
.brand-train-sign-page .van-tag--primary {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.brand-train-sign-page .van-button--info {
|
||||
border-color: #1989fa;
|
||||
background: #1989fa;
|
||||
}
|
||||
|
||||
.brand-train-sign-page .van-button--info.van-button--plain {
|
||||
background: #fff;
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.brand-train-sign-content {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.brand-train-sign-map {
|
||||
/* 固定 SDK 容器定位,防止地图控件溢出到信息区和底部按钮。 */
|
||||
position: relative !important;
|
||||
z-index: 0;
|
||||
flex: 1 0 240px;
|
||||
width: 100%;
|
||||
min-height: 240px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.brand-train-sign-details {
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.brand-train-sign-title {
|
||||
color: #263548;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.brand-train-sign-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.brand-train-sign-signed {
|
||||
color: #07c160;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.brand-train-sign-signed .van-icon {
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.brand-train-sign-location-tools {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.brand-train-sign-location-tools .van-button {
|
||||
flex-shrink: 0;
|
||||
height: 32px;
|
||||
padding: 0 2px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.brand-train-sign-location-status {
|
||||
color: #78869a;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.brand-train-sign-location-status.is-in-range {
|
||||
color: #263548;
|
||||
}
|
||||
|
||||
.brand-train-sign-location-status.is-in-range::before {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 6px;
|
||||
border-radius: 50%;
|
||||
background: #07c160;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.brand-train-sign-text {
|
||||
margin-top: 6px;
|
||||
color: #78869a;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.brand-train-sign-text .van-icon {
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.brand-train-sign-progress {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-top: 14px;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
background: #f4f7fc;
|
||||
color: #78869a;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.brand-train-sign-progress-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.brand-train-sign-progress-heading {
|
||||
color: #263548;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.brand-train-sign-record-link {
|
||||
display: inline-flex;
|
||||
min-height: 32px;
|
||||
margin-left: auto;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #1989fa;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.brand-train-sign-count {
|
||||
margin: 0 3px;
|
||||
color: #1989fa;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.brand-train-sign-progress-track {
|
||||
/* 使用容器百分比宽度,避免旧组件缓存像素宽度后在屏幕尺寸变化时越出卡片。 */
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #dce3ee;
|
||||
}
|
||||
|
||||
.brand-train-sign-progress-fill {
|
||||
display: block;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
border-radius: inherit;
|
||||
background: #1989fa;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
.brand-train-sign-progress-message {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.brand-train-sign-map-warning {
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
.brand-train-sign-footer {
|
||||
/* 状态留在信息区,底栏仅容纳操作;双按钮平分宽度,单按钮居中且不铺满屏幕。 */
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 12px 24px;
|
||||
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #edf0f5;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.brand-train-sign-footer .van-button {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 40px;
|
||||
padding: 0 8px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.brand-train-sign-footer .van-button:only-child {
|
||||
flex: 0 1 180px;
|
||||
}
|
||||
|
||||
.brand-train-sign-points-popup {
|
||||
/* 从右侧滑入并覆盖整个视口;导航和底部提示保留高度,点位卡片区域独立滚动。 */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 100dvh;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.brand-train-sign-points-popup > .van-nav-bar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.brand-train-sign-points-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.brand-train-sign-point {
|
||||
margin-bottom: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e9edf4;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.brand-train-sign-point:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.brand-train-sign-point .brand-train-sign-title {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.brand-train-sign-point .brand-train-sign-text {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.brand-train-sign-point-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.brand-train-sign-point-header .brand-train-sign-title-row {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
gap: 4px 8px;
|
||||
}
|
||||
|
||||
.brand-train-sign-point-status {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 8px;
|
||||
border-radius: 8px;
|
||||
background: #fff7e9;
|
||||
color: #d98a13;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.brand-train-sign-point-status.is-signed {
|
||||
background: #e8fbf2;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.brand-train-sign-point-status .van-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.brand-train-sign-point-time {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #edf0f5;
|
||||
color: #78869a;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.brand-train-sign-point-time-value {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: #3b4b60;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.brand-train-sign-points-note {
|
||||
flex-shrink: 0;
|
||||
padding: 12px 16px;
|
||||
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #edf0f5;
|
||||
background: #fff;
|
||||
color: #78869a;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-popup { padding: 16px; overflow-y: auto; }
|
||||
.popup-footer { padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); background: #fff; }
|
||||
</style>
|
||||
|
||||
<div id="app" class="sign-page">
|
||||
<div id="app" class="brand-train-sign-page" v-cloak>
|
||||
<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>
|
||||
<div class="sign-location">{{signData.courseName}}<span v-if="signData.courseLocation"> · {{signData.courseLocation}}</span></div>
|
||||
<div class="sign-progress"><span>签到进度</span><strong>{{signData.signCount || 0}} / {{signData.signPointCount || 0}}</strong></div>
|
||||
<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="signDisabled" @click="onSign">
|
||||
{{signData.currentPointSigned ? '当前点位已签到' : '立即签到'}}
|
||||
</van-button>
|
||||
<van-button plain type="info" block @click="openPopup('points')">查看全部签到点位</van-button>
|
||||
<van-button v-if="signData.needDynamicForm" type="info" block @click="openPopup('form')">填写达标信息</van-button>
|
||||
<van-button v-if="signData.needWelfareSelect" type="warning" block @click="goWelfare">选择礼物或福利</van-button>
|
||||
<div class="brand-train-sign-content">
|
||||
<div id="mapContainer" class="brand-train-sign-map"></div>
|
||||
<section class="brand-train-sign-details">
|
||||
<div class="brand-train-sign-location-tools">
|
||||
<span class="brand-train-sign-location-status" role="status"
|
||||
:class="{'brand-train-sign-map-warning': locationFailed || distance !== null && !isInRange,
|
||||
'is-in-range': !locationFailed && !locating && isInRange}">{{locationMessage}}</span>
|
||||
<van-button size="small" plain type="info" icon="aim"
|
||||
:loading="locating || mapLoading" :disabled="signLoading || courseLoading"
|
||||
@click="refreshPosition">{{mapReady ? '重新定位' : '重新加载地图'}}</van-button>
|
||||
</div>
|
||||
<div class="brand-train-sign-title-row">
|
||||
<div class="brand-train-sign-title">{{signData.courseName || row.courseName || '活动签到'}}</div>
|
||||
<span v-if="currentPointSigned" class="brand-train-sign-signed" role="status">
|
||||
<van-icon name="checked"></van-icon>当前点位已签到
|
||||
</span>
|
||||
</div>
|
||||
<div class="brand-train-sign-text">签到范围:{{signRadius}} 米</div>
|
||||
<div v-if="distance !== null" class="brand-train-sign-text">距签到地点约 {{Math.ceil(distance)}} 米</div>
|
||||
<div v-if="extensionEnabled" class="brand-train-sign-progress">
|
||||
<div class="brand-train-sign-progress-header">
|
||||
<span class="brand-train-sign-progress-heading">活动签到进度</span>
|
||||
<van-tag v-if="signData.isReachTarget" plain type="success">活动已达标</van-tag>
|
||||
<button type="button" class="brand-train-sign-record-link" @click="openPopup('points')">
|
||||
点位打卡情况<van-icon name="arrow"></van-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div>已打卡 <strong class="brand-train-sign-count">{{signData.signCount || 0}}</strong> 个 / 达标需 {{signData.signPointCount || 0}} 个</div>
|
||||
<div class="brand-train-sign-progress-track" role="progressbar" aria-label="活动签到进度"
|
||||
aria-valuemin="0" aria-valuemax="100" :aria-valuenow="progressPercentage">
|
||||
<span class="brand-train-sign-progress-fill" :style="{width: progressPercentage + '%'}"></span>
|
||||
</div>
|
||||
<div class="brand-train-sign-progress-message" role="status"
|
||||
:class="{'brand-train-sign-map-warning': messageType === 'error'}">{{progressMessage}}</div>
|
||||
</div>
|
||||
<div v-else class="brand-train-sign-text">{{legacyResult.msg}}</div>
|
||||
</section>
|
||||
</template>
|
||||
<div v-else class="weui-msg legacy-result">
|
||||
<div class="weui-msg__icon-area">
|
||||
<i v-if="legacyResult.code !== 0" class="weui-icon-warn weui-icon_msg"></i>
|
||||
<i v-else class="weui-icon-success weui-icon_msg"></i>
|
||||
<div v-if="showSignButton || showDynamicFormButton || signData.needWelfareSelect" class="brand-train-sign-footer">
|
||||
<van-button v-if="showDynamicFormButton" :plain="showSignButton" type="info"
|
||||
:disabled="signLoading || formLoading" @click="openPopup('form')">填写详细信息</van-button>
|
||||
<van-button v-if="signData.needWelfareSelect" :plain="showSignButton" type="info"
|
||||
:disabled="signLoading || formLoading" @click="goWelfare">去选择福利</van-button>
|
||||
<van-button v-if="showSignButton" type="info" loading-text="签到中..."
|
||||
:loading="signLoading" :disabled="signDisabled" @click="onSign">确认签到</van-button>
|
||||
</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">
|
||||
<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 :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">
|
||||
<div>{{point.courseName}}<small>{{point.courseLocation || '未填写地点'}}</small></div>
|
||||
<span class="point-state" :class="{signed: point.signed}">{{point.signed ? '已签到' : '未签到'}}</span>
|
||||
<van-popup :value="pointPopup" class="brand-train-sign-points-popup" position="right"
|
||||
@input="visible => { if (!visible) closePopup('points') }" :close-on-click-overlay="false">
|
||||
<van-nav-bar title="点位打卡情况" left-text="返回" left-arrow @click-left="closePopup('points')"></van-nav-bar>
|
||||
<div ref="signPointsList" class="brand-train-sign-points-list">
|
||||
<van-empty v-if="!displaySignPoints.length" description="暂无点位信息"></van-empty>
|
||||
<div v-for="point in displaySignPoints" :key="point.courseId" class="brand-train-sign-point">
|
||||
<div class="brand-train-sign-point-header">
|
||||
<div class="brand-train-sign-title-row">
|
||||
<div class="brand-train-sign-title">{{point.courseName || '点位名称未填写'}}</div>
|
||||
<van-tag v-if="point.courseId === courseId" plain type="primary">当前点位</van-tag>
|
||||
</div>
|
||||
<span class="brand-train-sign-point-status" :class="{'is-signed': point.signed === true}">
|
||||
<van-icon :name="point.signed === true ? 'checked' : 'clock-o'"></van-icon>{{point.signed === true ? '已打卡' : '未打卡'}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="brand-train-sign-text"><van-icon name="location-o"></van-icon>地点:{{point.courseLocation || '未填写地点名称'}}</div>
|
||||
<div v-if="point.signed === true" class="brand-train-sign-point-time">
|
||||
签到时间
|
||||
<span class="brand-train-sign-point-time-value">{{formatAttendTime(point.attendTime)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="brand-train-sign-points-note">
|
||||
{{signData.isReachTarget ? '活动已达标,仍可继续打卡其他点位' : '完成 ' + (signData.signPointCount || 0) + ' 个不同点位打卡即可达标'}}
|
||||
</div>
|
||||
</van-popup>
|
||||
<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>
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user