feat: rebuild pet app and backend around core care flows

This commit is contained in:
2026-10-01 13:11:30 +08:00
parent 51ead8f411
commit bfcf8788a4
321 changed files with 4918 additions and 25199 deletions
+165
View File
@@ -0,0 +1,165 @@
const { secure, toast } = require("../../utils/api");
Page({
data: {
pets: [],
selected: [],
walk: null,
points: [],
polyline: [],
latitude: 31.23,
longitude: 121.47,
duration: "00:00",
distance: "0.00",
share: false,
},
onShow() {
this.load();
this.clock = setInterval(() => this.tick(), 1000);
},
onHide() {
this.stopTimers();
},
onUnload() {
this.stopTimers();
},
stopTimers() {
clearInterval(this.clock);
clearInterval(this.tracker);
},
async load() {
try {
const [pets, walks] = await Promise.all([
secure("/pets"),
secure("/walks"),
]);
const selected = (wx.getStorageSync("walk_pet_ids") || []).filter((id) =>
pets.some((p) => p.id === id),
);
const walk =
walks.find((w) => w.status === "active" || w.status === "paused") ||
null;
this.setData({ pets, selected, walk });
if (walk) {
this.renderWalk(walk);
this.track();
}
} catch (e) {
toast(e);
}
},
pick(e) {
if (this.data.walk) return;
this.setData({ selected: e.detail.selected });
wx.setStorageSync("walk_pet_ids", e.detail.selected);
},
async start() {
if (!this.data.selected.length) {
wx.showToast({ title: "请选择同行的狗狗", icon: "none" });
return;
}
try {
const walk = await secure("/walks", "POST", {
pet_ids: this.data.selected,
});
this.setData({ walk });
this.track();
this.point();
} catch (e) {
toast(e);
}
},
track() {
clearInterval(this.tracker);
this.tracker = setInterval(() => this.point(), 15000);
},
async point() {
if (!this.data.walk || this.data.walk.status !== "active") return;
try {
const pos = await wx.getLocation({ type: "gcj02" });
const walk = await secure(
"/walks/" + this.data.walk.id + "/points",
"POST",
{ latitude: pos.latitude, longitude: pos.longitude },
);
this.renderWalk(walk);
} catch (e) {
console.warn(e);
}
},
renderWalk(walk) {
const pts = (walk.points || []).map((p) => ({
latitude: p.latitude,
longitude: p.longitude,
}));
const last = pts[pts.length - 1];
this.setData({
walk,
points: pts,
polyline:
pts.length > 1 ? [{ points: pts, color: "#268e6a", width: 5 }] : [],
latitude: last ? last.latitude : this.data.latitude,
longitude: last ? last.longitude : this.data.longitude,
distance: (walk.distance_meters / 1000).toFixed(2),
share: walk.share_enabled,
});
this.tick();
},
tick() {
const walk = this.data.walk;
if (!walk) return;
const end = walk.ended_at
? new Date(walk.ended_at)
: walk.status === "paused"
? new Date(walk.paused_at || Date.now())
: new Date();
const sec = Math.max(
0,
Math.floor((end - new Date(walk.started_at)) / 1000) -
(walk.paused_seconds || 0),
);
this.setData({
duration:
String(Math.floor(sec / 60)).padStart(2, "0") +
":" +
String(sec % 60).padStart(2, "0"),
});
},
async action(e) {
const kind = e.currentTarget.dataset.kind;
try {
const walk = await secure(
"/walks/" + this.data.walk.id + "/" + kind,
"POST",
);
this.renderWalk(walk);
if (kind === "finish") {
clearInterval(this.tracker);
wx.showToast({ title: "今天的快乐已记录", icon: "success" });
}
} catch (err) {
toast(err);
}
},
async shareChange(e) {
try {
const walk = await secure(
"/walks/" + this.data.walk.id + "/share",
"PUT",
{ enabled: e.detail.value },
);
this.renderWalk(walk);
} catch (err) {
toast(err);
}
},
onShareAppMessage() {
const walk = this.data.walk;
return {
title: "肉垫计划 · 看看我们遛到哪儿了 🐾",
path:
walk && walk.share_enabled
? "/pages/share/share?token=" + walk.share_token
: "/pages/home/home",
};
},
});