"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