Files
2026-08-11 17:41:36 +08:00

3550 lines
128 KiB
JavaScript
Raw Permalink 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.
"use strict";
const common_vendor = require("../../common/vendor.js");
const tongji_utils_treeLevels = require("../utils/treeLevels.js");
const tongji_utils_treeSpecies = require("../utils/treeSpecies.js");
const tongji_composables_useDietAi = require("../composables/useDietAi.js");
const tongji_composables_useSpeechToText = require("../composables/useSpeechToText.js");
const tongji_utils_voiceParse = require("../utils/voiceParse.js");
if (!Math) {
(CelebrateBurst + TongjiIcon + VoiceSpeakMascot + SugarTreeGraphic)();
}
const SugarTreeGraphic = () => "../components/SugarTreeGraphic.js";
const TongjiIcon = () => "../components/TongjiIcon.js";
const CelebrateBurst = () => "../components/CelebrateBurst.js";
const VoiceSpeakMascot = () => "../components/VoiceSpeakMascot.js";
const BLOOD_LOW_THRESHOLD = 3.9;
const VOICE_HOLD_DELAY_MS = 320;
const VOICE_DIALOG_PRE_MS = 250;
const VOICE_DIALOG_ANSWER_MS = 550;
const VOICE_DIALOG_ANSWER_MAX_MS = 1800;
const VOICE_DIALOG_MAX_RETRY = 2;
const VOICE_DIALOG_ERR_MAX_RETRY = 3;
const VOICE_DIALOG_RETRY_MS = 600;
const _sfc_main = {
__name: "more",
setup(__props) {
const { proxy } = common_vendor.getCurrentInstance();
function formatUserMessage(msg, fallback = "") {
if (msg == null || msg === "")
return fallback;
if (typeof msg === "string")
return msg;
if (typeof msg === "number" || typeof msg === "boolean")
return String(msg);
if (Array.isArray(msg)) {
const parts = msg.map((item) => {
if (item == null)
return "";
if (typeof item === "string")
return item;
if (typeof item === "number" || typeof item === "boolean")
return String(item);
if (typeof item === "object") {
return item.msg || item.message || item.error || item.title || item.label || "";
}
return String(item);
}).filter(Boolean);
return parts.length ? parts.join(";") : fallback;
}
if (typeof msg === "object") {
return msg.msg || msg.message || msg.error || msg.title || msg.label || fallback;
}
return String(msg);
}
function showUserToast(title, options = {}) {
const text = formatUserMessage(title, "");
if (!text)
return;
common_vendor.index.showToast({
title: text,
icon: options.icon || "none",
duration: typeof options.duration === "number" ? options.duration : 2e3
});
}
const diagnosisId = common_vendor.ref(0);
const patientId = common_vendor.ref(0);
const patientName = common_vendor.ref("");
const headerStyle = common_vendor.ref({});
const headerInnerStyle = common_vendor.ref({});
const mainPadStyle = common_vendor.ref({});
const age = common_vendor.ref(0);
const gender = common_vendor.ref(0);
const authChecking = common_vendor.ref(false);
let gateRedirected = false;
const loading = common_vendor.ref(false);
const cardsLoading = common_vendor.ref(false);
const cards = common_vendor.ref([]);
const rangeMode = common_vendor.ref("7");
const rangeOptions = [
{ value: "7", label: "最近 7 天" },
{ value: "30", label: "最近 30 天" }
];
const bloodRecords = common_vendor.ref([]);
const dietRecords = common_vendor.ref([]);
const exerciseRecords = common_vendor.ref([]);
const trackingNotes = common_vendor.ref([]);
const hoverInfo = common_vendor.ref(null);
common_vendor.computed(() => age.value > 0 ? `${age.value} 岁` : "");
common_vendor.computed(() => {
const map = { 1: "男", 2: "女" };
return map[Number(gender.value)] || "";
});
common_vendor.computed(() => {
const opt = rangeOptions.find((o) => o.value === rangeMode.value);
return opt ? opt.label : "";
});
function formatDate(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
}
function parseDate(str) {
if (!str)
return /* @__PURE__ */ new Date();
const [y, m, d] = str.split("-").map(Number);
return new Date(y, (m || 1) - 1, d || 1);
}
const currentRange = common_vendor.computed(() => {
const days = rangeMode.value === "30" ? 30 : 7;
const end = /* @__PURE__ */ new Date();
end.setHours(0, 0, 0, 0);
const start = new Date(end);
start.setDate(start.getDate() - (days - 1));
return { start: formatDate(start), end: formatDate(end) };
});
const visibleDates = common_vendor.computed(() => {
const { start, end } = currentRange.value;
if (!start || !end)
return [];
const dates = [];
const cursor = parseDate(end);
const lower = parseDate(start).getTime();
while (cursor.getTime() >= lower) {
dates.push(formatDate(cursor));
cursor.setDate(cursor.getDate() - 1);
}
return dates;
});
const chartDates = common_vendor.computed(() => [...visibleDates.value].reverse());
function toNumber(v) {
if (v === null || v === void 0 || v === "")
return null;
const n = Number(v);
return Number.isFinite(n) && n !== 0 ? n : null;
}
function getBloodSugarThresholds(ageValue) {
const a = Number(ageValue);
if (!Number.isFinite(a) || a <= 0)
return { fasting: 7, postprandial: 10 };
if (a < 50)
return { fasting: 7, postprandial: 9 };
return { fasting: 8, postprandial: 10 };
}
function isLowGlucose(v) {
const n = toNumber(v);
return n !== null && n < BLOOD_LOW_THRESHOLD;
}
function isHighFasting(v) {
const n = toNumber(v);
const t = getBloodSugarThresholds(age.value);
return n !== null && t !== null && n >= t.fasting;
}
function isHighPostprandial(v) {
const n = toNumber(v);
const t = getBloodSugarThresholds(age.value);
return n !== null && t !== null && n >= t.postprandial;
}
function isLowFasting(v) {
return isLowGlucose(v) && !isHighFasting(v);
}
function isLowPostprandial(v) {
return isLowGlucose(v) && !isHighPostprandial(v);
}
function isHighBp(row) {
if (!row)
return false;
const s = toNumber(row.systolic_pressure);
const d = toNumber(row.diastolic_pressure);
return s !== null && s > 140 || d !== null && d > 90;
}
function formatBp(row) {
const s = row == null ? void 0 : row.systolic_pressure;
const d = row == null ? void 0 : row.diastolic_pressure;
if ((s === null || s === void 0 || s === "" || s === 0) && (d === null || d === void 0 || d === "" || d === 0))
return "—";
return `${s || "—"} / ${d || "—"}`;
}
function formatBloodMetric(v) {
const n = toNumber(v);
return n === null ? "——" : String(n);
}
const intensityTextMap = { 1: "低强度", 2: "中强度", 3: "高强度" };
const bloodByDate = common_vendor.computed(() => {
const map = {};
for (const r of bloodRecords.value) {
const date = String(r.record_date || "");
if (!date)
continue;
if (!map[date]) {
map[date] = {
record_date: date,
record_time: r.record_time || "",
fasting_blood_sugar: "",
postprandial_blood_sugar: "",
other_blood_sugar: "",
systolic_pressure: "",
diastolic_pressure: "",
western_medicine: "",
insulin: "",
remark: ""
};
}
const merged = map[date];
const fields = ["fasting_blood_sugar", "postprandial_blood_sugar", "other_blood_sugar", "systolic_pressure", "diastolic_pressure", "western_medicine", "insulin", "remark"];
for (const f of fields) {
if ((merged[f] === "" || merged[f] === null || merged[f] === void 0) && r[f] !== "" && r[f] !== null && r[f] !== void 0) {
merged[f] = r[f];
}
}
}
for (const date in map) {
const m = map[date];
m.isHighFasting = isHighFasting(m.fasting_blood_sugar);
m.isHighPostprandial = isHighPostprandial(m.postprandial_blood_sugar);
m.isHighOther = isHighPostprandial(m.other_blood_sugar);
m.isLowFasting = isLowFasting(m.fasting_blood_sugar);
m.isLowPostprandial = isLowPostprandial(m.postprandial_blood_sugar);
m.isLowOther = isLowPostprandial(m.other_blood_sugar);
m.isHighBp = isHighBp(m);
m.bpText = formatBp(m);
m.hasBpOrMed = m.bpText !== "—" || !!(m.western_medicine || m.insulin || m.remark);
}
return map;
});
const dietByDate = common_vendor.computed(() => {
const map = {};
for (const r of dietRecords.value) {
const date = String(r.record_date || "");
if (!date || map[date])
continue;
map[date] = r;
}
return map;
});
const exerciseByDate = common_vendor.computed(() => {
const map = {};
for (const r of exerciseRecords.value) {
const date = String(r.record_date || "");
if (!date || map[date])
continue;
const item = { ...r };
item.intensityText = intensityTextMap[Number(r.intensity)] || "";
map[date] = item;
}
return map;
});
const trackingByDate = common_vendor.computed(() => {
const map = {};
for (const r of trackingNotes.value) {
const date = String(r.note_date || "");
if (!date || map[date])
continue;
map[date] = r;
}
return map;
});
const weekdayNames = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
function getWeekday(dateStr) {
return weekdayNames[parseDate(dateStr).getDay()] || "";
}
const todayStr = common_vendor.computed(() => formatDate(/* @__PURE__ */ new Date()));
common_vendor.computed(() => {
const result = [];
for (const date of visibleDates.value) {
const bloodRaw = bloodByDate.value[date];
const blood = bloodRaw && bloodRaw.hasBpOrMed ? bloodRaw : null;
const diet = dietByDate.value[date];
const exercise = exerciseByDate.value[date];
const tracking = trackingByDate.value[date];
const hasAny = blood || diet || exercise || tracking && tracking.content;
if (!hasAny)
continue;
const [y, m, d] = date.split("-");
result.push({
date,
dateLabel: `${m}-${d}`,
weekday: getWeekday(date),
isToday: date === todayStr.value,
blood,
diet,
exercise,
tracking
});
}
return result;
});
const bloodRecordDays = common_vendor.computed(() => {
const result = [];
for (const date of visibleDates.value) {
const blood = bloodByDate.value[date];
if (!blood)
continue;
const hasSugar = toNumber(blood.fasting_blood_sugar) !== null || toNumber(blood.postprandial_blood_sugar) !== null || toNumber(blood.other_blood_sugar) !== null;
if (!hasSugar)
continue;
const [y, m, d] = date.split("-");
result.push({
date,
dateLabel: `${m}-${d}`,
weekday: getWeekday(date),
isToday: date === todayStr.value,
blood
});
}
return result;
});
common_vendor.computed(() => {
return chartDates.value.some((date) => {
const b = bloodByDate.value[date];
return b && (toNumber(b.fasting_blood_sugar) !== null || toNumber(b.postprandial_blood_sugar) !== null);
});
});
const bloodRecordDayCount = common_vendor.computed(() => {
return chartDates.value.filter((date) => {
const b = bloodByDate.value[date];
return b && (toNumber(b.fasting_blood_sugar) !== null || toNumber(b.postprandial_blood_sugar) !== null);
}).length;
});
const bloodHighDayCount = common_vendor.computed(() => {
return chartDates.value.filter((date) => {
const b = bloodByDate.value[date];
return b && (b.isHighFasting || b.isHighPostprandial);
}).length;
});
common_vendor.computed(() => bloodRecordDayCount.value - bloodHighDayCount.value);
function makeTrend(curr, prev, precision = 1) {
if (curr == null || prev == null)
return null;
const diff = Number(curr) - Number(prev);
if (!Number.isFinite(diff))
return null;
const abs = Math.abs(diff);
const dir = abs < 0.01 ? "flat" : diff > 0 ? "up" : "down";
const sign = dir === "up" ? "+" : dir === "down" ? "-" : "";
const deltaText = dir === "flat" ? "持平" : `${sign}${abs.toFixed(precision)}`;
return { dir, delta: abs, deltaText, prev };
}
function findPrev(field, beforeDate) {
const dates = chartDates.value;
const idx = dates.indexOf(beforeDate);
if (idx <= 0)
return null;
for (let i = idx - 1; i >= 0; i--) {
const b = bloodByDate.value[dates[i]];
if (!b)
continue;
const v = toNumber(b[field]);
if (v !== null)
return { date: dates[i], value: v };
}
return null;
}
const latestStat = common_vendor.computed(() => {
const dates = [...chartDates.value].reverse();
const dummy = { value: "—", date: "暂无记录", isHigh: false, trend: null };
const result = { fasting: { ...dummy }, postprandial: { ...dummy }, other: { ...dummy } };
for (const date of dates) {
const b = bloodByDate.value[date];
if (!b)
continue;
if (result.fasting.value === "—" && toNumber(b.fasting_blood_sugar) !== null) {
const cur = toNumber(b.fasting_blood_sugar);
const prev = findPrev("fasting_blood_sugar", date);
result.fasting = {
value: String(b.fasting_blood_sugar),
date: date.slice(5),
isHigh: b.isHighFasting,
trend: prev ? makeTrend(cur, prev.value) : null
};
}
if (result.postprandial.value === "—" && toNumber(b.postprandial_blood_sugar) !== null) {
const cur = toNumber(b.postprandial_blood_sugar);
const prev = findPrev("postprandial_blood_sugar", date);
result.postprandial = {
value: String(b.postprandial_blood_sugar),
date: date.slice(5),
isHigh: b.isHighPostprandial,
trend: prev ? makeTrend(cur, prev.value) : null
};
}
if (result.other.value === "—" && toNumber(b.other_blood_sugar) !== null) {
const cur = toNumber(b.other_blood_sugar);
const prev = findPrev("other_blood_sugar", date);
result.other = {
value: String(b.other_blood_sugar),
date: date.slice(5),
isHigh: b.isHighOther,
trend: prev ? makeTrend(cur, prev.value) : null
};
}
if (result.fasting.value !== "—" && result.postprandial.value !== "—" && result.other.value !== "—")
break;
}
return result;
});
let authSessionPromise = null;
function hasAuthToken() {
return !!String(common_vendor.index.getStorageSync("token") || "").trim();
}
function clearAuthStorage() {
common_vendor.index.removeStorageSync("token");
authSessionPromise = null;
}
function wxLoginGetCode() {
return new Promise((resolve, reject) => {
common_vendor.index.login({
provider: "weixin",
success: (res) => {
if (res && res.code)
resolve(res.code);
else
reject(new Error("微信登录未返回 code"));
},
fail: reject
});
});
}
async function mnpLoginWithCode(code) {
const res = await proxy.apiUrl({
url: "/api/login/mnpLogin",
method: "POST",
data: { code }
}, false);
if (res && res.code === 1 && res.data && res.data.token) {
common_vendor.index.setStorageSync("token", res.data.token);
common_vendor.index.setStorageSync("userData", res.data);
return res.data;
}
clearAuthStorage();
throw new Error(formatUserMessage(res == null ? void 0 : res.msg, "登录失败"));
}
async function doWxLogin() {
const code = await wxLoginGetCode();
await mnpLoginWithCode(code);
return hasAuthToken();
}
async function verifyOrLogin() {
if (!hasAuthToken()) {
return doWxLogin();
}
try {
const res = await proxy.apiUrl({ url: "/api/user/info", method: "POST" }, false);
if (res && res.code === 1 && res.data) {
common_vendor.index.setStorageSync("userData", res.data);
return true;
}
} catch (e) {
}
clearAuthStorage();
try {
return await doWxLogin();
} catch (e2) {
return false;
}
}
async function ensureLoggedIn() {
if (authSessionPromise) {
return authSessionPromise;
}
authSessionPromise = verifyOrLogin().then((ok) => {
if (!ok)
authSessionPromise = null;
return !!ok;
}).catch(() => {
authSessionPromise = null;
return false;
});
return authSessionPromise;
}
async function fetchCardList() {
if (!hasAuthToken())
return [];
cardsLoading.value = true;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/getCardList",
method: "GET",
data: {}
}, false);
if (res && res.code === 1 && Array.isArray(res.data)) {
cards.value = res.data;
return res.data;
}
cards.value = [];
return [];
} catch (e) {
cards.value = [];
return [];
} finally {
cardsLoading.value = false;
}
}
async function applyCard(card) {
if (!card || !hasAuthToken())
return;
diagnosisId.value = Number(card.id) || 0;
patientId.value = Number(card.patient_id) || 0;
patientName.value = card.patient_name || "";
age.value = Number(card.age) || 0;
gender.value = Number(card.gender) || 0;
lastFamilyLikeCount.value = getStoredFamilyLikeSeen(Number(card.id) || 0);
familyLikeSummary.value = {
like_count: 0,
summary_line: "分享战报给家人,邀请他们为您点赞鼓劲",
recent: []
};
gamifyStatusTip.value = "";
loadTreeSpecies();
await fetchGamifyState();
}
function readUserMobile() {
try {
const userData = common_vendor.index.getStorageSync("userData") || {};
return String(userData.mobile || userData.phone || "").trim();
} catch (e) {
return "";
}
}
async function ensureDailyContextByPhone() {
if (diagnosisId.value)
return true;
const res = await proxy.apiUrl({
url: "/api/tcm/dailyEnsurePhoneContext",
method: "POST",
data: {}
}, false);
if (res && res.code === 1 && res.data && res.data.diagnosis_id) {
await applyCard({
id: res.data.diagnosis_id,
patient_id: res.data.patient_id,
patient_name: res.data.patient_name || "",
age: res.data.age || 0,
gender: res.data.gender || 0
});
if (res.data.created || res.data.linked_existing) {
await fetchCardList();
}
return true;
}
if (res && res.data && res.data.need_mobile) {
return "need_mobile";
}
showUserToast(formatUserMessage(res == null ? void 0 : res.msg, "无法加载记录"));
return false;
}
async function onSelectCard(card) {
if (!card || Number(card.id) === Number(diagnosisId.value))
return;
bloodRecords.value = [];
dietRecords.value = [];
exerciseRecords.value = [];
trackingNotes.value = [];
hoverInfo.value = null;
await applyCard(card);
fetchAll();
}
async function fetchAll() {
if (!hasAuthToken() || !diagnosisId.value)
return;
loading.value = true;
const { start, end } = currentRange.value;
try {
const [winRes, noteRes] = await Promise.all([
proxy.apiUrl({
url: "/api/tcm/dailyTrackingWindow",
method: "GET",
data: {
diagnosis_id: diagnosisId.value,
patient_id: patientId.value || 0,
start_date: start,
end_date: end
}
}, false),
proxy.apiUrl({
url: "/api/tcm/dailyTrackingNotes",
method: "GET",
data: {
diagnosis_id: diagnosisId.value,
patient_id: patientId.value || 0
}
}, false)
]);
if (winRes && winRes.code === 1) {
const d = winRes.data || {};
bloodRecords.value = Array.isArray(d.blood_records) ? d.blood_records : [];
dietRecords.value = Array.isArray(d.diet_records) ? d.diet_records : [];
exerciseRecords.value = Array.isArray(d.exercise_records) ? d.exercise_records : [];
if (d.diagnosis) {
if (!patientName.value)
patientName.value = d.diagnosis.patient_name || "";
if (!age.value)
age.value = Number(d.diagnosis.age) || 0;
if (!gender.value)
gender.value = Number(d.diagnosis.gender) || 0;
if (!patientId.value)
patientId.value = Number(d.diagnosis.patient_id) || 0;
}
} else {
bloodRecords.value = [];
dietRecords.value = [];
exerciseRecords.value = [];
if (winRes == null ? void 0 : winRes.msg)
showUserToast(winRes.msg);
}
if (noteRes && noteRes.code === 1) {
const list = Array.isArray(noteRes.data) ? noteRes.data : [];
trackingNotes.value = list.filter((it) => visibleDates.value.includes(String((it == null ? void 0 : it.note_date) || "")));
} else {
trackingNotes.value = [];
}
await fetchGamifyState();
await loadFamilyLikeSummary();
} catch (e) {
common_vendor.index.showToast({ title: "加载失败,请稍后再试", icon: "none" });
} finally {
loading.value = false;
if (ttsAutoPlay.value && !recordOverlayOpen.value) {
const token = ++ttsAutoToken;
setTimeout(() => {
if (token === ttsAutoToken && !recordOverlayOpen.value)
speakLatestSummary();
}, 350);
}
}
}
function onChangeRange(value) {
if (rangeMode.value === value)
return;
rangeMode.value = value;
fetchAll();
}
function onRefresh() {
fetchAll();
}
function buildTimeGreetingMeta(hour = (/* @__PURE__ */ new Date()).getHours()) {
const h = Number(hour) || 0;
if (h < 5)
return { text: "夜深了", icon: "moon" };
if (h < 11)
return { text: "早上好", icon: "sun" };
if (h < 13)
return { text: "中午好", icon: "sun" };
if (h < 18)
return { text: "下午好", icon: "sun" };
if (h < 22)
return { text: "晚上好", icon: "sunset" };
return { text: "夜深了", icon: "moon" };
}
common_vendor.computed(() => buildTimeGreetingMeta());
function dayHasBloodRecord(key) {
const b = bloodByDate.value[key];
if (!b)
return false;
return toNumber(b.fasting_blood_sugar) !== null || toNumber(b.postprandial_blood_sugar) !== null || toNumber(b.other_blood_sugar) !== null || toNumber(b.systolic_pressure) !== null || toNumber(b.diastolic_pressure) !== null;
}
function dayCoverageLevel(key) {
const b = bloodByDate.value[key];
const dt = dietByDate.value[key];
const ex = exerciseByDate.value[key];
if (!b && !dt && !ex)
return 0;
let score = 0;
if (b) {
const fields = ["fasting_blood_sugar", "postprandial_blood_sugar", "other_blood_sugar", "systolic_pressure", "diastolic_pressure"];
score += fields.filter((f) => toNumber(b[f]) !== null).length;
}
if (dt && (dt.breakfast_foods || dt.lunch_foods || dt.dinner_foods))
score += 1;
if (ex && (ex.exercise_type || ex.duration))
score += 1;
if (score === 0)
return 0;
if (score === 1)
return 1;
if (score <= 3)
return 2;
return 3;
}
const todayChecked = common_vendor.computed(() => dayHasBloodRecord(todayStr.value));
const streakDays = common_vendor.computed(() => {
const today = /* @__PURE__ */ new Date();
let count = 0;
let startedFromYesterday = false;
for (let i = 0; i < 90; i++) {
const d = new Date(today);
d.setDate(today.getDate() - i);
const key = formatDate(d);
const has = dayHasBloodRecord(key);
if (has) {
count++;
} else {
if (i === 0) {
startedFromYesterday = true;
continue;
}
break;
}
}
return { days: count, startedFromYesterday };
});
const last7Dots = common_vendor.computed(() => {
const arr = [];
const today = /* @__PURE__ */ new Date();
const wkChars = ["日", "一", "二", "三", "四", "五", "六"];
for (let i = 6; i >= 0; i--) {
const d = new Date(today);
d.setDate(today.getDate() - i);
const key = formatDate(d);
arr.push({
date: key,
label: wkChars[d.getDay()],
isToday: i === 0,
hasRecord: dayHasBloodRecord(key)
});
}
return arr;
});
const last7CheckedCount = common_vendor.computed(() => last7Dots.value.filter((d) => d.hasRecord).length);
function dayHasFastingRecord(key) {
const b = bloodByDate.value[key];
return !!(b && toNumber(b.fasting_blood_sugar) !== null);
}
function dayHasPostprandialRecord(key) {
const b = bloodByDate.value[key];
return !!(b && toNumber(b.postprandial_blood_sugar) !== null);
}
function dayIsCompleteRecord(key) {
return dayHasFastingRecord(key) && dayHasPostprandialRecord(key);
}
function dayHasHighBloodSugar(key) {
const b = bloodByDate.value[key];
if (!b)
return false;
return b.isHighFasting || b.isHighPostprandial;
}
const weekCompleteDays = common_vendor.computed(() => last7Dots.value.filter((d) => dayIsCompleteRecord(d.date)).length);
const heatmapWeeks = common_vendor.computed(() => {
const dates = chartDates.value;
if (!dates || !dates.length)
return [];
const sorted = [...dates].sort();
const first = new Date(sorted[0]);
const last = new Date(sorted[sorted.length - 1]);
const firstDow = (first.getDay() + 6) % 7;
const lastDow = (last.getDay() + 6) % 7;
const gridStart = new Date(first);
gridStart.setDate(first.getDate() - firstDow);
const gridEnd = new Date(last);
gridEnd.setDate(last.getDate() + (6 - lastDow));
const totalDays = Math.round((gridEnd - gridStart) / 864e5) + 1;
const weeks = [];
let cur = new Date(gridStart);
for (let w = 0; w < Math.ceil(totalDays / 7); w++) {
const week = [];
for (let i = 0; i < 7; i++) {
const key = formatDate(cur);
const inRange = sorted.includes(key);
week.push({
date: key,
day: cur.getDate(),
inRange,
isToday: key === todayStr.value,
level: inRange ? dayCoverageLevel(key) : -1,
isHigh: inRange ? dayHasHighBloodSugar(key) : false
});
cur.setDate(cur.getDate() + 1);
}
weeks.push(week);
}
return weeks;
});
const heatmapCells = common_vendor.computed(() => {
const weeks = heatmapWeeks.value;
return weeks.length ? weeks.flat() : [];
});
const heatmapTotalRecords = common_vendor.computed(() => {
let c = 0;
for (const w of heatmapWeeks.value)
for (const c2 of w)
if (c2.inRange && c2.level > 0)
c++;
return c;
});
const heatmapInRangeDays = common_vendor.computed(() => {
let c = 0;
for (const w of heatmapWeeks.value)
for (const c2 of w)
if (c2.inRange)
c++;
return c;
});
const heatmapMonthLabel = common_vendor.computed(() => {
const weeks = heatmapWeeks.value;
if (!weeks.length)
return "";
const counter = /* @__PURE__ */ new Map();
for (const w of weeks) {
for (const c of w) {
if (!c.inRange)
continue;
const [y2, m2] = c.date.split("-");
const key = `${y2}-${m2}`;
counter.set(key, (counter.get(key) || 0) + 1);
}
}
if (!counter.size) {
const first = weeks[0][0].date.split("-");
return `${Number(first[0])}年${Number(first[1])}月`;
}
let best = "";
let bestCount = -1;
for (const [k, v] of counter) {
if (v > bestCount) {
best = k;
bestCount = v;
}
}
const [y, m] = best.split("-");
return `${Number(y)}年${Number(m)}月`;
});
function jumpToDayRecord(date) {
if (!bloodRecordDays.value.some((d) => d.date === date)) {
common_vendor.index.showToast({ title: "该日无记录", icon: "none" });
return;
}
common_vendor.index.createSelectorQuery().in(proxy).select(`#day-${date.replace(/-/g, "")}`).boundingClientRect().selectViewport().scrollOffset().exec((res) => {
if (!res || !res[0])
return;
const top = res[0].top + (res[1] ? res[1].scrollTop : 0) - 80;
common_vendor.index.pageScrollTo({ scrollTop: top, duration: 280 });
});
}
const todayText = common_vendor.computed(() => {
const d = /* @__PURE__ */ new Date();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
return `${d.getFullYear()}-${m}-${day} ${weekdays[d.getDay()]}`;
});
const emptyInputForm = () => ({
visible: false,
loading: false,
submitting: false,
id: 0,
fasting_blood_sugar: "",
postprandial_blood_sugar: "",
other_blood_sugar: "",
remark: ""
});
const inputForm = common_vendor.ref(emptyInputForm());
const emptyBpForm = () => ({
visible: false,
loading: false,
submitting: false,
id: 0,
systolic_pressure: "",
diastolic_pressure: "",
western_medicine: "",
insulin: "",
remark: ""
});
const bpForm = common_vendor.ref(emptyBpForm());
function prepareRecordOverlay() {
stopVoiceDialog();
ttsStop();
}
function onInputField(field, e) {
var _a;
inputForm.value[field] = ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.value) ?? "";
}
async function openInputForm() {
if (!diagnosisId.value) {
showUserToast("请先选择就诊卡");
return;
}
const token = common_vendor.index.getStorageSync("token");
if (!token) {
showUserToast("请先登录后再录入");
return;
}
prepareRecordOverlay();
inputForm.value = { ...emptyInputForm(), visible: true, loading: true };
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyTodayBloodRecord",
method: "GET",
data: { diagnosis_id: diagnosisId.value }
}, false);
if (res && res.code === 1 && res.data && res.data.record) {
const r = res.data.record;
inputForm.value.id = Number(r.id) || 0;
inputForm.value.fasting_blood_sugar = numToStr(r.fasting_blood_sugar);
inputForm.value.postprandial_blood_sugar = numToStr(r.postprandial_blood_sugar);
inputForm.value.other_blood_sugar = numToStr(r.other_blood_sugar);
inputForm.value.remark = String(r.remark || "");
if (todayHasGlucose.value) {
showUserToast("今日血糖已录入,如需修改可在下方调整", { duration: 2600 });
}
}
} catch (e) {
} finally {
inputForm.value.loading = false;
}
}
function closeInputForm() {
if (inputForm.value.submitting)
return;
cancelVoiceHold();
stopVoiceDialog();
inputForm.value.visible = false;
}
async function submitInputForm() {
if (inputForm.value.submitting || inputForm.value.loading)
return;
if (voiceDialog.active)
stopVoiceDialog();
const f = inputForm.value;
const hasAny = [
f.fasting_blood_sugar,
f.postprandial_blood_sugar,
f.other_blood_sugar
].some((v) => v !== "" && v !== null && Number(v) > 0) || !!(f.remark && f.remark.trim());
if (!hasAny) {
showUserToast("请至少填写一项数据");
return;
}
inputForm.value.submitting = true;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailySaveBloodRecord",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
fasting_blood_sugar: f.fasting_blood_sugar,
postprandial_blood_sugar: f.postprandial_blood_sugar,
other_blood_sugar: f.other_blood_sugar,
remark: f.remark
}
}, false);
if (res && res.code === 1) {
showUserToast(res.msg || "今日血糖已录入完成", { icon: "success" });
inputForm.value.visible = false;
await fetchAll();
await fetchGamifyState();
promptWaterTreeGuide("血糖");
} else {
showUserToast(res && res.msg || "保存失败");
}
} catch (e) {
showUserToast("网络异常,请稍后再试");
} finally {
inputForm.value.submitting = false;
}
}
function deleteInputRecord() {
if (!inputForm.value.id || inputForm.value.submitting)
return;
if (voiceDialog.active)
stopVoiceDialog();
common_vendor.index.showModal({
title: "删除确认",
content: "确定删除今天已录入的血糖数据吗?删除后无法恢复。",
confirmColor: "#dc2626",
success: async (m) => {
if (!m.confirm)
return;
inputForm.value.submitting = true;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyDeleteBloodRecord",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
id: inputForm.value.id
}
}, false);
if (res && res.code === 1) {
common_vendor.index.showToast({ title: "已删除", icon: "success" });
inputForm.value.visible = false;
await fetchAll();
await fetchGamifyState();
} else {
showUserToast(res && res.msg || "删除失败");
}
} catch (e) {
common_vendor.index.showToast({ title: "网络异常,请稍后再试", icon: "none" });
} finally {
inputForm.value.submitting = false;
}
}
});
}
function onBpField(field, e) {
var _a;
const val = ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.value) ?? "";
bpForm.value[field] = val;
}
async function openBpForm() {
if (!diagnosisId.value) {
showUserToast("请先选择就诊卡");
return;
}
const token = common_vendor.index.getStorageSync("token");
if (!token) {
showUserToast("请先登录后再录入");
return;
}
prepareRecordOverlay();
bpForm.value = { ...emptyBpForm(), visible: true, loading: true };
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyTodayBloodRecord",
method: "GET",
data: { diagnosis_id: diagnosisId.value }
}, false);
if (res && res.code === 1 && res.data && res.data.record) {
const r = res.data.record;
bpForm.value.id = Number(r.id) || 0;
bpForm.value.systolic_pressure = numToStr(r.systolic_pressure);
bpForm.value.diastolic_pressure = numToStr(r.diastolic_pressure);
bpForm.value.western_medicine = String(r.western_medicine || "");
bpForm.value.insulin = String(r.insulin || "");
bpForm.value.remark = String(r.remark || "");
if (todayHasBpPressure.value) {
showUserToast("今日血压已录入,如需修改可在下方调整", { duration: 2600 });
}
}
} catch (e) {
} finally {
bpForm.value.loading = false;
}
}
function numToStr(v) {
if (v === null || v === void 0 || v === "" || v === 0 || v === "0")
return "";
if (typeof v === "object")
return "";
return String(v);
}
function closeBpForm() {
if (bpForm.value.submitting)
return;
cancelVoiceHold();
bpForm.value.visible = false;
common_vendor.nextTick$1(() => {
});
}
const emptyDietForm = () => ({
visible: false,
loading: false,
submitting: false,
id: 0,
breakfast_foods: "",
lunch_foods: "",
dinner_foods: "",
note: ""
});
const dietForm = common_vendor.ref(emptyDietForm());
const dietAiPrefill = common_vendor.ref(null);
function fillDietFormFromAi() {
const r = dietAiPrefill.value;
if (!r)
return;
if (r.breakfast)
dietForm.value.breakfast_foods = r.breakfast;
if (r.lunch)
dietForm.value.lunch_foods = r.lunch;
if (r.dinner)
dietForm.value.dinner_foods = r.dinner;
if (r.tips) {
const note = String(dietForm.value.note || "").trim();
dietForm.value.note = note ? `${note};${r.tips}` : r.tips;
}
}
function onDietField(field, e) {
var _a;
dietForm.value[field] = ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.value) ?? "";
}
async function openDietForm(options = {}) {
var _a, _b;
const autoFill = !!options.autoFill;
if (!dietAiPrefill.value) {
dietAiPrefill.value = tongji_composables_useDietAi.consumeDietAiPrefill();
}
if (!diagnosisId.value) {
showUserToast("请先选择就诊卡");
return;
}
const token = common_vendor.index.getStorageSync("token");
if (!token) {
showUserToast("请先登录后再录入");
return;
}
prepareRecordOverlay();
dietForm.value = { ...emptyDietForm(), visible: true, loading: true };
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyTodayDietRecord",
method: "GET",
data: { diagnosis_id: diagnosisId.value }
}, false);
if ((res == null ? void 0 : res.code) === 1 && ((_a = res.data) == null ? void 0 : _a.record)) {
const r = res.data.record;
dietForm.value.id = Number(r.id) || 0;
dietForm.value.breakfast_foods = String(r.breakfast_foods || "");
dietForm.value.lunch_foods = String(r.lunch_foods || "");
dietForm.value.dinner_foods = String(r.dinner_foods || "");
dietForm.value.note = String(r.note || "");
if (todayHasDiet.value) {
showUserToast("今日饮食已录入,如需修改可在下方调整", { duration: 2600 });
}
}
} catch (e) {
} finally {
dietForm.value.loading = false;
if (autoFill && ((_b = dietAiPrefill.value) == null ? void 0 : _b.breakfast)) {
fillDietFormFromAi();
}
}
}
function closeDietForm() {
if (dietForm.value.submitting)
return;
cancelVoiceHold();
dietForm.value.visible = false;
dietAiPrefill.value = null;
common_vendor.nextTick$1(() => {
});
}
async function submitDietForm() {
if (dietForm.value.submitting || dietForm.value.loading)
return;
const f = dietForm.value;
if (!f.breakfast_foods.trim() && !f.lunch_foods.trim() && !f.dinner_foods.trim()) {
showUserToast("请至少填写一餐饮食");
return;
}
dietForm.value.submitting = true;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailySaveDietRecord",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
breakfast_foods: f.breakfast_foods,
lunch_foods: f.lunch_foods,
dinner_foods: f.dinner_foods,
note: f.note
}
}, false);
if ((res == null ? void 0 : res.code) === 1) {
showUserToast(res.msg || "今日饮食已录入完成", { icon: "success" });
dietForm.value.visible = false;
await fetchAll();
await fetchGamifyState();
promptWaterTreeGuide("饮食");
} else {
showUserToast(res && res.msg || "保存失败");
}
} catch (e) {
showUserToast("网络异常,请稍后再试");
} finally {
dietForm.value.submitting = false;
}
}
const exerciseIntensityOptions = [
{ value: 1, label: "低强度" },
{ value: 2, label: "中强度" },
{ value: 3, label: "高强度" }
];
const emptyExerciseForm = () => ({
visible: false,
loading: false,
submitting: false,
id: 0,
exercise_type: "",
duration: "",
intensity: 2,
note: ""
});
const exerciseForm = common_vendor.ref(emptyExerciseForm());
const recordOverlayOpen = common_vendor.computed(
() => inputForm.value.visible || bpForm.value.visible || dietForm.value.visible || exerciseForm.value.visible
);
const voiceTarget = common_vendor.ref("");
const {
sttListening,
sttHolding,
sttSupported,
beginHoldSpeech,
endHoldSpeech
} = tongji_composables_useSpeechToText.useSpeechToText({
onResult(text) {
if (voiceDialog.active)
return;
applyVoiceResult(text);
},
onSettle(text) {
if (voiceDialog.active) {
handleDialogSettle(text);
return true;
}
return false;
},
onError(msg) {
if (tongji_composables_useSpeechToText.isBenignSttError(msg))
return true;
if (voiceDialog.active) {
handleDialogError();
return true;
}
return false;
},
onPartial(text) {
if (voiceDialog.active)
handleDialogPartial(text);
},
showToast: (msg) => showUserToast(msg)
});
let voiceHoldTimer = null;
let voiceHoldPendingTarget = "";
const manualVoicePressing = common_vendor.ref(false);
let manualVoicePressTarget = "";
function blockVoiceTouchMove() {
}
function isManualVoiceActive(target) {
return voiceTarget.value === target && (sttHolding.value || sttListening.value);
}
function isVoicePressing(target) {
return manualVoicePressing.value && manualVoicePressTarget === target;
}
function isVoiceBarLive(target) {
return isManualVoiceActive(target) || isVoicePressing(target);
}
function clearVoiceHoldTimer() {
if (voiceHoldTimer) {
clearTimeout(voiceHoldTimer);
voiceHoldTimer = null;
}
}
function cancelVoiceHold() {
manualVoicePressing.value = false;
manualVoicePressTarget = "";
clearVoiceHoldTimer();
voiceHoldPendingTarget = "";
forceEndHoldSpeech();
}
function forceEndHoldSpeech() {
if (sttHolding.value || sttListening.value) {
endHoldSpeech();
} else {
endHoldSpeech(true);
}
}
function startVoiceInput(target) {
if (!sttSupported.value) {
showUserToast("当前环境不支持语音输入");
return;
}
if (voiceDialog.active)
stopVoiceDialog();
manualVoicePressing.value = true;
manualVoicePressTarget = target;
clearVoiceHoldTimer();
voiceHoldPendingTarget = target;
voiceHoldTimer = setTimeout(() => {
voiceHoldTimer = null;
if (!manualVoicePressing.value || manualVoicePressTarget !== target || voiceHoldPendingTarget !== target)
return;
voiceTarget.value = target;
beginHoldSpeech();
}, VOICE_HOLD_DELAY_MS);
}
function endVoiceInput() {
const wasPressing = manualVoicePressing.value;
const hadPendingOnly = !!voiceHoldPendingTarget;
const wasRecording = sttHolding.value || sttListening.value;
manualVoicePressing.value = false;
manualVoicePressTarget = "";
clearVoiceHoldTimer();
voiceHoldPendingTarget = "";
if (wasRecording || wasPressing) {
forceEndHoldSpeech();
}
if (hadPendingOnly && !wasRecording) {
showUserToast("请长按说话", { icon: "none", duration: 1500 });
}
}
function onPageVoiceTouchEnd() {
if (manualVoicePressing.value) {
if (voiceDialog.active && voiceDialog.phase === "listening") {
onDialogAnswerTouchEnd();
} else {
endVoiceInput();
}
return;
}
if ((sttHolding.value || sttListening.value) && voiceTarget.value && !voiceDialog.active) {
endVoiceInput();
}
}
function applyVoiceResult(text) {
const raw = String(text || "").trim();
if (!raw)
return;
const target = voiceTarget.value;
let parsed = {};
let formRef = null;
if (target === "glucose") {
parsed = tongji_utils_voiceParse.parseGlucose(raw);
formRef = inputForm;
} else if (target === "bp") {
parsed = tongji_utils_voiceParse.parseBloodPressure(raw);
formRef = bpForm;
} else if (target === "diet") {
parsed = tongji_utils_voiceParse.parseDiet(raw);
formRef = dietForm;
} else if (target === "exercise") {
parsed = tongji_utils_voiceParse.parseExercise(raw);
formRef = exerciseForm;
}
if (!formRef)
return;
const keys = Object.keys(parsed);
if (!keys.length) {
if (target === "diet") {
appendToField(formRef, "note", raw);
showUserToast("已记入饮食备注,请确认");
} else if (target === "glucose") {
appendToField(formRef, "remark", raw);
showUserToast("未识别到血糖数值,已记入备注");
} else if (target === "bp") {
showUserToast("未识别到血压数值,请重试或手动填写");
} else {
showUserToast("没听清,请再说一次");
}
return;
}
keys.forEach((k) => {
if (k === "note" || k === "remark" || k === "western_medicine" || k === "insulin") {
appendToField(formRef, k, parsed[k]);
} else {
formRef.value[k] = parsed[k];
}
});
const summary = tongji_utils_voiceParse.summarizeParsed(parsed);
showUserToast(summary ? `已识别:${summary}` : "已填写", { icon: "none", duration: 2200 });
}
function appendToField(formRef, field, value) {
const v = String(value || "").trim();
if (!v)
return;
const prev = String(formRef.value[field] || "").trim();
formRef.value[field] = prev ? `${prev};${v}` : v;
}
const GLUCOSE_DIALOG_STEPS = [
{ field: "fasting_blood_sugar", label: "空腹血糖", question: "请问空腹血糖是多少?" },
{ field: "postprandial_blood_sugar", label: "餐后血糖", question: "餐后血糖是多少?" },
{ field: "other_blood_sugar", label: "其他时段血糖", question: "其他时段血糖是多少?" }
];
const voiceDialog = common_vendor.reactive({
active: false,
// 对话流程是否进行中
phase: "idle",
// idle | asking | listening (仅表示麦克风/播报状态)
stage: "collect",
// collect(逐项采集)| confirm(确认提交)
stepIndex: 0,
retries: 0,
errorRetries: 0,
// 录音启动失败的自动重试计数
barged: false,
// 用户是否已在问题播报中抢答
pendingValue: "",
// 实时识别已捕获、待定格的数值
botText: "",
// 当前机器人提示/问题
heardText: "",
// 最近一次识别到的用户回答
steps: GLUCOSE_DIALOG_STEPS,
collected: []
// [{ label, value }]
});
let voiceListenTimer = null;
let voiceAnswerTimer = null;
let voiceFlowToken = 0;
function clearVoiceListenTimer() {
if (voiceListenTimer) {
clearTimeout(voiceListenTimer);
voiceListenTimer = null;
}
}
function clearAnswerTimer() {
if (voiceAnswerTimer) {
clearTimeout(voiceAnswerTimer);
voiceAnswerTimer = null;
}
}
function clearVoiceTimers() {
clearVoiceListenTimer();
clearAnswerTimer();
}
function currentDialogStep() {
return voiceDialog.steps[voiceDialog.stepIndex] || null;
}
function askCurrentStep(prefix = "") {
const step = currentDialogStep();
if (!step) {
finishVoiceDialog();
return;
}
const token = voiceFlowToken;
voiceDialog.phase = "asking";
voiceDialog.errorRetries = 0;
voiceDialog.barged = false;
voiceDialog.pendingValue = "";
voiceDialog.heardText = "";
voiceDialog.botText = `${prefix}${step.question}`;
clearVoiceTimers();
ttsSpeak(voiceDialog.botText, {
force: true,
onDone: () => {
if (token !== voiceFlowToken || !voiceDialog.active)
return;
listenForAnswer();
}
});
}
function listenForAnswer() {
if (!voiceDialog.active)
return;
const token = voiceFlowToken;
voiceDialog.pendingValue = "";
voiceDialog.heardText = "";
clearVoiceTimers();
voiceDialog.phase = "asking";
voiceListenTimer = setTimeout(() => {
if (token !== voiceFlowToken || !voiceDialog.active)
return;
voiceDialog.phase = "listening";
}, VOICE_DIALOG_PRE_MS);
}
function onDialogAnswerTouchEnd() {
manualVoicePressing.value = false;
manualVoicePressTarget = "";
if (!voiceDialog.active || voiceDialog.phase !== "listening") {
if (sttHolding.value || sttListening.value)
forceEndHoldSpeech();
return;
}
clearVoiceTimers();
if (sttHolding.value || sttListening.value) {
finishListeningEarly();
return;
}
forceEndHoldSpeech();
}
function handleDialogPartial(text) {
if (!voiceDialog.active)
return;
if (voiceDialog.stage !== "collect")
return;
if (voiceDialog.phase !== "listening")
return;
const value = extractGlucoseValue(text);
if (value === null)
return;
const token = voiceFlowToken;
voiceDialog.pendingValue = value;
voiceDialog.heardText = text;
if (!voiceDialog.barged) {
voiceDialog.barged = true;
clearVoiceListenTimer();
voiceListenTimer = setTimeout(() => {
if (token !== voiceFlowToken || !voiceDialog.active)
return;
finalizeCollectAnswer();
}, VOICE_DIALOG_ANSWER_MAX_MS);
}
clearAnswerTimer();
voiceAnswerTimer = setTimeout(() => {
if (token !== voiceFlowToken || !voiceDialog.active)
return;
finalizeCollectAnswer();
}, VOICE_DIALOG_ANSWER_MS);
}
function finalizeCollectAnswer() {
if (!voiceDialog.active)
return;
const step = currentDialogStep();
const value = voiceDialog.pendingValue;
voiceDialog.pendingValue = "";
clearVoiceTimers();
endHoldSpeech();
if (!step || !value)
return;
inputForm.value[step.field] = value;
voiceDialog.collected.push({ label: step.label, value });
voiceDialog.retries = 0;
gotoNextStep(`好的,${step.label}已记录。`);
}
function handleDialogError() {
if (!voiceDialog.active)
return;
clearVoiceTimers();
if (voiceDialog.errorRetries < VOICE_DIALOG_ERR_MAX_RETRY) {
voiceDialog.errorRetries += 1;
const token = voiceFlowToken;
voiceDialog.phase = "asking";
voiceListenTimer = setTimeout(() => {
if (token !== voiceFlowToken || !voiceDialog.active)
return;
listenForAnswer();
}, VOICE_DIALOG_RETRY_MS);
} else {
voiceDialog.errorRetries = 0;
gotoNextStep("录音没成功,这项先空着。");
}
}
function finishListeningEarly() {
if (!voiceDialog.active || voiceDialog.phase !== "listening")
return;
if (voiceDialog.stage === "collect" && voiceDialog.pendingValue) {
finalizeCollectAnswer();
return;
}
clearVoiceTimers();
endHoldSpeech();
}
function handleDialogSettle(text) {
if (!voiceDialog.active)
return;
if (voiceDialog.phase !== "listening")
return;
clearVoiceTimers();
const partial = voiceDialog.heardText;
let raw = String(text || "").trim();
if (!raw && voiceDialog.barged && partial)
raw = partial;
voiceDialog.heardText = raw;
if (voiceDialog.stage === "confirm") {
handleConfirmAnswer(raw);
return;
}
const step = currentDialogStep();
if (!step) {
finishVoiceDialog();
return;
}
const value = extractGlucoseValue(raw) ?? (voiceDialog.pendingValue || null);
voiceDialog.pendingValue = "";
if (value !== null) {
inputForm.value[step.field] = value;
voiceDialog.collected.push({ label: step.label, value });
voiceDialog.retries = 0;
gotoNextStep(`好的,${step.label}已记录。`);
return;
}
if (raw && /退出|取消|关闭/.test(raw)) {
speakDialog("已为您关闭语音问答,可以手动填写。", () => stopVoiceDialog());
return;
}
if (raw && /跳过|没有|不测|没测|没量|不用|下一/.test(raw)) {
gotoNextStep(`好的,${step.label}先空着。`);
return;
}
if (voiceDialog.retries < VOICE_DIALOG_MAX_RETRY) {
voiceDialog.retries += 1;
askCurrentStep("没听清,");
} else {
voiceDialog.retries = 0;
gotoNextStep(`暂时没听清${step.label},先空着。`);
}
}
function extractGlucoseValue(raw) {
if (!raw)
return null;
const norm = tongji_utils_voiceParse.normalizeNumbers(raw);
const m = norm.match(/\d+(?:\.\d+)?/);
if (!m)
return null;
const num = Number(m[0]);
if (!Number.isFinite(num) || num <= 0)
return null;
return m[0];
}
function gotoNextStep(prefix = "") {
voiceDialog.stepIndex += 1;
voiceDialog.retries = 0;
voiceDialog.errorRetries = 0;
if (voiceDialog.stepIndex >= voiceDialog.steps.length) {
finishVoiceDialog(prefix);
} else {
askCurrentStep(prefix);
}
}
function finishVoiceDialog(prefix = "") {
if (!voiceDialog.collected.length) {
speakDialog(`${prefix}还没有录入任何血糖数据,您可以手动填写或重新开始语音问答。`, () => stopVoiceDialog());
return;
}
const summary = voiceDialog.collected.map((c) => `${c.label}${c.value}`).join(",");
voiceDialog.stage = "confirm";
voiceDialog.phase = "asking";
voiceDialog.retries = 0;
voiceDialog.heardText = "";
voiceDialog.botText = `${prefix}已为您填写:${summary}。要现在提交吗?说"提交"确认,或说"取消"。`;
const token = voiceFlowToken;
ttsSpeak(voiceDialog.botText, {
force: true,
onDone: () => {
if (token !== voiceFlowToken || !voiceDialog.active)
return;
listenForAnswer();
}
});
}
function handleConfirmAnswer(raw) {
if (raw && /提交|确认|可以|好的|好了|是的|对|要|提交吧|保存/.test(raw)) {
speakDialog("好的,正在为您提交。", () => {
stopVoiceDialog();
submitInputForm();
});
return;
}
if (raw && /取消|不提交|不要|先不|算了|退出|关闭/.test(raw)) {
speakDialog("已为您填好,请核对后手动提交。", () => stopVoiceDialog());
return;
}
if (voiceDialog.retries < VOICE_DIALOG_MAX_RETRY) {
voiceDialog.retries += 1;
const token = voiceFlowToken;
voiceDialog.stage = "confirm";
voiceDialog.phase = "asking";
voiceDialog.botText = '没听清,要现在提交吗?说"提交"或"取消"。';
ttsSpeak(voiceDialog.botText, {
force: true,
onDone: () => {
if (token !== voiceFlowToken || !voiceDialog.active)
return;
listenForAnswer();
}
});
} else {
speakDialog("已为您填好,请核对后手动提交。", () => stopVoiceDialog());
}
}
function speakDialog(text, onDone) {
voiceDialog.botText = text;
const token = voiceFlowToken;
ttsSpeak(text, {
force: true,
onDone: () => {
if (token !== voiceFlowToken)
return;
if (typeof onDone === "function")
onDone();
}
});
}
function stopVoiceDialog() {
clearVoiceTimers();
voiceFlowToken += 1;
endHoldSpeech();
ttsStop();
voiceDialog.active = false;
voiceDialog.phase = "idle";
voiceDialog.stage = "collect";
voiceDialog.barged = false;
voiceDialog.pendingValue = "";
voiceDialog.heardText = "";
}
common_vendor.computed(() => {
if (voiceDialog.phase === "listening") {
return voiceDialog.stage === "confirm" ? "按住作答区:说提交或取消,松手结束" : "按住下方作答区说话,松手结束";
}
if (voiceDialog.phase === "asking") {
return "正在提问…";
}
return "";
});
function onExerciseField(field, e) {
var _a;
exerciseForm.value[field] = ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.value) ?? "";
}
async function openExerciseForm() {
var _a;
if (!diagnosisId.value) {
showUserToast("请先选择就诊卡");
return;
}
const token = common_vendor.index.getStorageSync("token");
if (!token) {
showUserToast("请先登录后再录入");
return;
}
prepareRecordOverlay();
exerciseForm.value = { ...emptyExerciseForm(), visible: true, loading: true };
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyTodayExerciseRecord",
method: "GET",
data: { diagnosis_id: diagnosisId.value }
}, false);
if ((res == null ? void 0 : res.code) === 1 && ((_a = res.data) == null ? void 0 : _a.record)) {
const r = res.data.record;
exerciseForm.value.id = Number(r.id) || 0;
exerciseForm.value.exercise_type = String(r.exercise_type || "");
exerciseForm.value.duration = numToStr(r.duration);
exerciseForm.value.intensity = Number(r.intensity) || 2;
exerciseForm.value.note = String(r.note || "");
if (todayHasExercise.value) {
showUserToast("今日运动已录入,如需修改可在下方调整", { duration: 2600 });
}
}
} catch (e) {
} finally {
exerciseForm.value.loading = false;
}
}
function closeExerciseForm() {
if (exerciseForm.value.submitting)
return;
cancelVoiceHold();
exerciseForm.value.visible = false;
common_vendor.nextTick$1(() => {
});
}
async function submitExerciseForm() {
if (exerciseForm.value.submitting || exerciseForm.value.loading)
return;
const f = exerciseForm.value;
if (!f.exercise_type.trim() && !f.duration) {
showUserToast("请填写运动类型或时长");
return;
}
exerciseForm.value.submitting = true;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailySaveExerciseRecord",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
exercise_type: f.exercise_type,
duration: f.duration,
intensity: f.intensity,
note: f.note
}
}, false);
if ((res == null ? void 0 : res.code) === 1) {
showUserToast(res.msg || "今日运动已录入完成", { icon: "success" });
exerciseForm.value.visible = false;
await fetchAll();
await fetchGamifyState();
promptWaterTreeGuide("运动");
} else {
showUserToast(res && res.msg || "保存失败");
}
} catch (e) {
showUserToast("网络异常,请稍后再试");
} finally {
exerciseForm.value.submitting = false;
}
}
async function submitBpForm() {
if (bpForm.value.submitting || bpForm.value.loading)
return;
const f = bpForm.value;
const hasAny = [
f.systolic_pressure,
f.diastolic_pressure
].some((v) => v !== "" && v !== null && Number(v) > 0) || !!(String(f.western_medicine || "").trim() || String(f.insulin || "").trim() || String(f.remark || "").trim());
if (!hasAny) {
common_vendor.index.showToast({ title: "请至少填写一项数据", icon: "none" });
return;
}
bpForm.value.submitting = true;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailySaveBloodRecord",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
systolic_pressure: f.systolic_pressure,
diastolic_pressure: f.diastolic_pressure,
western_medicine: f.western_medicine,
insulin: f.insulin,
remark: f.remark
}
}, false);
if (res && res.code === 1) {
showUserToast(res.msg || "今日血压已录入完成", { icon: "success" });
bpForm.value.visible = false;
await fetchAll();
await fetchGamifyState();
promptWaterTreeGuide("血压");
} else {
showUserToast(res && res.msg || "保存失败");
}
} catch (e) {
common_vendor.index.showToast({ title: "网络异常,请稍后再试", icon: "none" });
} finally {
bpForm.value.submitting = false;
}
}
function deleteBpRecord() {
if (!bpForm.value.id || bpForm.value.submitting)
return;
common_vendor.index.showModal({
title: "删除确认",
content: "确定删除今天已录入的数据吗?删除后无法恢复。",
confirmColor: "#dc2626",
success: async (m) => {
if (!m.confirm)
return;
bpForm.value.submitting = true;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyDeleteBloodRecord",
method: "POST",
data: {
diagnosis_id: diagnosisId.value,
id: bpForm.value.id
}
}, false);
if (res && res.code === 1) {
common_vendor.index.showToast({ title: "已删除", icon: "success" });
bpForm.value.visible = false;
fetchAll();
} else {
showUserToast(res && res.msg || "删除失败");
}
} catch (e) {
common_vendor.index.showToast({ title: "网络异常,请稍后再试", icon: "none" });
} finally {
bpForm.value.submitting = false;
}
}
});
}
const ttsEnabled = common_vendor.ref(true);
const ttsAutoPlay = common_vendor.ref(false);
const ttsSpeaking = common_vendor.ref(false);
let ttsAudioCtx = null;
let ttsAutoToken = 0;
let ttsPlayToken = 0;
function teardownTtsAudio() {
if (ttsAudioCtx) {
try {
ttsAudioCtx.stop();
} catch (e) {
}
try {
ttsAudioCtx.destroy && ttsAudioCtx.destroy();
} catch (e) {
}
ttsAudioCtx = null;
}
}
function ttsStop() {
ttsPlayToken += 1;
teardownTtsAudio();
ttsSpeaking.value = false;
}
function normalizeTtsText(text) {
if (text == null)
return "";
if (typeof text === "string")
return text.replace(/\s+/g, " ").trim();
if (Array.isArray(text)) {
return text.map((item) => {
if (typeof item === "string")
return item;
if (item && typeof item === "object") {
return item.title || item.label || item.desc || item.msg || item.message || "";
}
return formatUserMessage(item, "");
}).filter(Boolean).join(",");
}
if (typeof text === "object") {
return text.title || text.label || text.desc || text.msg || text.message || "";
}
return formatUserMessage(text, "");
}
function ttsSpeak(text, opts = {}) {
const { force = false, onDone } = opts || {};
let doneCalled = false;
const fireDone = () => {
if (doneCalled)
return;
doneCalled = true;
if (typeof onDone === "function") {
try {
onDone();
} catch (e) {
}
}
};
if (!ttsEnabled.value && !force) {
fireDone();
return;
}
const content = normalizeTtsText(text);
if (!content) {
fireDone();
return;
}
ttsStop();
const myToken = ++ttsPlayToken;
try {
const plugin = requirePlugin("WechatSI");
ttsSpeaking.value = true;
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content,
success: (res) => {
if (myToken !== ttsPlayToken) {
fireDone();
return;
}
if (!res || !res.filename) {
ttsSpeaking.value = false;
fireDone();
return;
}
teardownTtsAudio();
ttsAudioCtx = common_vendor.index.createInnerAudioContext();
ttsAudioCtx.src = res.filename;
ttsAudioCtx.obeyMuteSwitch = false;
ttsAudioCtx.onPlay(() => {
ttsSpeaking.value = true;
});
ttsAudioCtx.onEnded(() => {
ttsSpeaking.value = false;
fireDone();
});
ttsAudioCtx.onError(() => {
ttsSpeaking.value = false;
fireDone();
});
ttsAudioCtx.onStop(() => {
ttsSpeaking.value = false;
fireDone();
});
ttsAudioCtx.play();
},
fail: (err) => {
ttsSpeaking.value = false;
common_vendor.index.__f__("warn", "at tongji/pages/more.vue:3186", "WechatSI textToSpeech failed", err);
common_vendor.index.showToast({ title: "语音不可用,请检查网络/插件", icon: "none", duration: 2e3 });
fireDone();
}
});
return;
} catch (e) {
common_vendor.index.__f__("warn", "at tongji/pages/more.vue:3193", "WechatSI plugin not available", e);
}
showUserToast(content, { duration: 3200 });
setTimeout(fireDone, Math.min(6e3, 1200 + content.length * 180));
}
function readableNumber(v) {
if (v === null || v === void 0 || v === "")
return "";
return String(v).replace(/(\d+)\.(\d+)/g, (_, a, b) => `${a}点${b}`);
}
function buildLatestSummaryText() {
const f = latestStat.value.fasting;
const p = latestStat.value.postprandial;
const o = latestStat.value.other;
const head = `${patientName.value || "您"}的最新血糖情况:`;
const lines = [];
if (f && f.value !== "—") {
let line = `空腹血糖 ${readableNumber(f.value)} 毫摩每升`;
if (f.isHigh)
line += ",偏高,请遵医嘱。";
else
line += "。";
if (f.trend && f.trend.dir !== "flat") {
line += `比上次${f.trend.dir === "up" ? "升高" : "下降"} ${readableNumber(f.trend.delta.toFixed(1))}。`;
}
lines.push(line);
}
if (p && p.value !== "—") {
let line = `餐后血糖 ${readableNumber(p.value)} 毫摩每升`;
if (p.isHigh)
line += ",偏高,请注意饮食。";
else
line += "。";
if (p.trend && p.trend.dir !== "flat") {
line += `比上次${p.trend.dir === "up" ? "升高" : "下降"} ${readableNumber(p.trend.delta.toFixed(1))}。`;
}
lines.push(line);
}
if (o && o.value !== "—") {
let line = `其他时段血糖 ${readableNumber(o.value)} 毫摩每升`;
if (o.isHigh)
line += ",偏高,请遵医嘱。";
else
line += "。";
if (o.trend && o.trend.dir !== "flat") {
line += `比上次${o.trend.dir === "up" ? "升高" : "下降"} ${readableNumber(o.trend.delta.toFixed(1))}。`;
}
lines.push(line);
}
if (!lines.length) {
return `${patientName.value || "您"}近期暂无血糖记录。`;
}
return head + lines.join(" ");
}
function speakLatestSummary() {
const text = buildLatestSummaryText();
if (text)
ttsSpeak(text);
}
function readUserContext() {
try {
const userData = common_vendor.index.getStorageSync("userData");
if (!userData)
return;
const name = String(userData.real_name || userData.nickname || "").trim();
if (!patientName.value && name)
patientName.value = name;
const sex = Number(userData.sex);
if (!gender.value && (sex === 1 || sex === 2))
gender.value = sex === 1 ? 1 : 0;
if (!age.value && userData.age)
age.value = Number(userData.age) || 0;
} catch (e) {
}
}
function initHeaderSafeArea() {
const sys = common_vendor.index.getSystemInfoSync();
const statusBar = Number(sys.statusBarHeight) || 0;
const greenBlockH = common_vendor.index.upx2px(300);
let capsulePad = common_vendor.index.upx2px(24);
try {
const menu = typeof common_vendor.index.getMenuButtonBoundingClientRect === "function" ? common_vendor.index.getMenuButtonBoundingClientRect() : null;
if (menu && menu.width > 0) {
capsulePad = sys.windowWidth - menu.left + common_vendor.index.upx2px(8);
}
} catch (e) {
}
const headerTotal = statusBar + greenBlockH;
headerStyle.value = {
height: `${headerTotal - 35}px`,
paddingTop: `${statusBar}px`,
boxSizing: "border-box"
};
headerInnerStyle.value = {
paddingRight: `${capsulePad}px`
};
mainPadStyle.value = {
paddingTop: `${headerTotal + common_vendor.index.upx2px(24)}px`
};
}
async function bootstrap(options) {
options = options || {};
initHeaderSafeArea();
gateRedirected = false;
authChecking.value = true;
const passedDiagnosisId = Number(options.diagnosis_id || options.id || 0);
Number(options.patient_id || 0);
options.patient_name ? decodeURIComponent(String(options.patient_name)) : "";
Number(options.age) || 0;
Number(options.gender) || 0;
const loggedIn = await ensureLoggedIn();
if (!loggedIn || !hasAuthToken()) {
authChecking.value = false;
showUserToast("登录失败,请稍后重试");
return;
}
readUserContext();
const list = await fetchCardList();
if (Array.isArray(list) && list.length > 0) {
let target = null;
if (passedDiagnosisId) {
target = list.find((c) => Number(c.id) === Number(passedDiagnosisId));
}
if (!target)
target = list[0];
await applyCard(target);
} else {
diagnosisId.value = 0;
patientId.value = 0;
patientName.value = "";
age.value = 0;
gender.value = 0;
if (readUserMobile()) {
await ensureDailyContextByPhone();
}
if (!diagnosisId.value) {
authChecking.value = false;
if (readUserMobile()) {
showUserToast("暂时无法加载记录,请稍后重试");
} else {
showUserToast("请先在统计页授权手机号后再查看记录");
}
setTimeout(() => {
const pages = getCurrentPages();
if (pages.length > 1)
common_vendor.index.navigateBack();
else
common_vendor.index.redirectTo({ url: "/tongji/pages/weekly" });
}, 1600);
return;
}
}
authChecking.value = false;
await fetchAll();
if (options.openDiet === "1" || options.openDiet === 1) {
await openDietForm({ autoFill: true });
}
}
common_vendor.onLoad((options) => {
bootstrap(options);
});
common_vendor.onShareAppMessage(() => {
const d = weeklyReportData.value;
const code = pendingShareInviteCode.value;
const title = shareTitle.value || `【稳糖打卡】${d.patientLabel}本周记录${d.recordDays}天(邀请码今日有效)`;
const path = code ? `/tongji/pages/index?from=share&invite_code=${code}` : "/tongji/pages/more";
return { title, path };
});
common_vendor.onShow(() => {
initHeaderSafeArea();
if (authChecking.value || gateRedirected)
return;
if (diagnosisId.value) {
loadFamilyLikeSummary();
}
});
common_vendor.onPullDownRefresh(async () => {
if (await ensureLoggedIn()) {
await fetchAll();
}
common_vendor.index.stopPullDownRefresh();
});
common_vendor.onHide(() => {
cancelVoiceHold();
stopVoiceDialog();
ttsStop();
});
common_vendor.onUnload(() => {
cancelVoiceHold();
stopVoiceDialog();
ttsStop();
clearWateringFx();
});
const gamifyPoints = common_vendor.ref(0);
const gamifyBadges = common_vendor.ref([]);
const gamifyTaskAwards = common_vendor.ref({});
const gamifyTodayTasks = common_vendor.ref([]);
const gamifyClaimablePoints = common_vendor.ref(0);
const gamifyStatusTip = common_vendor.ref("");
const treeWaterLocked = common_vendor.ref(false);
const treePulseActive = common_vendor.ref(false);
function isApiSuccess(res) {
return !!(res && Number(res.code) === 1);
}
function applyGamifyPayload(data) {
if (!data || typeof data !== "object")
return;
gamifyPoints.value = Number(data.points) || 0;
gamifyBadges.value = Array.isArray(data.badges) ? [...data.badges] : [];
try {
gamifyTaskAwards.value = data.task_awards ? JSON.parse(JSON.stringify(data.task_awards)) : {};
} catch (e) {
gamifyTaskAwards.value = data.task_awards || {};
}
gamifyTodayTasks.value = Array.isArray(data.today_tasks) ? normalizeGamifyTasks(data.today_tasks.map((t) => ({
id: String(t.id || ""),
name: String(t.name || ""),
points: Number(t.points) || 0,
completed: !!t.completed,
claimed: !!t.claimed
}))) : [];
gamifyClaimablePoints.value = Number(data.claimable_points) || 0;
if (data.message) {
gamifyStatusTip.value = String(data.message);
}
}
async function fetchGamifyState() {
if (!hasAuthToken() || !diagnosisId.value)
return;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyGetGamify",
method: "GET",
data: { diagnosis_id: diagnosisId.value }
}, false);
if (isApiSuccess(res) && res.data) {
applyGamifyPayload(res.data);
}
} catch (e) {
common_vendor.index.__f__("error", "at tongji/pages/more.vue:3893", "获取游戏化数据失败", e);
}
}
const todayHasGlucose = common_vendor.computed(() => {
const b = bloodByDate.value[todayStr.value];
if (!b)
return false;
return toNumber(b.fasting_blood_sugar) !== null || toNumber(b.postprandial_blood_sugar) !== null || toNumber(b.other_blood_sugar) !== null;
});
const todayHasBpPressure = common_vendor.computed(() => {
const b = bloodByDate.value[todayStr.value];
if (!b)
return false;
return toNumber(b.systolic_pressure) !== null || toNumber(b.diastolic_pressure) !== null;
});
const todayHasDiet = common_vendor.computed(() => {
const today = todayStr.value;
const d = dietByDate.value[today];
return !!(d && (d.breakfast_foods || d.lunch_foods || d.dinner_foods));
});
const todayHasExercise = common_vendor.computed(() => {
const today = todayStr.value;
const ex = exerciseByDate.value[today];
return !!(ex && (ex.exercise_type || ex.duration));
});
const GAMIFY_TASK_ORDER = ["glucose", "bp", "diet", "exercise"];
const GAMIFY_TASK_ICONS = {
glucose: "glucose",
bp: "activity",
diet: "utensils",
exercise: "flame"
};
function isGamifyTaskClaimed(id, awards, completion = {}) {
if (awards[id])
return true;
if (awards.blood) {
if (id === "glucose" && completion.glucose)
return true;
if (id === "bp" && completion.bp)
return true;
}
return false;
}
function gamifyTaskCompletionMap() {
return {
glucose: todayHasGlucose.value,
bp: todayHasBpPressure.value,
diet: todayHasDiet.value,
exercise: todayHasExercise.value
};
}
function resolveGamifyTaskCompleted(id, fallback = false) {
const map = gamifyTaskCompletionMap();
if (Object.prototype.hasOwnProperty.call(map, id)) {
return !!map[id];
}
return !!fallback;
}
function gamifyTaskIcon(id) {
return GAMIFY_TASK_ICONS[id] || "check-circle";
}
function normalizeGamifyTasks(tasks) {
if (!Array.isArray(tasks) || !tasks.length)
return [];
const ids = new Set(tasks.map((t) => t.id));
if (ids.has("glucose") || ids.has("bp")) {
return tasks;
}
if (ids.has("blood")) {
const blood = tasks.find((t) => t.id === "blood");
const others = tasks.filter((t) => t.id !== "blood");
if (!blood)
return tasks;
return [
{ ...blood, id: "glucose", name: "测血糖" },
{ ...blood, id: "bp", name: "测血压" },
...others
];
}
return tasks;
}
const todayTasks = common_vendor.computed(() => {
const today = todayStr.value;
const awards = gamifyTaskAwards.value[today] || {};
const completion = gamifyTaskCompletionMap();
const mapTask = (t) => ({
...t,
completed: resolveGamifyTaskCompleted(t.id, t.completed),
claimed: isGamifyTaskClaimed(t.id, awards, completion)
});
if (gamifyTodayTasks.value.length) {
return normalizeGamifyTasks(gamifyTodayTasks.value).map(mapTask);
}
return [
{
id: "glucose",
name: "测血糖",
points: 10,
completed: completion.glucose,
claimed: isGamifyTaskClaimed("glucose", awards, completion)
},
{
id: "bp",
name: "测血压",
points: 10,
completed: completion.bp,
claimed: isGamifyTaskClaimed("bp", awards, completion)
},
{
id: "diet",
name: "饮食",
points: 10,
completed: completion.diet,
claimed: isGamifyTaskClaimed("diet", awards, completion)
},
{
id: "exercise",
name: "运动",
points: 10,
completed: completion.exercise,
claimed: isGamifyTaskClaimed("exercise", awards, completion)
}
];
});
const displayTasksForUi = common_vendor.computed(() => {
const map = new Map(todayTasks.value.map((t) => [t.id, t]));
return GAMIFY_TASK_ORDER.map((id) => map.get(id)).filter(Boolean);
});
const claimablePoints = common_vendor.computed(() => {
return todayTasks.value.filter((t) => t.completed && !t.claimed).reduce((sum, t) => sum + t.points, 0);
});
const canWaterTree = common_vendor.computed(() => claimablePoints.value > 0);
const displayTasksAllCleared = common_vendor.computed(() => {
const tasks = displayTasksForUi.value;
return tasks.length > 0 && tasks.every((t) => t.completed && t.claimed);
});
const firstPendingDisplayTask = common_vendor.computed(() => {
return displayTasksForUi.value.find((t) => !t.completed) || null;
});
const waterHintText = common_vendor.computed(() => {
if (canWaterTree.value) {
return `有 ${claimablePoints.value} 积分待领,点击浇水查看进度并领取`;
}
if (displayTasksAllCleared.value) {
return "今日任务已全部完成,小树喝饱啦 🌱";
}
const pending = firstPendingDisplayTask.value;
if (pending) {
return `还差「${pending.name}」等项,点击浇水查看今日进度`;
}
return "点击浇水查看今日任务进度";
});
const allTasksCleared = common_vendor.computed(() => {
const tasks = todayTasks.value;
return tasks.length > 0 && tasks.every((t) => t.completed && t.claimed);
});
common_vendor.computed(() => {
var _a;
const list = [];
const streak = Number((_a = streakDays.value) == null ? void 0 : _a.days) || 0;
if (todayChecked.value) {
list.push({ id: "today", label: "今日打卡", icon: "check-circle", color: "#386641" });
}
if (streak >= 3) {
list.push({ id: "streak", label: `连击${streak}天`, icon: "flame", color: "#EA580C" });
}
if (streak >= 7) {
list.push({ id: "week", label: "一周达人", icon: "trophy", color: "#CA8A04" });
}
if (last7CheckedCount.value >= 5) {
list.push({ id: "w5", label: "本周5天+", icon: "calendar", color: "#204E2B" });
}
if (gamifyPoints.value >= 50) {
list.push({ id: "p50", label: "积分50+", icon: "sparkles", color: "#204E2B" });
}
if (gamifyPoints.value >= 100) {
list.push({ id: "p100", label: "积分破百", icon: "trophy", color: "#7C3AED" });
}
if (gamifyPoints.value >= 200) {
list.push({ id: "p200", label: "积分200+", icon: "sparkles", color: "#7C3AED" });
}
if (treeLevel.value >= 3) {
list.push({ id: "tree3", label: "小树苗", icon: "glucose", color: "#0D9488" });
}
if (treeLevel.value >= 5) {
list.push({ id: "tree5", label: "拔节高", icon: "activity", color: "#386641" });
}
if (treeLevel.value >= 7) {
list.push({ id: "tree7", label: "初绽香", icon: "heart", color: "#EA580C" });
}
if (treeState.value.isMax) {
list.push({ id: "tree9", label: "圆满树", icon: "trophy", color: "#CA8A04" });
}
return list;
});
const celebrateShow = common_vendor.ref(false);
const celebrateTitle = common_vendor.ref("");
const celebrateSub = common_vendor.ref("");
const floatReward = common_vendor.ref(null);
common_vendor.ref(false);
const taskJustDoneId = common_vendor.ref("");
let celebrateTimer = null;
let taskPopTimer = null;
const waterDroplets = common_vendor.ref([]);
const sparkles = common_vendor.ref([]);
const sugarDecrease = common_vendor.ref(null);
const sugarParticles = common_vendor.ref([]);
let fxTimers = [];
function clearWateringFx() {
fxTimers.forEach((t) => clearTimeout(t));
fxTimers = [];
waterDroplets.value = [];
sparkles.value = [];
sugarDecrease.value = null;
sugarParticles.value = [];
}
function startRain() {
const base = Date.now();
const drops = [];
for (let i = 0; i < 15; i++) {
drops.push({
key: `d${base}_${i}`,
left: Math.random() * 100,
delay: Math.random() * 0.4
});
}
waterDroplets.value = drops;
fxTimers.push(setTimeout(() => {
waterDroplets.value = [];
}, 600));
}
function burstWateringFx() {
const base = Date.now();
const sps = [];
for (let i = 0; i < 12; i++) {
sps.push({
key: `s${base}_${i}`,
left: 40 + (Math.random() - 0.5) * 40,
top: 40 + (Math.random() - 0.5) * 40,
dur: 0.6 + Math.random() * 0.4
});
}
sparkles.value = sps;
fxTimers.push(setTimeout(() => {
sparkles.value = [];
}, 1e3));
sugarDecrease.value = { key: `sd${base}` };
fxTimers.push(setTimeout(() => {
sugarDecrease.value = null;
}, 2e3));
const parts = [];
for (let i = 0; i < 20; i++) {
parts.push({
key: `p${base}_${i}`,
left: Math.random() * 100,
size: 32 + Math.random() * 32,
delay: i * 0.05,
kind: Math.random() > 0.5 ? "south" : "tri"
});
}
sugarParticles.value = parts;
fxTimers.push(setTimeout(() => {
sugarParticles.value = [];
}, 1500));
}
function scheduleWateringBurstFx() {
fxTimers.push(setTimeout(() => {
treePulseActive.value = true;
burstWateringFx();
fxTimers.push(setTimeout(() => {
treePulseActive.value = false;
}, 800));
}, 600));
}
function hapticLight() {
try {
common_vendor.index.vibrateShort({ type: "light" });
} catch (e) {
}
}
function triggerCelebrate(title, sub = "") {
if (celebrateTimer)
clearTimeout(celebrateTimer);
celebrateTitle.value = title;
celebrateSub.value = sub;
celebrateShow.value = true;
celebrateTimer = setTimeout(() => {
celebrateShow.value = false;
celebrateSub.value = "";
}, 2400);
}
function showFloatReward(points) {
floatReward.value = { text: points, key: Date.now() };
setTimeout(() => {
floatReward.value = null;
}, 1100);
}
function popTaskDone(taskId) {
if (!taskId)
return;
taskJustDoneId.value = taskId;
if (taskPopTimer)
clearTimeout(taskPopTimer);
taskPopTimer = setTimeout(() => {
taskJustDoneId.value = "";
}, 700);
}
function onTreeWhisper() {
if (treeWaterLocked.value)
return;
const line = tongji_utils_treeLevels.pickTreeWhisper(treeLevel.value);
showUserToast(line, { duration: 2800 });
hapticLight();
}
function promptWaterTreeGuide(taskLabel = "", delayMs = 900) {
setTimeout(() => {
if (claimablePoints.value > 0) {
const prefix = taskLabel ? `今日${taskLabel}已录入完成,` : "今日已录入完成,";
showUserToast(
`${prefix}点击「给小树浇水」领取 ${claimablePoints.value} 积分`,
{ duration: 3200 }
);
return;
}
const pending = firstPendingDisplayTask.value;
if (pending) {
const prefix = taskLabel ? `今日${taskLabel}已录入完成。` : "今日该项已录入完成。";
showUserToast(
`${prefix}继续完成「${pending.name}」,再给小树浇水领积分`,
{ duration: 3200 }
);
return;
}
if (displayTasksAllCleared.value) {
showUserToast("今日任务已全部完成,小树喝饱啦 🌱", { duration: 2800 });
}
}, delayMs);
}
async function waterTree() {
if (!diagnosisId.value)
return;
if (treeWaterLocked.value)
return;
if (!canWaterTree.value)
return;
const oldLevel = treeLevel.value;
treeWaterLocked.value = true;
clearWateringFx();
startRain();
scheduleWateringBurstFx();
hapticLight();
fxTimers.push(setTimeout(() => {
treeWaterLocked.value = false;
}, 1400));
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyWaterTree",
method: "POST",
data: { diagnosis_id: diagnosisId.value }
}, false);
if (!isApiSuccess(res)) {
showUserToast(res && res.msg || "浇水失败");
return;
}
const data = res.data && typeof res.data === "object" ? res.data : {};
const addedPoints = Number(data.added_points) || 0;
applyGamifyPayload({
...data,
message: data.message || res.msg || ""
});
await common_vendor.nextTick$1();
const tip = formatUserMessage(data.message || res.msg, "");
if (addedPoints > 0) {
showFloatReward(addedPoints);
hapticLight();
closeWaterStatusPanel();
triggerCelebrate("浇水成功", tip || `+${addedPoints} 稳糖积分,小树咕咚喝饱啦`);
const claimedIds = Array.isArray(data.claimed_tasks) ? data.claimed_tasks : [];
claimedIds.forEach((id) => popTaskDone(id));
const newLevel = treeLevel.value;
if (newLevel > oldLevel) {
const meta = tongji_utils_treeLevels.TREE_LEVELS[newLevel] || tongji_utils_treeLevels.TREE_LEVELS[0];
setTimeout(() => triggerCelebrate("小树升级啦", `Lv.${newLevel} ${meta.name} · ${meta.desc}`), 520);
}
setTimeout(() => {
if (todayTasks.value.every((t) => t.completed && t.claimed)) {
triggerCelebrate("今日任务大满贯", "明天继续来浇浇水吧");
}
}, 900);
return;
}
if (tip) {
showUserToast(tip, { duration: 2800 });
}
const needRecord = todayTasks.value.find((t) => !t.completed && !t.claimed);
if (needRecord) {
onGamifyTaskTap(needRecord);
return;
}
const pending = Array.isArray(data.pending_tasks) ? data.pending_tasks : [];
if (pending.length) {
const match = todayTasks.value.find((t) => t.id === pending[0].id) || todayTasks.value.find((t) => t.name === pending[0].name);
if (match && !match.completed) {
onGamifyTaskTap(match);
} else if (!tip) {
showUserToast(`先完成:${pending[0].name}`, { duration: 2800 });
}
return;
}
if (Number(data.claimable_points) > 0) {
showUserToast("还有积分待领取,请再点一次浇水");
return;
}
if (allTasksCleared.value && !tip) {
showUserToast("今日奖励已全部领取", { duration: 2800 });
}
} catch (e) {
showUserToast("浇水失败,请稍后重试");
}
}
const treeState = common_vendor.computed(() => tongji_utils_treeLevels.calcTreeFromPoints(gamifyPoints.value));
const treeLevel = common_vendor.computed(() => treeState.value.level);
const treeProgress = common_vendor.computed(() => treeState.value.progress);
const treeLevelName = common_vendor.computed(() => treeState.value.name);
const treeEmoji = common_vendor.computed(() => tongji_utils_treeSpecies.speciesEmojiAtLevel(treeSpeciesId.value, treeLevel.value));
common_vendor.computed(() => treeState.value.mood);
const treeSpeciesId = common_vendor.ref("classic");
const treeSpeciesPickerVisible = common_vendor.ref(false);
const speciesDaysToMax = tongji_utils_treeSpecies.estimateDaysToMaxLevel();
const currentTreeSpecies = common_vendor.computed(() => tongji_utils_treeSpecies.getTreeSpecies(treeSpeciesId.value));
function treeSpeciesStorageKey(dId = diagnosisId.value) {
return `tongji_tree_species_v1_${Number(dId) || 0}`;
}
function loadTreeSpecies(dId = diagnosisId.value) {
if (!dId) {
treeSpeciesId.value = "classic";
return;
}
try {
const saved = common_vendor.index.getStorageSync(treeSpeciesStorageKey(dId));
if (saved && tongji_utils_treeSpecies.getTreeSpecies(saved)) {
treeSpeciesId.value = saved;
return;
}
} catch (e) {
}
treeSpeciesId.value = "classic";
}
function selectTreeSpecies(id) {
if (!tongji_utils_treeSpecies.getTreeSpecies(id))
return;
treeSpeciesId.value = id;
try {
common_vendor.index.setStorageSync(treeSpeciesStorageKey(), id);
} catch (e) {
}
treeSpeciesPickerVisible.value = false;
showUserToast(`已切换为「${tongji_utils_treeSpecies.getTreeSpecies(id).name}」`, { duration: 2200 });
hapticLight();
}
function openTreeSpeciesPicker() {
treeSpeciesPickerVisible.value = true;
}
function closeTreeSpeciesPicker() {
treeSpeciesPickerVisible.value = false;
}
const waterStatusVisible = common_vendor.ref(false);
const waterStatusRows = common_vendor.computed(() => {
return displayTasksForUi.value.map((t) => {
let statusLabel = "未记录";
let statusKind = "pending";
let statusSub = "今日尚未打开此项";
if (t.claimed) {
statusLabel = "已完成";
statusKind = "claimed";
statusSub = "今日已录入,积分已领取";
} else if (t.completed) {
statusLabel = "待浇水";
statusKind = "water";
statusSub = `今日已录入,浇水可领 +${t.points} 积分`;
}
return { ...t, statusLabel, statusKind, statusSub };
});
});
const waterStatusSummary = common_vendor.computed(() => {
if (displayTasksAllCleared.value) {
return "今日四项任务已全部完成并领取,小树喝饱啦,明天继续加油 🌱";
}
const pending = displayTasksForUi.value.filter((t) => !t.completed);
const toWater = displayTasksForUi.value.filter((t) => t.completed && !t.claimed);
if (canWaterTree.value) {
return `今日已打开 ${toWater.length} 项记录,浇水可领 ${claimablePoints.value} 积分`;
}
if (pending.length) {
return `今日还差 ${pending.length} 项未打开:${pending.map((t) => t.name).join("、")}`;
}
return "先完成今日记录,再给小树浇水领积分";
});
const waterStatusGrowthHint = common_vendor.computed(() => {
const sp = currentTreeSpecies.value;
if (treeState.value.isMax) {
return `${sp.name} 已满级 · 继续打卡巩固习惯`;
}
return `${sp.name} · Lv.${treeLevel.value} ${treeLevelName.value} · 再积 ${treeState.value.pointsToNext} 分升级「${treeState.value.nextName}」`;
});
function onWaterTreeTap() {
if (treeWaterLocked.value)
return;
openWaterStatusPanel();
hapticLight();
}
function openWaterStatusPanel() {
waterStatusVisible.value = true;
}
function closeWaterStatusPanel() {
waterStatusVisible.value = false;
}
function waterTreeFromPanel() {
closeWaterStatusPanel();
waterTree();
}
function goRecordFromWaterPanel() {
const pending = firstPendingDisplayTask.value;
if (!pending) {
closeWaterStatusPanel();
return;
}
closeWaterStatusPanel();
onGamifyTaskTap(pending, { skipGuide: true });
}
const treeProgressLabel = common_vendor.computed(() => {
if (treeState.value.isMax)
return "已满级 · 圆满树";
return `${treeState.value.xpInLevel}/${treeState.value.xpNeed} XP`;
});
common_vendor.computed(() => {
if (treeState.value.isMax)
return "您已养成满级稳糖树,习惯就是最好的奖励";
if (claimablePoints.value > 0)
return `浇水后可再积 ${claimablePoints.value} 分,离升级更近一步`;
return `再积 ${treeState.value.pointsToNext} 分,升级「${treeState.value.nextName}」`;
});
const treeXpUpgradeHint = common_vendor.computed(() => {
if (treeState.value.isMax)
return "已满级,继续浇水巩固习惯";
const need = treeState.value.pointsToNext;
const nextLv = treeLevel.value + 1;
return `还差 ${need} XP 升级至 Lv.${nextLv}`;
});
const weeklyReportVisible = common_vendor.ref(false);
const shareTitle = common_vendor.ref("");
const pendingShareInviteCode = common_vendor.ref("");
const familyLikeSummary = common_vendor.ref({
like_count: 0,
summary_line: "分享战报给家人,邀请他们为您点赞鼓劲",
recent: []
});
const lastFamilyLikeCount = common_vendor.ref(0);
function familyLikeSeenStorageKey(dId = diagnosisId.value) {
return `tongji_family_like_seen_${Number(dId) || 0}_${todayStr.value}`;
}
function getStoredFamilyLikeSeen(dId = diagnosisId.value) {
try {
return Number(common_vendor.index.getStorageSync(familyLikeSeenStorageKey(dId))) || 0;
} catch (e) {
return 0;
}
}
function setStoredFamilyLikeSeen(count, dId = diagnosisId.value) {
try {
common_vendor.index.setStorageSync(familyLikeSeenStorageKey(dId), Number(count) || 0);
} catch (e) {
}
}
function getWeekOfYear(date) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil(((d - yearStart) / 864e5 + 1) / 7);
}
const weeklyReportData = common_vendor.computed(() => {
const name = (patientName.value || "我").trim();
const patientLabel = name.length > 6 ? `${name.slice(0, 6)}…` : name;
const now = /* @__PURE__ */ new Date();
const weekLabel = `${now.getFullYear()}年 第${getWeekOfYear(now)}周`;
const rd = last7CheckedCount.value;
const cd = weekCompleteDays.value;
let quote = "坚持记录,就是在照顾未来的自己。";
if (rd >= 7)
quote = "本周每天都留下了记录,这份自律值得骄傲!";
else if (cd >= 5)
quote = `本周有 ${cd} 天完成了空腹+餐后记录,习惯越来越稳。`;
else if (rd >= 4)
quote = `本周已记录 ${rd} 天,比不记录强一百倍。`;
else if (rd > 0)
quote = "好的开始!下周试着多记录几天。";
else
quote = "从今天记一笔开始,小树就会发芽。";
return {
patientLabel,
weekLabel,
recordDays: rd,
completeDays: cd,
streakDays: streakDays.value.days,
quote
};
});
const reportTreeLevel = common_vendor.computed(() => treeLevel.value);
common_vendor.computed(() => Number(invitePreview.value.tree_level) || 0);
common_vendor.computed(() => {
const list = invitePreview.value.daily_records;
return Array.isArray(list) ? list : [];
});
const ENCOURAGE_LINES = [
"每天记一笔,就是在照顾未来的自己。",
"坚持记录比纠结数值更重要,您做得很好。",
"给树浇浇水,也给自己点个赞。",
"家人会看到您的努力,习惯比完美更珍贵。",
"空腹、餐后各记一次,完整一日更有成就感。"
];
const encourageIndex = common_vendor.ref(0);
const encourageLine = common_vendor.computed(() => {
const pending = displayTasksForUi.value.find((t) => !t.completed);
if ((pending == null ? void 0 : pending.id) === "glucose")
return "先完成血糖记录,再给小树浇水领积分。";
if ((pending == null ? void 0 : pending.id) === "bp")
return "先测血压并记录,再给小树浇水领积分。";
if ((pending == null ? void 0 : pending.id) === "diet")
return "记下今日饮食,再给小树浇水领积分。";
if ((pending == null ? void 0 : pending.id) === "exercise")
return "动一动、记一记,再给小树浇水领积分。";
if (canWaterTree.value) {
return `有 ${claimablePoints.value} 积分待领,点击「给小树浇水」吧。`;
}
if (displayTasksAllCleared.value) {
return "今日任务已全部完成,小树喝饱啦,明天继续加油。";
}
return ENCOURAGE_LINES[encourageIndex.value % ENCOURAGE_LINES.length];
});
const encourageParts = common_vendor.computed(() => {
const line = encourageLine.value;
const highlights = ["给小树浇水", "今日第一格就亮起来"];
for (const highlight of highlights) {
if (line.includes(highlight)) {
const i = line.indexOf(highlight);
return {
prefix: line.slice(0, i),
highlight,
suffix: line.slice(i + highlight.length)
};
}
}
return { prefix: line, highlight: "", suffix: "" };
});
function onEncourageTap() {
encourageIndex.value = (encourageIndex.value + 1) % ENCOURAGE_LINES.length;
const line = ENCOURAGE_LINES[encourageIndex.value];
if (ttsEnabled.value) {
ttsSpeak(line);
} else {
showUserToast(line, { duration: 2800 });
}
}
function goBack() {
const pages = getCurrentPages();
if (pages.length > 1) {
common_vendor.index.navigateBack({ delta: 1 });
return;
}
common_vendor.index.navigateTo({ url: "/tongji/pages/weekly" });
}
function goGlucosePage() {
common_vendor.index.navigateTo({ url: "/tongji/pages/weekly" });
}
function onGamifyTaskTap(task, options = {}) {
if (!task)
return;
const skipGuide = !!options.skipGuide;
if (!task.completed) {
if (resolveGamifyTaskCompleted(task.id, task.completed)) {
showUserToast(
`今日「${task.name}」已录入${task.claimed ? ",积分已领取" : ",请去浇水领积分"}`,
{ duration: 2800 }
);
if (!task.claimed)
openWaterStatusPanel();
return;
}
if (!diagnosisId.value) {
showUserToast("请先选择就诊卡");
return;
}
const token = common_vendor.index.getStorageSync("token");
if (!token) {
showUserToast("请先登录后再录入");
return;
}
if (!skipGuide) {
showUserToast(`完成「${task.name}」后,点击「给小树浇水」领积分`, { duration: 2600 });
}
if (task.id === "glucose") {
openInputForm();
return;
}
if (task.id === "bp") {
openBpForm();
return;
}
if (task.id === "diet") {
openDietForm();
return;
}
if (task.id === "exercise") {
openExerciseForm();
return;
}
if (task.id === "blood") {
openInputForm();
return;
}
showUserToast(`请先完成:${task.name}`);
return;
}
if (task.claimed) {
showUserToast(`今日「${task.name}」已录入,积分已领取`);
return;
}
openWaterStatusPanel();
}
function shareInviteCacheKey() {
return `tongji_invite_${diagnosisId.value}_${todayStr.value}`;
}
async function ensureShareInvite() {
if (!diagnosisId.value)
return "";
const cacheKey = shareInviteCacheKey();
try {
const cached = common_vendor.index.getStorageSync(cacheKey);
if (cached && cached.invite_code && cached.invite_date === todayStr.value) {
pendingShareInviteCode.value = cached.invite_code;
return cached.invite_code;
}
} catch (e) {
}
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyCreateShareInvite",
method: "POST",
data: { diagnosis_id: diagnosisId.value }
}, false);
if (res && res.code === 1 && res.data && res.data.invite_code) {
const code = res.data.invite_code;
pendingShareInviteCode.value = code;
try {
common_vendor.index.setStorageSync(cacheKey, {
invite_code: code,
invite_date: todayStr.value
});
} catch (e) {
}
return code;
}
if (res == null ? void 0 : res.msg)
showUserToast(res.msg);
} catch (e) {
showUserToast("生成邀请码失败");
}
return "";
}
async function prepareShareWeekly() {
const code = await ensureShareInvite();
if (!code)
return;
const d = weeklyReportData.value;
shareTitle.value = `【稳糖打卡】${d.patientLabel}本周记录${d.recordDays}天,邀请码 ${code}(今日有效)`;
}
async function openWeeklyReport() {
if (!diagnosisId.value) {
showUserToast("请先选择就诊卡");
return;
}
await ensureShareInvite();
weeklyReportVisible.value = true;
}
function closeWeeklyReport() {
weeklyReportVisible.value = false;
}
async function loadFamilyLikeSummary() {
if (!hasAuthToken() || !diagnosisId.value)
return;
const token = common_vendor.index.getStorageSync("token");
if (!token)
return;
try {
const res = await proxy.apiUrl({
url: "/api/tcm/dailyFamilyLikeSummary",
method: "GET",
data: { diagnosis_id: diagnosisId.value }
}, false);
if (res && res.code === 1 && res.data) {
const storedSeen = getStoredFamilyLikeSeen();
const prev = Math.max(lastFamilyLikeCount.value, storedSeen);
const next = Number(res.data.like_count) || 0;
familyLikeSummary.value = {
like_count: next,
summary_line: res.data.summary_line || "",
recent: Array.isArray(res.data.recent) ? res.data.recent : []
};
if (next > prev) {
const delta = next - prev;
triggerCelebrate(
delta === 1 && prev === 0 ? "首个家人点赞" : "收到家人鼓励",
delta === 1 ? "分享真的有用,继续加油" : `新增 ${delta} 位家人点赞,共 ${next} 人`
);
hapticLight();
}
lastFamilyLikeCount.value = next;
setStoredFamilyLikeSeen(next);
}
} catch (e) {
}
}
function onFamilyLikeStripTap() {
if (familyLikeSummary.value.like_count > 0) {
if (ttsEnabled.value) {
ttsSpeak(familyLikeSummary.value.summary_line);
} else {
showUserToast(familyLikeSummary.value.summary_line, { duration: 2400 });
}
return;
}
openWeeklyReport();
}
return (_ctx, _cache) => {
return common_vendor.e({
a: common_vendor.p({
show: celebrateShow.value,
title: celebrateTitle.value,
subtitle: celebrateSub.value
}),
b: sugarParticles.value.length
}, sugarParticles.value.length ? {
c: common_vendor.f(sugarParticles.value, (p, k0, i0) => {
return {
a: common_vendor.t(p.kind === "south" ? "↓" : "▼"),
b: p.key,
c: p.kind === "south" ? 1 : "",
d: p.left + "vw",
e: p.size + "rpx",
f: p.delay + "s"
};
})
} : {}, {
d: authChecking.value
}, authChecking.value ? {} : {}, {
e: !authChecking.value
}, !authChecking.value ? common_vendor.e({
f: common_vendor.p({
name: "chevron-left",
size: "md",
color: "#ffffff"
}),
g: common_vendor.o(goBack, "82"),
h: common_vendor.t(patientName.value || "健康档案"),
i: common_vendor.p({
name: "glucose",
size: "sm",
color: "#ffffff"
}),
j: common_vendor.o(goGlucosePage, "5e"),
k: common_vendor.p({
name: "refresh",
size: "md",
color: "#ffffff"
}),
l: common_vendor.o(onRefresh, "a8"),
m: common_vendor.s(headerInnerStyle.value),
n: common_vendor.s(headerStyle.value),
o: common_vendor.t(gamifyPoints.value),
p: common_vendor.f(displayTasksForUi.value, (task, k0, i0) => {
return common_vendor.e({
a: task.completed
}, task.completed ? {
b: "5a4b0d9c-4-" + i0,
c: common_vendor.p({
name: "check",
size: "sm",
color: "#006c49"
})
} : {}, {
d: !task.completed ? 1 : "",
e: common_vendor.t(task.points),
f: "5a4b0d9c-5-" + i0,
g: common_vendor.p({
name: gamifyTaskIcon(task.id),
size: "sm",
color: "#006c49"
}),
h: common_vendor.t(task.name),
i: task.completed && task.claimed
}, task.completed && task.claimed ? {} : task.completed ? {} : {}, {
j: task.completed,
k: task.id,
l: task.completed ? 1 : "",
m: common_vendor.o(($event) => onGamifyTaskTap(task), task.id)
});
}),
q: common_vendor.t(treeLevel.value),
r: common_vendor.t(treeLevelName.value),
s: common_vendor.t(currentTreeSpecies.value.name),
t: common_vendor.p({
name: "chevron-right",
size: "sm",
color: "#006c49"
}),
v: common_vendor.o(openTreeSpeciesPicker, "60"),
w: treeState.value.isMax
}, treeState.value.isMax ? {
x: common_vendor.t(treeXpUpgradeHint.value)
} : {
y: common_vendor.t(treeState.value.pointsToNext),
z: common_vendor.t(treeLevel.value + 1)
}, {
A: common_vendor.t(treeProgressLabel.value),
B: treeProgress.value + "%",
C: common_vendor.f(waterDroplets.value, (d, k0, i0) => {
return {
a: d.key,
b: d.left + "%",
c: d.delay + "s"
};
}),
D: common_vendor.t(treeEmoji.value),
E: treePulseActive.value ? 1 : "",
F: common_vendor.f(sparkles.value, (s, k0, i0) => {
return {
a: s.key,
b: s.left + "%",
c: s.top + "%",
d: s.dur + "s"
};
}),
G: sugarDecrease.value
}, sugarDecrease.value ? {
H: sugarDecrease.value.key
} : {}, {
I: floatReward.value
}, floatReward.value ? {
J: common_vendor.t(floatReward.value.text),
K: floatReward.value.key
} : {}, {
L: common_vendor.o(onTreeWhisper, "4a"),
M: common_vendor.p({
name: "droplet",
size: "md",
color: "#FFFFFF"
}),
N: treeWaterLocked.value ? 1 : "",
O: common_vendor.o(onWaterTreeTap, "9b"),
P: common_vendor.t(waterHintText.value),
Q: diagnosisId.value
}, diagnosisId.value ? common_vendor.e({
R: common_vendor.f(rangeOptions, (opt, k0, i0) => {
return {
a: common_vendor.t(opt.label),
b: opt.value,
c: rangeMode.value === opt.value ? 1 : "",
d: common_vendor.o(($event) => onChangeRange(opt.value), opt.value)
};
}),
S: heatmapWeeks.value.length > 0
}, heatmapWeeks.value.length > 0 ? {
T: common_vendor.t(heatmapMonthLabel.value),
U: common_vendor.t(heatmapTotalRecords.value),
V: common_vendor.t(heatmapInRangeDays.value),
W: common_vendor.p({
name: "chevron-right",
size: "md",
color: "#006c49"
}),
X: common_vendor.f(heatmapCells.value, (cell, k0, i0) => {
return common_vendor.e({
a: common_vendor.t(cell.day),
b: cell.inRange && cell.level > 0
}, cell.inRange && cell.level > 0 ? {
c: cell.isHigh ? 1 : ""
} : {}, {
d: cell.date,
e: !cell.inRange ? 1 : "",
f: cell.inRange && cell.level > 0 && !cell.isHigh ? 1 : "",
g: cell.inRange && cell.isHigh ? 1 : "",
h: cell.isToday && cell.inRange && cell.level === 0 ? 1 : "",
i: cell.inRange && cell.level === 0 && !cell.isToday ? 1 : "",
j: !cell.inRange && cell.level < 0 ? 1 : "",
k: common_vendor.o(($event) => cell.inRange && cell.level > 0 ? jumpToDayRecord(cell.date) : null, cell.date)
});
}),
Y: common_vendor.t(todayChecked.value ? "今日已完成记录" : "今日还没有记录"),
Z: common_vendor.o(($event) => openInputForm(), "d9")
} : {}, {
aa: common_vendor.t(bloodRecordDays.value.length),
ab: loading.value
}, loading.value ? {} : bloodRecordDays.value.length === 0 ? {} : {
ad: common_vendor.f(bloodRecordDays.value, (day, k0, i0) => {
return common_vendor.e({
a: common_vendor.t(day.dateLabel),
b: common_vendor.t(day.weekday),
c: day.blood
}, day.blood ? common_vendor.e({
d: day.blood.isHighFasting
}, day.blood.isHighFasting ? {} : day.blood.isLowFasting ? {} : {}, {
e: day.blood.isLowFasting,
f: common_vendor.t(formatBloodMetric(day.blood.fasting_blood_sugar)),
g: day.blood.isHighFasting ? 1 : "",
h: day.blood.isLowFasting ? 1 : "",
i: day.blood.isHighFasting ? 1 : "",
j: day.blood.isLowFasting ? 1 : "",
k: formatBloodMetric(day.blood.postprandial_blood_sugar) !== "——"
}, formatBloodMetric(day.blood.postprandial_blood_sugar) !== "——" ? common_vendor.e({
l: day.blood.isHighPostprandial
}, day.blood.isHighPostprandial ? {} : day.blood.isLowPostprandial ? {} : {}, {
m: day.blood.isLowPostprandial,
n: common_vendor.t(formatBloodMetric(day.blood.postprandial_blood_sugar)),
o: day.blood.isHighPostprandial ? 1 : "",
p: day.blood.isLowPostprandial ? 1 : ""
}) : {}, {
q: day.blood.isHighPostprandial ? 1 : "",
r: day.blood.isLowPostprandial ? 1 : ""
}) : {}, {
s: day.date,
t: `day-${day.date.replace(/-/g, "")}`
});
})
}, {
ac: bloodRecordDays.value.length === 0,
ae: common_vendor.p({
name: "heart",
size: "md",
color: "#a43a3a"
}),
af: common_vendor.p({
name: "chevron-right",
size: "md",
color: "#6c7a71"
}),
ag: common_vendor.o(onFamilyLikeStripTap, "1d")
}) : {}, {
ah: diagnosisId.value
}, diagnosisId.value ? common_vendor.e({
ai: common_vendor.t(encourageParts.value.prefix),
aj: encourageParts.value.highlight
}, encourageParts.value.highlight ? {
ak: common_vendor.t(encourageParts.value.highlight)
} : {}, {
al: common_vendor.t(encourageParts.value.suffix),
am: common_vendor.o(onEncourageTap, "67")
}) : {}, {
an: cards.value.length > 0
}, cards.value.length > 0 ? {
ao: common_vendor.t(cards.value.length),
ap: common_vendor.f(cards.value, (card, k0, i0) => {
return common_vendor.e({
a: common_vendor.t(card.patient_name || "未填"),
b: card.age
}, card.age ? {
c: common_vendor.t(card.age)
} : {}, {
d: card.id,
e: Number(card.id) === Number(diagnosisId.value) ? 1 : "",
f: common_vendor.o(($event) => onSelectCard(card), card.id)
});
})
} : {}, {
aq: common_vendor.s(mainPadStyle.value),
ar: inputForm.value.visible
}, inputForm.value.visible ? common_vendor.e({
as: common_vendor.t(inputForm.value.id ? "修改今日血糖" : "录入今日血糖"),
at: common_vendor.t(todayText.value),
av: common_vendor.t(patientName.value || "本人"),
aw: common_vendor.o(closeInputForm, "f9"),
ax: inputForm.value.loading
}, inputForm.value.loading ? {} : common_vendor.e({
ay: common_vendor.unref(sttSupported)
}, common_vendor.unref(sttSupported) ? {
az: common_vendor.p({
active: isVoiceBarLive("glucose"),
["on-dark"]: isVoiceBarLive("glucose")
}),
aA: common_vendor.t(isVoiceBarLive("glucose") ? "正在聆听…松手结束" : "长按说话,自动填写"),
aB: isVoiceBarLive("glucose") ? 1 : "",
aC: voiceDialog.active ? 1 : "",
aD: common_vendor.o(($event) => startVoiceInput("glucose"), "bc"),
aE: common_vendor.o(endVoiceInput, "bb"),
aF: common_vendor.o(endVoiceInput, "bc"),
aG: common_vendor.o(blockVoiceTouchMove, "4a")
} : {}, {
aH: inputForm.value.fasting_blood_sugar,
aI: common_vendor.o(($event) => onInputField("fasting_blood_sugar", $event), "0b"),
aJ: inputForm.value.postprandial_blood_sugar,
aK: common_vendor.o(($event) => onInputField("postprandial_blood_sugar", $event), "bc"),
aL: inputForm.value.other_blood_sugar,
aM: common_vendor.o(($event) => onInputField("other_blood_sugar", $event), "5d"),
aN: inputForm.value.remark,
aO: common_vendor.o(($event) => onInputField("remark", $event), "c9"),
aP: !manualVoicePressing.value,
aQ: common_vendor.o(onPageVoiceTouchEnd, "7e"),
aR: common_vendor.o(onPageVoiceTouchEnd, "49")
}), {
aS: inputForm.value.id
}, inputForm.value.id ? {
aT: inputForm.value.submitting ? 1 : "",
aU: common_vendor.o(deleteInputRecord, "75")
} : {}, {
aV: common_vendor.t(inputForm.value.submitting ? "提交中…" : inputForm.value.id ? "保存修改" : "提交录入"),
aW: inputForm.value.submitting || inputForm.value.loading ? 1 : "",
aX: common_vendor.o(submitInputForm, "4a"),
aY: common_vendor.o(() => {
}, "21"),
aZ: common_vendor.o(closeInputForm, "3d")
}) : {}, {
ba: bpForm.value.visible
}, bpForm.value.visible ? common_vendor.e({
bb: common_vendor.t(bpForm.value.id ? "修改今日血压" : "录入今日血压"),
bc: common_vendor.t(todayText.value),
bd: common_vendor.t(patientName.value || "本人"),
be: common_vendor.o(closeBpForm, "5a"),
bf: bpForm.value.loading
}, bpForm.value.loading ? {} : {
bg: common_vendor.p({
active: isVoiceBarLive("bp"),
["on-dark"]: isVoiceBarLive("bp")
}),
bh: common_vendor.t(isVoiceBarLive("bp") ? "正在聆听…松手结束" : "长按说话,自动填写"),
bi: isVoiceBarLive("bp") ? 1 : "",
bj: common_vendor.o(($event) => startVoiceInput("bp"), "a3"),
bk: common_vendor.o(endVoiceInput, "11"),
bl: common_vendor.o(endVoiceInput, "7d"),
bm: common_vendor.o(blockVoiceTouchMove, "61"),
bn: bpForm.value.systolic_pressure,
bo: common_vendor.o(($event) => onBpField("systolic_pressure", $event), "09"),
bp: bpForm.value.diastolic_pressure,
bq: common_vendor.o(($event) => onBpField("diastolic_pressure", $event), "70"),
br: bpForm.value.western_medicine,
bs: common_vendor.o(($event) => onBpField("western_medicine", $event), "af"),
bt: bpForm.value.insulin,
bv: common_vendor.o(($event) => onBpField("insulin", $event), "a1"),
bw: !manualVoicePressing.value,
bx: common_vendor.o(onPageVoiceTouchEnd, "0f"),
by: common_vendor.o(onPageVoiceTouchEnd, "5d")
}, {
bz: bpForm.value.id
}, bpForm.value.id ? {
bA: bpForm.value.submitting ? 1 : "",
bB: common_vendor.o(deleteBpRecord, "f0")
} : {}, {
bC: common_vendor.t(bpForm.value.submitting ? "提交中…" : "保存"),
bD: bpForm.value.submitting || bpForm.value.loading ? 1 : "",
bE: common_vendor.o(submitBpForm, "04"),
bF: common_vendor.o(() => {
}, "a6"),
bG: common_vendor.o(closeBpForm, "a0")
}) : {}, {
bH: dietForm.value.visible
}, dietForm.value.visible ? common_vendor.e({
bI: common_vendor.t(todayText.value),
bJ: common_vendor.o(closeDietForm, "b2"),
bK: dietForm.value.loading
}, dietForm.value.loading ? {} : common_vendor.e({
bL: dietAiPrefill.value && dietAiPrefill.value.breakfast
}, dietAiPrefill.value && dietAiPrefill.value.breakfast ? common_vendor.e({
bM: common_vendor.t(dietAiPrefill.value.breakfast),
bN: dietAiPrefill.value.drinks
}, dietAiPrefill.value.drinks ? {
bO: common_vendor.t(dietAiPrefill.value.drinks)
} : {}, {
bP: common_vendor.t(dietAiPrefill.value.lunch),
bQ: common_vendor.t(dietAiPrefill.value.dinner),
bR: common_vendor.o(fillDietFormFromAi, "45")
}) : {}, {
bS: common_vendor.p({
active: isVoiceBarLive("diet"),
["on-dark"]: isVoiceBarLive("diet")
}),
bT: common_vendor.t(isVoiceBarLive("diet") ? "正在聆听…松手结束" : "长按说话,自动填写"),
bU: isVoiceBarLive("diet") ? 1 : "",
bV: common_vendor.o(($event) => startVoiceInput("diet"), "16"),
bW: common_vendor.o(endVoiceInput, "b7"),
bX: common_vendor.o(endVoiceInput, "0d"),
bY: common_vendor.o(blockVoiceTouchMove, "1a"),
bZ: dietForm.value.breakfast_foods,
ca: common_vendor.o(($event) => onDietField("breakfast_foods", $event), "15"),
cb: dietForm.value.lunch_foods,
cc: common_vendor.o(($event) => onDietField("lunch_foods", $event), "6c"),
cd: dietForm.value.dinner_foods,
ce: common_vendor.o(($event) => onDietField("dinner_foods", $event), "2c"),
cf: dietForm.value.note,
cg: common_vendor.o(($event) => onDietField("note", $event), "84"),
ch: !manualVoicePressing.value,
ci: common_vendor.o(onPageVoiceTouchEnd, "05"),
cj: common_vendor.o(onPageVoiceTouchEnd, "0e")
}), {
ck: common_vendor.t(dietForm.value.submitting ? "提交中…" : "保存饮食"),
cl: dietForm.value.submitting ? 1 : "",
cm: common_vendor.o(submitDietForm, "8f"),
cn: common_vendor.o(() => {
}, "28"),
co: common_vendor.o(closeDietForm, "25")
}) : {}, {
cp: exerciseForm.value.visible
}, exerciseForm.value.visible ? common_vendor.e({
cq: common_vendor.t(todayText.value),
cr: common_vendor.o(closeExerciseForm, "1e"),
cs: exerciseForm.value.loading
}, exerciseForm.value.loading ? {} : {
ct: common_vendor.p({
active: isVoiceBarLive("exercise"),
["on-dark"]: isVoiceBarLive("exercise")
}),
cv: common_vendor.t(isVoiceBarLive("exercise") ? "正在聆听…松手结束" : "长按说话,自动填写"),
cw: isVoiceBarLive("exercise") ? 1 : "",
cx: common_vendor.o(($event) => startVoiceInput("exercise"), "c8"),
cy: common_vendor.o(endVoiceInput, "2c"),
cz: common_vendor.o(endVoiceInput, "5d"),
cA: common_vendor.o(blockVoiceTouchMove, "35"),
cB: exerciseForm.value.exercise_type,
cC: common_vendor.o(($event) => onExerciseField("exercise_type", $event), "ec"),
cD: exerciseForm.value.duration,
cE: common_vendor.o(($event) => onExerciseField("duration", $event), "68"),
cF: common_vendor.f(exerciseIntensityOptions, (opt, k0, i0) => {
return {
a: common_vendor.t(opt.label),
b: opt.value,
c: exerciseForm.value.intensity === opt.value ? 1 : "",
d: common_vendor.o(($event) => exerciseForm.value.intensity = opt.value, opt.value)
};
}),
cG: !manualVoicePressing.value,
cH: common_vendor.o(onPageVoiceTouchEnd, "34"),
cI: common_vendor.o(onPageVoiceTouchEnd, "3c")
}, {
cJ: common_vendor.t(exerciseForm.value.submitting ? "提交中…" : "保存运动"),
cK: exerciseForm.value.submitting ? 1 : "",
cL: common_vendor.o(submitExerciseForm, "cf"),
cM: common_vendor.o(() => {
}, "86"),
cN: common_vendor.o(closeExerciseForm, "0f")
}) : {}, {
cO: (common_vendor.unref(sttHolding) || common_vendor.unref(sttListening)) && !recordOverlayOpen.value && !voiceDialog.active
}, (common_vendor.unref(sttHolding) || common_vendor.unref(sttListening)) && !recordOverlayOpen.value && !voiceDialog.active ? {
cP: common_vendor.p({
name: "mic",
size: "lg",
color: "#ffffff"
}),
cQ: common_vendor.unref(sttListening) ? 1 : "",
cR: common_vendor.t(common_vendor.unref(sttListening) ? "正在聆听…" : "准备录音…")
} : {}, {
cS: weeklyReportVisible.value
}, weeklyReportVisible.value ? {
cT: common_vendor.o(closeWeeklyReport, "a0"),
cU: common_vendor.p({
level: reportTreeLevel.value,
size: "md"
}),
cV: common_vendor.t(weeklyReportData.value.patientLabel),
cW: common_vendor.t(weeklyReportData.value.weekLabel),
cX: common_vendor.t(weeklyReportData.value.recordDays),
cY: common_vendor.t(weeklyReportData.value.completeDays),
cZ: common_vendor.t(weeklyReportData.value.streakDays),
da: common_vendor.t(weeklyReportData.value.quote),
db: common_vendor.o(prepareShareWeekly, "58"),
dc: common_vendor.o(closeWeeklyReport, "43"),
dd: common_vendor.o(() => {
}, "2e"),
de: common_vendor.o(closeWeeklyReport, "9b")
} : {}, {
df: waterStatusVisible.value
}, waterStatusVisible.value ? common_vendor.e({
dg: common_vendor.o(closeWaterStatusPanel, "55"),
dh: common_vendor.t(waterStatusSummary.value),
di: common_vendor.f(waterStatusRows.value, (row, k0, i0) => {
return {
a: "5a4b0d9c-17-" + i0,
b: common_vendor.p({
name: gamifyTaskIcon(row.id),
size: "sm",
color: "#006c49"
}),
c: common_vendor.t(row.name),
d: common_vendor.t(row.statusSub),
e: common_vendor.t(row.statusLabel),
f: row.id,
g: common_vendor.n("is-" + row.statusKind)
};
}),
dj: waterStatusGrowthHint.value
}, waterStatusGrowthHint.value ? {
dk: common_vendor.t(waterStatusGrowthHint.value)
} : {}, {
dl: canWaterTree.value
}, canWaterTree.value ? {
dm: common_vendor.t(claimablePoints.value),
dn: common_vendor.o(waterTreeFromPanel, "57")
} : firstPendingDisplayTask.value ? {
dq: common_vendor.t(firstPendingDisplayTask.value.name),
dr: common_vendor.o(goRecordFromWaterPanel, "89")
} : {
ds: common_vendor.o(closeWaterStatusPanel, "bb")
}, {
dp: firstPendingDisplayTask.value,
dt: common_vendor.o(() => {
}, "d2"),
dv: common_vendor.o(closeWaterStatusPanel, "f0")
}) : {}, {
dw: treeSpeciesPickerVisible.value
}, treeSpeciesPickerVisible.value ? {
dx: common_vendor.o(closeTreeSpeciesPicker, "2d"),
dy: common_vendor.t(common_vendor.unref(speciesDaysToMax)),
dz: common_vendor.t(common_vendor.unref(tongji_utils_treeLevels.TREE_XP_PER_LEVEL)),
dA: common_vendor.f(common_vendor.unref(tongji_utils_treeSpecies.TREE_SPECIES_LIST), (sp, k0, i0) => {
return {
a: common_vendor.t(common_vendor.unref(tongji_utils_treeSpecies.speciesEmojiAtLevel)(sp.id, treeLevel.value)),
b: common_vendor.t(sp.name),
c: common_vendor.t(sp.tagline),
d: common_vendor.f(common_vendor.unref(tongji_utils_treeSpecies.speciesGrowthRoadmap)(sp.id), (stage, k1, i1) => {
return {
a: common_vendor.t(stage.emoji),
b: common_vendor.t(stage.level),
c: common_vendor.t(stage.name),
d: stage.level,
e: treeLevel.value >= stage.level ? 1 : "",
f: treeLevel.value === stage.level ? 1 : ""
};
}),
e: sp.id,
f: treeSpeciesId.value === sp.id ? 1 : "",
g: common_vendor.o(($event) => selectTreeSpecies(sp.id), sp.id)
};
}),
dB: common_vendor.o(() => {
}, "50"),
dC: common_vendor.o(closeTreeSpeciesPicker, "d3")
} : {}) : {}, {
dD: common_vendor.o(onPageVoiceTouchEnd, "b5"),
dE: common_vendor.o(onPageVoiceTouchEnd, "3b")
});
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-5a4b0d9c"]]);
_sfc_main.__runtimeHooks = 2;
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/pages/more.js.map