This commit is contained in:
Your Name
2026-08-11 17:41:36 +08:00
parent cfe4c82c90
commit 03fe4ddf9d
18771 changed files with 3617239 additions and 0 deletions
@@ -0,0 +1,519 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const training_hooks_useMetronome = require("../hooks/useMetronome.js");
if (!Math) {
CrushCanvas();
}
const CrushCanvas = () => "./components/crush-canvas.js";
const TUTORIAL_VIDEO_URL = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/video/20260528/20260528173911bf6f93386.mp4";
const TUTORIAL_POSTER_URL = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/images/20260529/202605291030231aed12802.jpg";
const BGM_URL = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260528/20260528093709c669f4659.mp3";
const _sfc_defineComponent = common_vendor.defineComponent({
__name: "grip-ring",
setup(__props) {
const GRIP_PRESETS = [
{ id: "light", bpm: 60, met: 2.5, label: "轻度", desc: "热身 · 恢复" },
{ id: "medium", bpm: 80, met: 3.5, label: "中度", desc: "日常 · 锻炼" },
{ id: "heavy", bpm: 100, met: 4.5, label: "重度", desc: "强化 · 挑战" }
];
const TARGET_OPTIONS = [
{ minutes: 0, label: "自由" },
{ minutes: 5, label: "5 分钟" },
{ minutes: 10, label: "10 分钟" },
{ minutes: 15, label: "15 分钟" }
];
const REWARDS = [
{
emoji: "🥚",
title: "捏碎鸡蛋",
desc: "握力不错!",
itemType: "egg",
sound: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260528/202605280937077bad76716.mp3",
minReps: 8,
maxReps: 12
},
{
emoji: "🌰",
title: "捏碎核桃",
desc: "力量惊人!",
itemType: "walnut",
sound: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260528/20260528093707b021f5794.mp3",
minReps: 15,
maxReps: 20
},
{
emoji: "🥫",
title: "捏扁易拉罐",
desc: "太强了!",
itemType: "can",
sound: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260528/20260528093707ebd4d5733.mp3",
minReps: 25,
maxReps: 30
},
{
emoji: "🎈",
title: "捏爆气球",
desc: "完美!",
itemType: "balloon",
sound: "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260528/20260528093707016f07427.mp3",
minReps: 40,
maxReps: 50
}
];
const FOOD_SUGAR_TABLE = [
{ emoji: "🍬", name: "糖果", sugar: 95, unit: "颗", weight: 5 },
// 1颗糖果约5g
{ emoji: "🍪", name: "饼干", sugar: 65, unit: "块", weight: 10 },
// 1块饼干约10g
{ emoji: "🍫", name: "巧克力", sugar: 51.5, unit: "块", weight: 50 },
// 1块巧克力约50g
{ emoji: "🍎", name: "苹果", sugar: 10.3, unit: "个", weight: 200 },
// 1个苹果约200g
{ emoji: "🍌", name: "香蕉", sugar: 12.2, unit: "根", weight: 120 },
// 1根香蕉约120g
{ emoji: "🍚", name: "米饭", sugar: 25.9, unit: "碗", weight: 150 },
// 1碗米饭约150g
{ emoji: "🥤", name: "可乐", sugar: 10.6, unit: "罐", weight: 330 }
// 1罐可乐330ml
];
const currentPreset = common_vendor.ref("medium");
const targetMinutes = common_vendor.ref(0);
const crushSignal = common_vendor.ref(null);
const showRewardPopup = common_vendor.ref(false);
const showSummary = common_vendor.ref(false);
const currentReward = common_vendor.ref(REWARDS[0]);
const crushAudioCtx = common_vendor.ref(null);
const bgmAudioCtx = common_vendor.ref(null);
const bgmEnabled = common_vendor.ref(true);
const totalReps = common_vendor.ref(0);
const elapsedSeconds = common_vendor.ref(0);
const totalSugar = common_vendor.ref(0);
const beatCount = common_vendor.ref(0);
const rewardIndex = common_vendor.ref(0);
const nextRewardAt = common_vendor.ref(0);
const startTime = common_vendor.ref(0);
const timerInterval = common_vendor.ref(null);
const preset = common_vendor.computed(() => GRIP_PRESETS.find((p) => p.id === currentPreset.value) || GRIP_PRESETS[1]);
const currentBpm = common_vendor.computed(() => preset.value.bpm);
const currentMet = common_vendor.computed(() => preset.value.met);
const targetExpanded = common_vendor.ref(false);
const currentTargetLabel = common_vendor.computed(() => {
const opt = TARGET_OPTIONS.find((t) => t.minutes === targetMinutes.value);
return opt ? opt.label : "自由";
});
const benefitExpanded = common_vendor.ref(false);
const TRAINING_BENEFIT = {
muscles: ["前臂屈肌", "手部小肌群", "握力"],
effect: "增强握力与前臂肌肉耐力,促进手部血液循环,缓解手指与腕部僵硬,适合日常碎片化锻炼及手部功能康复。"
};
const metronome = training_hooks_useMetronome.useMetronome({
initialBpm: currentBpm.value,
accentEvery: 2,
silent: true,
// 移除木鱼节奏音,仅保留节拍驱动(视觉缩放/次数统计)
onBeat
});
const { isPlaying } = metronome;
common_vendor.watch(isPlaying, (playing) => {
const videoCtx = common_vendor.index.createVideoContext("gripDemoVideo");
if (!videoCtx)
return;
if (playing) {
videoCtx.play();
} else {
videoCtx.pause();
}
});
const intervalMs = common_vendor.computed(() => 6e4 / currentBpm.value);
const hasStats = common_vendor.computed(() => isPlaying.value || totalReps.value > 0);
const isPaused = common_vendor.computed(() => !isPlaying.value && totalReps.value > 0 && !showSummary.value);
const remainingSeconds = common_vendor.computed(() => {
if (targetMinutes.value === 0)
return null;
const total = targetMinutes.value * 60;
return Math.max(0, total - elapsedSeconds.value);
});
const centerHintText = common_vendor.computed(() => isPaused.value ? "点击继续" : "点击开始");
const statusText = common_vendor.computed(() => {
if (isPlaying.value)
return "训练中";
if (isPaused.value)
return "已暂停";
return "待开始";
});
const shareTitle = common_vendor.computed(() => {
if (totalReps.value === 0)
return "握力环训练 · 一起练起来";
return `刚刚完成 ${totalReps.value} 次握力训练,约消耗 ${totalSugar.value.toFixed(1)}g 糖分 💪`;
});
const rootStyle = common_vendor.computed(() => ({
"--beat-duration": `${intervalMs.value}ms`
}));
const sugarComparisons = common_vendor.computed(() => {
if (totalSugar.value === 0)
return [];
const results = [];
for (const food of FOOD_SUGAR_TABLE) {
const foodSugar = food.sugar * food.weight / 100;
const count = totalSugar.value / foodSugar;
if (count >= 0.1 && count <= 50) {
results.push({
emoji: food.emoji,
name: food.name,
count: count >= 10 ? Math.round(count).toString() : count.toFixed(1),
unit: food.unit
});
}
}
return results.slice(0, 3);
});
function onBeat() {
beatCount.value++;
if (beatCount.value % 2 === 0) {
totalReps.value++;
if (totalReps.value >= nextRewardAt.value && rewardIndex.value < REWARDS.length) {
triggerReward();
}
}
}
function calculateSugar(seconds) {
const hours = seconds / 3600;
const weight = 60;
const calories = currentMet.value * weight * hours;
return calories / 4;
}
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, "0")}`;
}
function triggerReward() {
const reward = REWARDS[rewardIndex.value];
currentReward.value = reward;
crushSignal.value = { type: reward.itemType, nonce: Date.now() };
playCrushSound(reward.sound);
triggerHaptic();
rewardIndex.value++;
if (rewardIndex.value < REWARDS.length) {
const nextReward = REWARDS[rewardIndex.value];
const range = nextReward.maxReps - nextReward.minReps;
nextRewardAt.value = totalReps.value + nextReward.minReps + Math.floor(Math.random() * (range + 1));
}
setTimeout(() => {
showRewardPopup.value = true;
}, 1200);
}
function closeRewardPopup() {
showRewardPopup.value = false;
}
function triggerHaptic() {
const buzz = (type) => {
common_vendor.index.vibrateShort({
type,
fail: () => {
try {
common_vendor.index.vibrateShort({});
} catch (_) {
}
}
});
};
buzz("heavy");
setTimeout(() => buzz("medium"), 120);
}
function playCrushSound(sound) {
if (!sound)
return;
if (crushAudioCtx.value) {
try {
crushAudioCtx.value.destroy();
} catch (_) {
}
}
crushAudioCtx.value = common_vendor.index.createInnerAudioContext();
crushAudioCtx.value.src = sound;
crushAudioCtx.value.obeyMuteSwitch = false;
crushAudioCtx.value.volume = 0.6;
try {
crushAudioCtx.value.play();
} catch (_) {
}
}
function startBgm() {
if (!bgmEnabled.value)
return;
if (bgmAudioCtx.value) {
try {
bgmAudioCtx.value.play();
} catch (_) {
}
return;
}
bgmAudioCtx.value = common_vendor.index.createInnerAudioContext();
bgmAudioCtx.value.src = BGM_URL;
bgmAudioCtx.value.loop = true;
bgmAudioCtx.value.obeyMuteSwitch = false;
bgmAudioCtx.value.volume = 0.4;
try {
bgmAudioCtx.value.play();
} catch (_) {
}
}
function stopBgm() {
if (bgmAudioCtx.value) {
try {
bgmAudioCtx.value.pause();
} catch (_) {
}
}
}
function toggleBgm() {
bgmEnabled.value = !bgmEnabled.value;
if (bgmEnabled.value && isPlaying.value) {
startBgm();
} else {
stopBgm();
}
}
function onPresetTap(id) {
if (isPlaying.value)
return;
currentPreset.value = id;
metronome.setBpm(preset.value.bpm);
}
function onTargetTap(minutes) {
if (isPlaying.value)
return;
targetMinutes.value = minutes;
}
function onTogglePlay() {
if (isPlaying.value) {
pauseTraining();
} else {
if (totalReps.value === 0) {
resetStats();
}
startTrainingInternal();
}
}
function startTrainingInternal() {
metronome.start();
startBgm();
startTimer();
common_vendor.index.setKeepScreenOn({ keepScreenOn: true });
}
function pauseTraining() {
metronome.stop();
stopBgm();
stopTimer();
common_vendor.index.setKeepScreenOn({ keepScreenOn: false });
}
function onEndTraining() {
if (totalReps.value === 0)
return;
pauseTraining();
showSummary.value = true;
}
function dismissSummary() {
showSummary.value = false;
resetStats();
}
function onFinish() {
showSummary.value = false;
resetStats();
}
function onRestart() {
showSummary.value = false;
resetStats();
startTrainingInternal();
}
function resetStats() {
totalReps.value = 0;
elapsedSeconds.value = 0;
totalSugar.value = 0;
beatCount.value = 0;
rewardIndex.value = 0;
startTime.value = Date.now();
const firstReward = REWARDS[0];
const range = firstReward.maxReps - firstReward.minReps;
nextRewardAt.value = firstReward.minReps + Math.floor(Math.random() * (range + 1));
}
function startTimer() {
if (timerInterval.value)
return;
startTime.value = Date.now() - elapsedSeconds.value * 1e3;
timerInterval.value = setInterval(() => {
elapsedSeconds.value = Math.floor((Date.now() - startTime.value) / 1e3);
totalSugar.value = calculateSugar(elapsedSeconds.value);
if (targetMinutes.value > 0 && elapsedSeconds.value >= targetMinutes.value * 60) {
onEndTraining();
}
}, 1e3);
}
function stopTimer() {
if (timerInterval.value) {
clearInterval(timerInterval.value);
timerInterval.value = null;
}
}
common_vendor.onShow(() => {
metronome.preload();
});
common_vendor.onShareAppMessage(() => ({
title: shareTitle.value,
path: "/training/pages/grip-ring",
imageUrl: TUTORIAL_POSTER_URL
}));
common_vendor.onShareTimeline(() => ({
title: shareTitle.value,
imageUrl: TUTORIAL_POSTER_URL
}));
function cleanupAllMedia() {
var _a;
try {
metronome.stop();
} catch (_) {
}
stopTimer();
common_vendor.index.setKeepScreenOn({ keepScreenOn: false });
try {
(_a = common_vendor.index.createVideoContext("gripDemoVideo")) == null ? void 0 : _a.pause();
} catch (_) {
}
if (bgmAudioCtx.value) {
try {
bgmAudioCtx.value.stop();
} catch (_) {
}
try {
bgmAudioCtx.value.destroy();
} catch (_) {
}
bgmAudioCtx.value = null;
}
if (crushAudioCtx.value) {
try {
crushAudioCtx.value.stop();
} catch (_) {
}
try {
crushAudioCtx.value.destroy();
} catch (_) {
}
crushAudioCtx.value = null;
}
}
common_vendor.onHide(cleanupAllMedia);
common_vendor.onUnload(cleanupAllMedia);
return (_ctx, _cache) => {
return common_vendor.e({
a: common_vendor.t(statusText.value),
b: common_vendor.unref(isPlaying) ? 1 : "",
c: isPaused.value ? 1 : "",
d: common_vendor.p({
["crush-signal"]: crushSignal.value
}),
e: TUTORIAL_VIDEO_URL,
f: TUTORIAL_POSTER_URL,
g: !common_vendor.unref(isPlaying)
}, !common_vendor.unref(isPlaying) ? {
h: common_vendor.t(centerHintText.value)
} : {
i: common_vendor.t(currentBpm.value)
}, {
j: common_vendor.o(onTogglePlay, "9b"),
k: common_vendor.unref(isPlaying) ? 1 : "",
l: common_vendor.t(bgmEnabled.value ? "🎵" : "🔇"),
m: common_vendor.o(toggleBgm, "6e"),
n: remainingSeconds.value !== null && hasStats.value
}, remainingSeconds.value !== null && hasStats.value ? {
o: common_vendor.t(formatTime(remainingSeconds.value))
} : {}, {
p: common_vendor.unref(isPlaying)
}, common_vendor.unref(isPlaying) ? {} : {}, {
q: common_vendor.f(GRIP_PRESETS, (p, k0, i0) => {
return {
a: common_vendor.t(p.label),
b: common_vendor.t(p.bpm),
c: p.id,
d: currentPreset.value === p.id ? 1 : "",
e: common_vendor.o(($event) => onPresetTap(p.id), p.id)
};
}),
r: common_vendor.unref(isPlaying) ? 1 : "",
s: common_vendor.t(currentTargetLabel.value),
t: common_vendor.t(targetExpanded.value ? "▲" : "▼"),
v: common_vendor.o(($event) => targetExpanded.value = !targetExpanded.value, "16"),
w: targetExpanded.value
}, targetExpanded.value ? {
x: common_vendor.f(TARGET_OPTIONS, (t, k0, i0) => {
return {
a: common_vendor.t(t.label),
b: t.minutes,
c: targetMinutes.value === t.minutes ? 1 : "",
d: common_vendor.o(($event) => onTargetTap(t.minutes), t.minutes)
};
}),
y: common_vendor.unref(isPlaying) ? 1 : ""
} : {}, {
z: common_vendor.t(benefitExpanded.value ? "收起 ▲" : "展开 ▼"),
A: common_vendor.o(($event) => benefitExpanded.value = !benefitExpanded.value, "ee"),
B: benefitExpanded.value
}, benefitExpanded.value ? {
C: common_vendor.f(TRAINING_BENEFIT.muscles, (m, k0, i0) => {
return {
a: common_vendor.t(m),
b: m
};
}),
D: common_vendor.t(TRAINING_BENEFIT.effect)
} : {}, {
E: isPaused.value
}, isPaused.value ? {
F: common_vendor.o(onTogglePlay, "8a"),
G: common_vendor.o(onEndTraining, "d0")
} : {}, {
H: common_vendor.unref(isPlaying) ? 1 : "",
I: isPaused.value ? 1 : "",
J: common_vendor.s(rootStyle.value),
K: showRewardPopup.value
}, showRewardPopup.value ? {
L: common_vendor.t(currentReward.value.emoji),
M: common_vendor.t(currentReward.value.title),
N: common_vendor.t(currentReward.value.desc),
O: common_vendor.t(totalReps.value),
P: common_vendor.o(closeRewardPopup, "ae"),
Q: common_vendor.o(() => {
}, "5b"),
R: common_vendor.o(closeRewardPopup, "1a")
} : {}, {
S: showSummary.value
}, showSummary.value ? common_vendor.e({
T: common_vendor.t(totalReps.value),
U: common_vendor.t(formatTime(elapsedSeconds.value)),
V: common_vendor.t(totalSugar.value.toFixed(1)),
W: sugarComparisons.value.length > 0
}, sugarComparisons.value.length > 0 ? {
X: common_vendor.f(sugarComparisons.value, (food, idx, i0) => {
return {
a: common_vendor.t(food.emoji),
b: common_vendor.t(food.count),
c: common_vendor.t(food.unit),
d: common_vendor.t(food.name),
e: idx
};
})
} : {}, {
Y: common_vendor.o(onRestart, "78"),
Z: common_vendor.o(onFinish, "a1"),
aa: common_vendor.o(() => {
}, "c4"),
ab: common_vendor.o(dismissSummary, "77")
}) : {});
};
}
});
_sfc_defineComponent.__runtimeHooks = 6;
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_defineComponent, [["__scopeId", "data-v-cebdebdc"]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/training/pages/grip-ring.js.map