Files
Pet3/miniprogram/pages/walking/walking.js
2026-06-25 18:24:29 +08:00

227 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// pages/walking/walking — 2/2b 遛狗中(进行/暂停同页)
// 计时基于时间戳差值切后台不丢R5GPS 距离可选累计、按 stride 换算狗步R3/R6
const { call } = require('../../utils/cloud.js');
const store = require('../../utils/store.js');
const { formatDuration } = require('../../utils/format.js');
const { BASE_STRIDE, dogStepsFromMeters } = require('../../utils/dogStep.js');
// 两点球面距离(米)
function haversine(lat1, lon1, lat2, lon2) {
const R = 6371000;
const toRad = (d) => (d * Math.PI) / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
return 2 * R * Math.asin(Math.sqrt(a));
}
Page({
data: {
status: 'idle', // idle | selecting | walking | paused
timeText: '00:00',
pooped: false,
dogs: [],
selectedIds: [],
selectedMap: {}, // id→true供 WXML 判断选中WXML 表达式不能调用 indexOf
activeDogNames: '',
showEndConfirm: false,
gps: 'off', // off | on | denied
distanceText: '—',
stepsText: '—',
},
_timer: null,
_locOn: false,
_locCb: null,
_lastPoint: null,
_stride: 0.35,
onLoad() {
const dogs = store.getState().dogs || [];
this.setData({ dogs });
const session = store.getWalkSession();
if (session) { this.resumeFrom(session); return; }
if (dogs.length === 0) {
wx.showToast({ title: '先添加一只狗狗', icon: 'none' });
setTimeout(() => wx.navigateBack(), 1200);
return;
}
if (dogs.length === 1) this.startSession([dogs[0]._id]);
else {
const def = store.getState().currentDogId || dogs[0]._id;
this.setData({ status: 'selecting', selectedIds: [def], selectedMap: this.buildSelectMap([def]) });
}
},
onUnload() { this.clearTimer(); this.stopLocation(); },
// ===== 多狗选择 =====
// WXML 表达式不支持方法调用(如 selectedIds.indexOf用 id→true 映射给视图层判断选中
buildSelectMap(ids) {
const map = {};
ids.forEach((id) => { map[id] = true; });
return map;
},
toggleSelect(e) {
const id = e.currentTarget.dataset.id;
const ids = this.data.selectedIds.slice();
const i = ids.indexOf(id);
if (i >= 0) ids.splice(i, 1); else ids.push(id);
this.setData({ selectedIds: ids, selectedMap: this.buildSelectMap(ids) });
},
confirmSelect() {
if (!this.data.selectedIds.length) return wx.showToast({ title: '至少选一只', icon: 'none' });
this.startSession(this.data.selectedIds);
},
// ===== 会话生命周期 =====
startSession(dogIds) {
const session = { dogIds, startAt: Date.now(), pausedTotal: 0, pausedAt: null, pooped: false, meters: 0, status: 'walking' };
store.setWalkSession(session);
store.set({ currentDogId: dogIds[0] });
this.setActiveNames(dogIds);
this.setData({ status: 'walking', pooped: false });
this.startTimer();
this.startLocation();
},
resumeFrom(session) {
this.setActiveNames(session.dogIds);
this.setData({ status: session.status, pooped: session.pooped });
if (session.status === 'walking') { this.startTimer(); this.startLocation(); }
else this.renderTime();
this.refreshGps();
},
setActiveNames(dogIds) {
const map = {};
(this.data.dogs || []).forEach((d) => { map[d._id] = d.name; });
this.setData({ activeDogNames: dogIds.map((id) => map[id] || '狗狗').join('、') });
},
// ===== 计时(时间戳差值)=====
elapsedSec() {
const s = store.getWalkSession();
if (!s) return 0;
const end = s.status === 'paused' ? s.pausedAt : Date.now();
return Math.max(0, Math.floor((end - s.startAt - s.pausedTotal) / 1000));
},
renderTime() { this.setData({ timeText: formatDuration(this.elapsedSec()) }); },
startTimer() { this.clearTimer(); this.renderTime(); this._timer = setInterval(() => this.renderTime(), 1000); },
clearTimer() { if (this._timer) { clearInterval(this._timer); this._timer = null; } },
// ===== GPS 距离累计可选增强R6=====
startLocation() {
if (this._locOn) return;
const s = store.getWalkSession();
const primary = (this.data.dogs || []).find((d) => s && d._id === s.dogIds[0]);
this._stride = (primary && (primary.stride || BASE_STRIDE[primary.sizeLevel])) || 0.35;
this._lastPoint = null;
this._locCb = (res) => this.onLoc(res);
wx.startLocationUpdate({
success: () => {
this._locOn = true;
wx.onLocationChange(this._locCb);
this.setData({ gps: 'on' });
this.refreshGps();
},
fail: () => { this.setData({ gps: 'denied' }); }, // 未授权 → 降级,只保留时长
});
},
onLoc(res) {
const s = store.getWalkSession();
if (!s || s.status !== 'walking') { this._lastPoint = null; return; } // 暂停不累计
if (res.accuracy && res.accuracy > 50) return; // 精度差丢弃
const p = { lat: res.latitude, lon: res.longitude };
if (this._lastPoint) {
const seg = haversine(this._lastPoint.lat, this._lastPoint.lon, p.lat, p.lon);
if (seg >= 2 && seg <= 50) { // 过滤静止抖动(<2m)与跳点(>50m)
s.meters = (s.meters || 0) + seg;
store.setWalkSession(s);
this.refreshGps();
}
}
this._lastPoint = p;
},
refreshGps() {
if (this.data.gps !== 'on') return;
const s = store.getWalkSession();
const m = (s && s.meters) || 0;
this.setData({
distanceText: (m / 1000).toFixed(2),
stepsText: String(dogStepsFromMeters(m, this._stride) || 0),
});
},
stopLocation() {
if (this._locOn) {
wx.offLocationChange(this._locCb);
wx.stopLocationUpdate();
this._locOn = false;
}
},
// ===== 便便打卡 =====
togglePoop() {
const s = store.getWalkSession();
if (!s) return;
s.pooped = !s.pooped;
store.setWalkSession(s);
this.setData({ pooped: s.pooped });
},
// ===== 暂停 / 继续 =====
pause() {
const s = store.getWalkSession();
if (!s || s.status !== 'walking') return;
s.status = 'paused';
s.pausedAt = Date.now();
store.setWalkSession(s);
this.clearTimer();
this._lastPoint = null;
this.setData({ status: 'paused' });
this.renderTime();
},
resume() {
const s = store.getWalkSession();
if (!s || s.status !== 'paused') return;
s.pausedTotal += Date.now() - s.pausedAt;
s.pausedAt = null;
s.status = 'walking';
store.setWalkSession(s);
this.setData({ status: 'walking' });
this.startTimer();
},
// ===== 结束 =====
askEnd() { this.setData({ showEndConfirm: true }); },
cancelEnd() { this.setData({ showEndConfirm: false }); },
async confirmEnd() {
const s = store.getWalkSession();
if (!s) return;
this.clearTimer();
this.stopLocation();
const duration = this.elapsedSec();
const endAt = Date.now();
const distanceMeters = this.data.gps === 'on' ? Math.round(s.meters || 0) : null;
this.setData({ showEndConfirm: false });
const res = await call('walkSave', {
action: 'finish',
dogIds: s.dogIds,
startAt: s.startAt,
endAt,
duration,
distanceMeters, // 米;未授权定位时为 nullR6
pooped: s.pooped,
}, { loading: true, loadingText: '生成成果卡' });
if (!res.ok) return;
store.setWalkSession(null);
getApp().globalData.lastSummary = res.data;
wx.redirectTo({ url: '/pages/summary/summary' });
},
});