"use strict"; const common_vendor = require("../../common/vendor.js"); const tongji_composables_useDietAi = require("../composables/useDietAi.js"); const tongji_composables_useSpeechToText = require("../composables/useSpeechToText.js"); if (!Math) { (TongjiIcon + DevTrainingEntry + TabBarDock)(); } const TongjiIcon = () => "../components/TongjiIcon.js"; const TabBarDock = () => "../../components/app-tab-bar/tab-bar-dock.js"; const DevTrainingEntry = () => "../../components/dev-training-entry/index.js"; const BLOOD_LOOKBACK_DAYS = 30; const REAL_BLOOD_DAYS_MIN = 7; const _sfc_main = { __name: "weekly", 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: options.duration || 2800 }); } function extractApiError(res, fallback = "") { var _a, _b; if (!res) return fallback; const raw = res.msg ?? res.message ?? ((_a = res.data) == null ? void 0 : _a.error) ?? ((_b = res.data) == null ? void 0 : _b.msg) ?? res.errMsg; return formatUserMessage(raw, fallback); } const diagnosisId = common_vendor.ref(0); const dailyContextError = common_vendor.ref(""); function clearDailyContextError() { dailyContextError.value = ""; } function reportDailyContextError(res, fallback = "无法开始录入", options = {}) { const text = extractApiError(res, fallback); if (!text) return; dailyContextError.value = text; if (options.alert) { common_vendor.index.showModal({ title: "无法录入血糖", content: text, showCancel: false, confirmText: "知道了" }); } else { const short = text.length > 14 ? `${text.slice(0, 14)}…` : text; showUserToast(short, { duration: 3200 }); } } const patientId = common_vendor.ref(0); const patientName = common_vendor.ref(""); const displayPatientName = common_vendor.computed(() => { const name = String(patientName.value || "").trim(); if (!name) return "您好"; return name.length > 10 ? `${name.slice(0, 10)}…` : name; }); const headerSafeStyle = common_vendor.ref({}); const headerLeftStyle = common_vendor.ref({}); const voiceBtnStyle = common_vendor.ref({}); const age = common_vendor.ref(0); const gender = common_vendor.ref(0); const { dietAiRecommend, dietAiLoading, dietAiStreaming, dietAiReplacing, dietAiStreamField, dietAiAskText, dietAiAsking, dietAiAskResult, hasDietAiMealContent, fetchDietAiRecommend, refreshDietAiRecommend, askDietAiFood, onDietAiAskInput, applyDietAiToForm, openDietForm } = tongji_composables_useDietAi.useDietAi(proxy, { diagnosisId, showUserToast, formatUserMessage }); const floatAskClosed = common_vendor.ref(false); const dietAiAskFocused = common_vendor.ref(false); function onDietAiAskFocus() { dietAiAskFocused.value = true; } function onDietAiAskBlur() { dietAiAskFocused.value = false; } const dietAiAskExpanded = common_vendor.computed( () => dietAiAskFocused.value || String(dietAiAskText.value || "").length > 0 ); const keyboardHeight = common_vendor.ref(0); function onKeyboardHeightChange(res) { keyboardHeight.value = res && res.height ? Math.round(res.height) : 0; } const { sttListening, sttHolding, sttSupported, beginHoldSpeech, endHoldSpeech, stopSpeechToText } = tongji_composables_useSpeechToText.useSpeechToText({ onResult(text) { const prev = String(dietAiAskText.value || "").trim(); dietAiAskText.value = prev ? `${prev} ${text}` : text; }, showToast: (msg) => showUserToast(msg) }); function onDietAiSpeechStart() { if (dietAiAsking.value) return; beginHoldSpeech(); } function onDietAiSpeechEnd() { endHoldSpeech(); } const dietAnalysisList = common_vendor.computed(() => { const list = dietAiRecommend.value && Array.isArray(dietAiRecommend.value.analysis) ? dietAiRecommend.value.analysis : []; return list.filter((s) => s && typeof s === "object"); }); common_vendor.computed(() => dietAnalysisList.value.length > 0); common_vendor.computed(() => { const ex = dietAiRecommend.value && dietAiRecommend.value.exercise; return ex && typeof ex === "object" && Array.isArray(ex.items) && ex.items.length ? ex : null; }); const authChecking = common_vendor.ref(false); const userMobile = common_vendor.ref(""); const phoneGateVisible = common_vendor.ref(false); const phoneGateSex = common_vendor.ref(0); const phoneBinding = common_vendor.ref(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); const chartSeriesVisible = common_vendor.ref({ fasting: true, postprandial: true }); let canvasNode = null; let canvasCtx = null; let canvasWidth = 0; let canvasHeight = 0; let canvasDpr = 1; common_vendor.computed(() => age.value > 0 ? `${age.value} 岁` : ""); common_vendor.computed(() => { const map = { 1: "男", 2: "女" }; return map[Number(gender.value)] || ""; }); const rangeText = 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 fetchRange = common_vendor.computed(() => { const displayDays = rangeMode.value === "30" ? 30 : 7; const lookback = Math.max(BLOOD_LOOKBACK_DAYS, displayDays); const end = /* @__PURE__ */ new Date(); end.setHours(0, 0, 0, 0); const start = new Date(end); start.setDate(start.getDate() - (lookback - 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 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 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 || "—"}`; } 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.isHighBp = isHighBp(m); m.bpText = formatBp(m); } return map; }); 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; }); 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; }); 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 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; }); function bloodDayHasSugar(b) { if (!b) return false; return toNumber(b.fasting_blood_sugar) !== null || toNumber(b.postprandial_blood_sugar) !== null || toNumber(b.other_blood_sugar) !== null; } const bloodHasData = common_vendor.computed(() => { return chartDates.value.some((date) => bloodDayHasSugar(bloodByDate.value[date])); }); const bloodRecordDayCount = common_vendor.computed(() => { return chartDates.value.filter((date) => bloodDayHasSugar(bloodByDate.value[date])).length; }); const bloodCumulativeDayCount = common_vendor.computed(() => { let n = 0; for (const date of Object.keys(bloodByDate.value)) { if (bloodDayHasSugar(bloodByDate.value[date])) n++; } return n; }); const chartUseMockData = common_vendor.computed(() => { if (!diagnosisId.value) return true; return bloodCumulativeDayCount.value < REAL_BLOOD_DAYS_MIN; }); const mockBloodByDate = common_vendor.computed(() => { const map = {}; const dates = chartDates.value; dates.forEach((date, i) => { const t = dates.length > 1 ? i / (dates.length - 1) : 0; const fasting = Number((5.2 + Math.sin(t * Math.PI) * 0.5 + i % 2 * 0.15).toFixed(1)); const post = Number((7.6 + Math.cos(t * Math.PI) * 1.1 + i % 3 * 0.25).toFixed(1)); const row = { record_date: date, fasting_blood_sugar: fasting, postprandial_blood_sugar: post, other_blood_sugar: "", systolic_pressure: "", diastolic_pressure: "" }; row.isHighFasting = isHighFasting(row.fasting_blood_sugar); row.isHighPostprandial = isHighPostprandial(row.postprandial_blood_sugar); row.isHighOther = false; row.isHighBp = false; row.bpText = "—"; map[date] = row; }); return map; }); const chartBloodByDate = common_vendor.computed(() => chartUseMockData.value ? mockBloodByDate.value : bloodByDate.value); const chartHasDisplayData = common_vendor.computed(() => { if (chartUseMockData.value) return chartDates.value.length > 0; return bloodHasData.value; }); 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; }); function elderMetricFromBlood(blood, field, label) { if (!blood) return { label, value: "—", isHigh: false }; const num = toNumber(blood[field]); if (num === null) return { label, value: "—", isHigh: false }; const isHigh = field === "fasting_blood_sugar" ? blood.isHighFasting : field === "postprandial_blood_sugar" ? blood.isHighPostprandial : blood.isHighOther; return { label, value: String(blood[field]), isHigh: !!isHigh }; } const todayBlood = common_vendor.computed(() => bloodByDate.value[todayStr.value] || null); common_vendor.computed( () => elderMetricFromBlood(todayBlood.value, "fasting_blood_sugar", "空腹") ); common_vendor.computed( () => elderMetricFromBlood(todayBlood.value, "postprandial_blood_sugar", "餐后") ); common_vendor.computed(() => { const blood = todayBlood.value; if (!blood) { return { hasValue: false, typeLabel: "", value: "—", isHigh: false }; } const post = toNumber(blood.postprandial_blood_sugar); if (post !== null) { return { hasValue: true, typeLabel: "餐后", value: String(blood.postprandial_blood_sugar), isHigh: !!blood.isHighPostprandial }; } const fasting = toNumber(blood.fasting_blood_sugar); if (fasting !== null) { return { hasValue: true, typeLabel: "空腹", value: String(blood.fasting_blood_sugar), isHigh: !!blood.isHighFasting }; } return { hasValue: false, typeLabel: "", value: "—", isHigh: false }; }); 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; } function readUserMobile() { try { const userData = common_vendor.index.getStorageSync("userData") || {}; userMobile.value = String(userData.mobile || userData.phone || "").trim(); } catch (e) { userMobile.value = ""; } return userMobile.value; } function syncPhoneGateSexFromUser() { try { const userData = common_vendor.index.getStorageSync("userData") || {}; const sex = Number(userData.sex); phoneGateSex.value = sex === 1 || sex === 2 ? sex : 0; } catch (e) { phoneGateSex.value = 0; } } async function openPhoneGate() { syncPhoneGateSexFromUser(); readUserMobile(); if (userMobile.value) { phoneGateVisible.value = false; await resolveDailyPhoneContext(false); return; } phoneGateVisible.value = true; } async function refreshUserMobile() { 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); userMobile.value = String(res.data.mobile || res.data.phone || "").trim(); } } catch (e) { } return userMobile.value; } async function ensureDailyContextByPhone(sex, options = {}) { if (diagnosisId.value) { clearDailyContextError(); return true; } const payload = {}; const sexVal = sex ?? phoneGateSex.value; if (sexVal === 1 || sexVal === 2) { payload.sex = sexVal; } const res = await proxy.apiUrl({ url: "/api/tcm/dailyEnsurePhoneContext", method: "POST", data: payload }, false); if (res && res.code === 1 && res.data && res.data.diagnosis_id) { clearDailyContextError(); 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) { clearDailyContextError(); return "need_mobile"; } reportDailyContextError(res, "无法开始录入", options); return false; } async function resolveDailyPhoneContext(openFormAfter = false) { const ok = await ensureDailyContextByPhone(phoneGateSex.value, { alert: true }); if (ok === true) { if (openFormAfter) await openInputForm(true); return true; } if (ok === "need_mobile") { syncPhoneGateSexFromUser(); phoneGateVisible.value = true; } return false; } async function ensureCanRecordGlucose() { if (diagnosisId.value) return true; readUserMobile(); if (!userMobile.value) { openPhoneGate(); return false; } const ok = await ensureDailyContextByPhone(void 0, { alert: true }); if (ok === "need_mobile") { openPhoneGate(); return false; } return !!ok; } function closePhoneGate() { if (phoneBinding.value) return; phoneGateVisible.value = false; resetCanvasCache(); common_vendor.nextTick$1(() => scheduleChartRedraw()); } async function onPhoneGateAuthorize(e) { const detail = (e == null ? void 0 : e.detail) || {}; if (detail.errMsg && !String(detail.errMsg).includes("ok")) { showUserToast("需要授权手机号后才能录入血糖"); return; } const code = detail.code; if (!code) { showUserToast("获取手机号失败,请重试"); return; } if (phoneGateSex.value !== 1 && phoneGateSex.value !== 2) { showUserToast("请先选择性别"); return; } phoneBinding.value = true; try { readUserMobile(); if (userMobile.value) { phoneGateVisible.value = false; await resolveDailyPhoneContext(true); return; } const res = await proxy.apiUrl({ url: "/api/user/getMobileByMnp", method: "POST", data: { code, sex: phoneGateSex.value } }, false); if (!res || res.code !== 1) { showUserToast(formatUserMessage(res == null ? void 0 : res.msg, "绑定手机号失败")); return; } await refreshUserMobile(); phoneGateVisible.value = false; await resolveDailyPhoneContext(true); } catch (err) { showUserToast("网络异常,请稍后再试"); } finally { phoneBinding.value = 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 } = fetchRange.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 fetchDietAiRecommend(false); } catch (e) { common_vendor.index.showToast({ title: "加载失败,请稍后再试", icon: "none" }); } finally { loading.value = false; await common_vendor.nextTick$1(); scheduleChartRedraw(); if (ttsAutoPlay.value && !recordOverlayOpen.value) { const token = ++ttsAutoToken; setTimeout(() => { if (token === ttsAutoToken && !recordOverlayOpen.value) speakLatestSummary(); }, 350); } } } function buildMorePageUrl() { if (!diagnosisId.value) return "/tongji/pages/more"; const q = [ `diagnosis_id=${diagnosisId.value}`, patientId.value ? `patient_id=${patientId.value}` : "" ].filter(Boolean).join("&"); return `/tongji/pages/more?${q}`; } async function onChartTap() { if (!diagnosisId.value) { const ok = await ensureCanRecordGlucose(); if (!ok || !diagnosisId.value) return; } common_vendor.index.navigateTo({ url: buildMorePageUrl() }); } function openGamePage(url) { common_vendor.index.navigateTo({ url, fail() { common_vendor.index.showToast({ title: "暂时无法打开游戏", icon: "none" }); } }); } function goEndlessGamePage() { openGamePage("/tongji/endless-game/index"); } 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" }; } const timeGreetingMeta = 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; } function dayCoverageLevel(key) { const b = bloodByDate.value[key]; if (!b) return 0; const fields = ["fasting_blood_sugar", "postprandial_blood_sugar", "other_blood_sugar"]; const score = fields.filter((f) => toNumber(b[f]) !== null).length; if (score === 0) return 0; if (score === 1) return 1; if (score === 2) return 2; return 3; } const todayChecked = common_vendor.computed(() => dayHasBloodRecord(todayStr.value)); 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; }); 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); } 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 }); cur.setDate(cur.getDate() + 1); } weeks.push(week); } return weeks; }); 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; }); common_vendor.computed(() => { let c = 0; for (const w of heatmapWeeks.value) for (const c2 of w) if (c2.inRange) c++; return c; }); 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)}月`; }); 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()); function prepareRecordOverlay() { resetCanvasCache(); ttsStop(); } function onInputField(field, e) { var _a; const val = ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.value) ?? ""; inputForm.value[field] = val; } async function openInputForm(skipEnsure = false) { const token = common_vendor.index.getStorageSync("token"); if (!token) { showUserToast("请先登录后再录入"); return; } if (!skipEnsure) { const can = await ensureCanRecordGlucose(); if (!can) return; } if (!diagnosisId.value) { 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 || ""); } } catch (e) { } finally { inputForm.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 closeInputForm() { if (inputForm.value.submitting) return; inputForm.value.visible = false; resetCanvasCache(); common_vendor.nextTick$1(() => scheduleChartRedraw()); } const recordOverlayOpen = common_vendor.computed(() => inputForm.value.visible || phoneGateVisible.value); common_vendor.watch(recordOverlayOpen, (open) => { if (!open) { resetCanvasCache(); common_vendor.nextTick$1(() => scheduleChartRedraw()); } }); async function submitInputForm() { if (inputForm.value.submitting || inputForm.value.loading) return; 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) { common_vendor.index.showToast({ title: "请至少填写一项数据", icon: "none" }); 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(); } else { showUserToast(res && res.msg || "保存失败"); } } catch (e) { common_vendor.index.showToast({ title: "网络异常,请稍后再试", icon: "none" }); } finally { inputForm.value.submitting = false; } } function deleteInputRecord() { if (!inputForm.value.id || inputForm.value.submitting) return; 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; fetchAll(); } else { showUserToast(res && res.msg || "删除失败"); } } catch (e) { common_vendor.index.showToast({ title: "网络异常,请稍后再试", icon: "none" }); } finally { inputForm.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; function teardownTtsAudio() { if (ttsAudioCtx) { try { ttsAudioCtx.stop(); } catch (e) { } try { ttsAudioCtx.destroy && ttsAudioCtx.destroy(); } catch (e) { } ttsAudioCtx = null; } } function ttsStop() { 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) { if (!ttsEnabled.value) return; const content = normalizeTtsText(text); if (!content) return; ttsStop(); try { const plugin = requirePlugin("WechatSI"); ttsSpeaking.value = true; plugin.textToSpeech({ lang: "zh_CN", tts: true, content, success: (res) => { if (!res || !res.filename) { ttsSpeaking.value = false; return; } teardownTtsAudio(); ttsAudioCtx = common_vendor.index.createInnerAudioContext(); ttsAudioCtx.src = res.filename; ttsAudioCtx.obeyMuteSwitch = false; ttsAudioCtx.onPlay(() => { ttsSpeaking.value = true; }); ttsAudioCtx.onEnded(() => { ttsSpeaking.value = false; }); ttsAudioCtx.onError(() => { ttsSpeaking.value = false; }); ttsAudioCtx.onStop(() => { ttsSpeaking.value = false; }); ttsAudioCtx.play(); }, fail: (err) => { ttsSpeaking.value = false; common_vendor.index.__f__("warn", "at tongji/pages/weekly.vue:1999", "WechatSI textToSpeech failed", err); common_vendor.index.showToast({ title: "语音不可用,请检查网络/插件", icon: "none", duration: 2e3 }); } }); return; } catch (e) { common_vendor.index.__f__("warn", "at tongji/pages/weekly.vue:2005", "WechatSI plugin not available", e); } showUserToast(content, { duration: 3200 }); } 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 resetCanvasCache() { canvasCtx = null; canvasNode = null; canvasWidth = 0; canvasHeight = 0; } function fallbackCanvasSize() { try { const sys = common_vendor.index.getSystemInfoSync(); const margin = common_vendor.index.upx2px(40) * 2; const cardPad = common_vendor.index.upx2px(40) * 2; return { width: Math.max(280, sys.windowWidth - margin - cardPad), height: common_vendor.index.upx2px(360) }; } catch (e) { return { width: 320, height: 180 }; } } function ensureCanvas(retryCount = 0) { return new Promise((resolve) => { if (canvasCtx && canvasWidth > 0 && canvasHeight > 0) { resolve(true); return; } if (canvasCtx && (canvasWidth <= 0 || canvasHeight <= 0)) { resetCanvasCache(); } const query = common_vendor.index.createSelectorQuery().in(proxy); query.select("#bloodWaveChart").fields({ node: true, size: true }).exec((res) => { const item = res && res[0]; if (!item || !item.node) { if (retryCount < 10) { setTimeout(() => ensureCanvas(retryCount + 1).then(resolve), retryCount < 4 ? 100 : 180); } else { resolve(false); } return; } let w = item.width || 0; let h = item.height || 0; if (w <= 0 || h <= 0) { const fallback = fallbackCanvasSize(); w = w > 0 ? w : fallback.width; h = h > 0 ? h : fallback.height; } if ((item.width <= 0 || item.height <= 0) && retryCount < 6) { setTimeout(() => ensureCanvas(retryCount + 1).then(resolve), 120); return; } canvasNode = item.node; canvasCtx = canvasNode.getContext("2d"); try { canvasDpr = common_vendor.index.getSystemInfoSync().pixelRatio || 1; } catch (e) { canvasDpr = 1; } canvasWidth = w; canvasHeight = h; canvasNode.width = canvasWidth * canvasDpr; canvasNode.height = canvasHeight * canvasDpr; canvasCtx.setTransform(1, 0, 0, 1, 0, 0); canvasCtx.scale(canvasDpr, canvasDpr); resolve(true); }); }); } let redrawScheduled = false; function scheduleChartRedraw() { if (redrawScheduled) return; redrawScheduled = true; setTimeout(async () => { redrawScheduled = false; await ensureCanvas(); drawChart(); }, 60); } function buildSeries(field) { return chartDates.value.map((date) => { const b = chartBloodByDate.value[date]; return b ? toNumber(b[field]) : null; }); } function computeYRange(...seriesList) { const values = seriesList.flat().filter((v) => v !== null); if (!values.length) return { yMin: 4, yMax: 12 }; let min = Math.min(...values); let max = Math.max(...values); const span = max - min; if (span < 2) { min = Math.max(0, min - 1); max = max + 1; } else { min = Math.max(0, min - span * 0.2); max = max + span * 0.2; } min = Math.max(2, Math.floor(min)); max = Math.min(28, Math.ceil(max)); if (max - min < 4) max = min + 4; return { yMin: min, yMax: max }; } function drawChart() { if (!canvasCtx || !canvasWidth || !canvasHeight) return; const ctx = canvasCtx; const w = canvasWidth; const h = canvasHeight; ctx.clearRect(0, 0, w, h); const padding = { left: 40, right: 18, top: 12, bottom: 28 }; const cw = w - padding.left - padding.right; const ch = h - padding.top - padding.bottom; const fastingSeries = buildSeries("fasting_blood_sugar"); const postSeries = buildSeries("postprandial_blood_sugar"); const n = chartDates.value.length; const threshold = getBloodSugarThresholds(age.value); const seriesDefs = [ { key: "fasting", data: fastingSeries, color: "#006c49", fillTop: "rgba(0,108,73,0.18)", fillBottom: "rgba(0,108,73,0)", label: "空腹", highThreshold: threshold ? threshold.fasting : null }, { key: "postprandial", data: postSeries, color: "#a43a3a", fillTop: "rgba(164,58,58,0.18)", fillBottom: "rgba(164,58,58,0)", label: "餐后", highThreshold: threshold ? threshold.postprandial : null } ]; const visibleDefs = seriesDefs.filter((item) => chartSeriesVisible.value[item.key]); const rangeSources = visibleDefs.length ? visibleDefs.map((item) => item.data) : [fastingSeries, postSeries]; const { yMin, yMax } = computeYRange(...rangeSources); const valueToY = (v) => padding.top + ch * (1 - (v - yMin) / (yMax - yMin)); ctx.fillStyle = "#fbfcfd"; ctx.fillRect(padding.left, padding.top, cw, ch); if (threshold) { const yThr = valueToY(threshold.fasting); const top = Math.max(padding.top, Math.min(yThr, padding.top + ch)); if (top > padding.top) { const dangerGrad = ctx.createLinearGradient(0, padding.top, 0, top); dangerGrad.addColorStop(0, "rgba(254, 226, 226, 0.55)"); dangerGrad.addColorStop(1, "rgba(254, 226, 226, 0.15)"); ctx.fillStyle = dangerGrad; ctx.fillRect(padding.left, padding.top, cw, top - padding.top); } } ctx.strokeStyle = "#eef2f5"; ctx.lineWidth = 1; const yTicks = 4; for (let i = 0; i <= yTicks; i++) { const y = padding.top + ch * i / yTicks; ctx.beginPath(); ctx.moveTo(padding.left, y); ctx.lineTo(padding.left + cw, y); ctx.stroke(); } ctx.font = "bold 12px sans-serif"; ctx.fillStyle = "#64748b"; ctx.textAlign = "right"; ctx.textBaseline = "middle"; for (let i = 0; i <= yTicks; i++) { const y = padding.top + ch * i / yTicks; const v = yMax - (yMax - yMin) * i / yTicks; ctx.fillText(v.toFixed(1), padding.left - 8, y); } ctx.font = "bold 12px sans-serif"; ctx.fillStyle = "#475569"; ctx.textAlign = "center"; ctx.textBaseline = "top"; if (n > 0) { const maxLabels = n > 20 ? 5 : 6; const labelStep = Math.max(1, Math.ceil(n / maxLabels)); chartDates.value.forEach((date, i) => { if (i !== 0 && i !== n - 1 && i % labelStep !== 0) return; const x = n === 1 ? padding.left + cw / 2 : padding.left + cw * i / (n - 1); ctx.fillText(date.slice(5), x, padding.top + ch + 8); }); } if (threshold) { const y = valueToY(threshold.fasting); if (y >= padding.top && y <= padding.top + ch) { ctx.strokeStyle = "#fda4af"; ctx.lineWidth = 1; try { ctx.setLineDash([6, 5]); } catch (e) { } ctx.beginPath(); ctx.moveTo(padding.left, y); ctx.lineTo(padding.left + cw, y); ctx.stroke(); try { ctx.setLineDash([]); } catch (e) { } ctx.font = "bold 11px sans-serif"; ctx.fillStyle = "#dc2626"; ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.fillText(`空腹阈 ${threshold.fasting}`, padding.left + 6, y - 9); } } const xAt = (i) => n === 1 ? padding.left + cw / 2 : padding.left + cw * i / (n - 1); const coordByKey = { fasting: [], postprandial: [] }; const showDots = n <= 10; seriesDefs.forEach((meta) => { const projected = meta.data.map((v, i) => ({ x: xAt(i), y: v === null ? null : valueToY(v), value: v })); coordByKey[meta.key] = projected; if (!chartSeriesVisible.value[meta.key]) return; const segments = []; let cur = []; projected.forEach((p) => { if (p.y !== null) cur.push(p); else if (cur.length) { segments.push(cur); cur = []; } }); if (cur.length) segments.push(cur); segments.forEach((seg) => { if (seg.length === 1) { ctx.fillStyle = meta.color; ctx.beginPath(); ctx.arc(seg[0].x, seg[0].y, 4, 0, Math.PI * 2); ctx.fill(); return; } const drawPath = () => { ctx.beginPath(); ctx.moveTo(seg[0].x, seg[0].y); for (let i = 0; i < seg.length - 1; i++) { const p0 = seg[i - 1] || seg[i]; const p1 = seg[i]; const p2 = seg[i + 1]; const p3 = seg[i + 2] || seg[i + 1]; const cp1x = p1.x + (p2.x - p0.x) / 6; const cp1y = p1.y + (p2.y - p0.y) / 6; const cp2x = p2.x - (p3.x - p1.x) / 6; const cp2y = p2.y - (p3.y - p1.y) / 6; ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y); } }; drawPath(); ctx.lineTo(seg[seg.length - 1].x, padding.top + ch); ctx.lineTo(seg[0].x, padding.top + ch); ctx.closePath(); const grad = ctx.createLinearGradient(0, padding.top, 0, padding.top + ch); grad.addColorStop(0, meta.fillTop); grad.addColorStop(1, meta.fillBottom); ctx.fillStyle = grad; ctx.fill(); drawPath(); ctx.strokeStyle = meta.color; ctx.lineWidth = 2.4; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.stroke(); if (showDots) { seg.forEach((p) => { ctx.beginPath(); ctx.arc(p.x, p.y, 3, 0, Math.PI * 2); ctx.fillStyle = "#ffffff"; ctx.fill(); ctx.strokeStyle = meta.color; ctx.lineWidth = 1.8; ctx.stroke(); }); } }); let maxIdx = -1; let maxVal = -Infinity; projected.forEach((p, i) => { if (p.value != null && p.value > maxVal) { maxVal = p.value; maxIdx = i; } }); if (maxIdx >= 0) { const p = projected[maxIdx]; const isHigh = meta.highThreshold != null && p.value >= meta.highThreshold; ctx.beginPath(); ctx.arc(p.x, p.y, 5, 0, Math.PI * 2); ctx.fillStyle = isHigh ? "#dc2626" : meta.color; ctx.fill(); ctx.strokeStyle = "#ffffff"; ctx.lineWidth = 2.5; ctx.stroke(); const labelText = String(p.value); ctx.font = "bold 12px sans-serif"; const textWidth = ctx.measureText(labelText).width; const padX = 8; const padY = 5; const boxWidth = textWidth + padX * 2; const boxHeight = 14 + padY * 2; let labelX = p.x; let labelAbove = true; if (p.y - 12 - boxHeight - 4 < padding.top) labelAbove = false; let labelY = labelAbove ? p.y - 12 - boxHeight / 2 : p.y + 12 + boxHeight / 2; if (labelX - boxWidth / 2 < padding.left + 2) labelX = padding.left + boxWidth / 2 + 2; if (labelX + boxWidth / 2 > padding.left + cw - 2) labelX = padding.left + cw - boxWidth / 2 - 2; ctx.fillStyle = isHigh ? "#dc2626" : meta.color; ctx.fillRect(labelX - boxWidth / 2, labelY - boxHeight / 2, boxWidth, boxHeight); ctx.fillStyle = "#ffffff"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(labelText, labelX, labelY); } }); } function toggleChartSeries(key) { if (key !== "fasting" && key !== "postprandial") return; chartSeriesVisible.value[key] = !chartSeriesVisible.value[key]; hoverInfo.value = null; scheduleChartRedraw(); } common_vendor.watch(rangeMode, () => { scheduleChartRedraw(); }); common_vendor.watch([chartUseMockData, bloodRecords], () => { scheduleChartRedraw(); }); 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 voiceSize = common_vendor.index.upx2px(80); const voiceGap = common_vendor.index.upx2px(16); let capsulePad = common_vendor.index.upx2px(200); let paddingTopPx = common_vendor.index.upx2px(24); try { const sys = common_vendor.index.getSystemInfoSync(); const statusBarHeight = Number(sys.statusBarHeight) || 0; paddingTopPx = statusBarHeight + common_vendor.index.upx2px(16); 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(12); paddingTopPx = Math.max(paddingTopPx, menu.top); voiceBtnStyle.value = { position: "absolute", right: `${capsulePad}px`, top: `${menu.top + (menu.height - voiceSize) / 2}px` }; } else { voiceBtnStyle.value = {}; } } catch (e) { paddingTopPx = common_vendor.index.upx2px(48); voiceBtnStyle.value = {}; } headerSafeStyle.value = { paddingTop: `${paddingTopPx}px`, paddingRight: `${capsulePad + voiceSize + voiceGap}px` }; headerLeftStyle.value = {}; } async function bootstrap(options) { options = options || {}; initHeaderSafeArea(); isInviteViewer.value = 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; readUserMobile(); if (userMobile.value) { await ensureDailyContextByPhone(void 0, { alert: false }); } } authChecking.value = false; if (diagnosisId.value) { await fetchAll(); } else { bloodRecords.value = []; await common_vendor.nextTick$1(); scheduleChartRedraw(); } } common_vendor.onLoad((options) => { bootstrap(options); }); common_vendor.onShow(() => { initHeaderSafeArea(); try { common_vendor.index.onKeyboardHeightChange(onKeyboardHeightChange); } catch (e) { } if (authChecking.value) return; resetCanvasCache(); common_vendor.nextTick$1(() => scheduleChartRedraw()); }); common_vendor.onPullDownRefresh(async () => { if (await ensureLoggedIn()) { await fetchAll(); } common_vendor.index.stopPullDownRefresh(); }); common_vendor.onHide(() => { ttsStop(); stopSpeechToText(); try { common_vendor.index.offKeyboardHeightChange(onKeyboardHeightChange); } catch (e) { } keyboardHeight.value = 0; }); common_vendor.onUnload(() => { ttsStop(); stopSpeechToText(); try { common_vendor.index.offKeyboardHeightChange(onKeyboardHeightChange); } catch (e) { } keyboardHeight.value = 0; }); const isInviteViewer = common_vendor.ref(false); common_vendor.ref(false); common_vendor.ref(""); const invitePreview = common_vendor.ref({}); common_vendor.ref(false); common_vendor.ref("家人"); common_vendor.ref(false); common_vendor.computed(() => Number(invitePreview.value.tree_level) || 0); common_vendor.computed(() => { const list = invitePreview.value.daily_records; return Array.isArray(list) ? list : []; }); return (_ctx, _cache) => { return common_vendor.e({ a: authChecking.value }, authChecking.value ? {} : {}, { b: !authChecking.value }, !authChecking.value ? common_vendor.e({ c: common_vendor.p({ name: "person", size: "md", color: "#006c49" }), d: common_vendor.t(timeGreetingMeta.value.text), e: common_vendor.t(displayPatientName.value), f: common_vendor.t(todayChecked.value ? "今日血糖已记录" : "今日还没记血糖"), g: common_vendor.s(headerLeftStyle.value), h: common_vendor.p({ name: ttsSpeaking.value ? "pause" : "volume", size: "md", color: "#006c49" }), i: !ttsEnabled.value ? 1 : "", j: ttsSpeaking.value ? 1 : "", k: common_vendor.s(voiceBtnStyle.value), l: common_vendor.o(($event) => ttsSpeaking.value ? ttsStop() : speakLatestSummary(), "7a"), m: common_vendor.s(headerSafeStyle.value), n: cards.value.length > 1 }, cards.value.length > 1 ? { o: common_vendor.f(cards.value, (card, k0, i0) => { return { a: common_vendor.t(card.patient_name || "就诊卡"), b: card.id, c: Number(card.id) === Number(diagnosisId.value) ? 1 : "", d: common_vendor.o(($event) => onSelectCard(card), card.id) }; }) } : {}, { p: dailyContextError.value && !diagnosisId.value }, dailyContextError.value && !diagnosisId.value ? { q: common_vendor.t(dailyContextError.value) } : {}, { r: common_vendor.t(rangeText.value), s: !chartSeriesVisible.value.fasting ? 1 : "", t: common_vendor.o(($event) => toggleChartSeries("fasting"), "b7"), v: !chartSeriesVisible.value.postprandial ? 1 : "", w: common_vendor.o(($event) => toggleChartSeries("postprandial"), "2d"), x: !recordOverlayOpen.value }, !recordOverlayOpen.value ? { y: common_vendor.o(onChartTap, "69") } : {}, { z: recordOverlayOpen.value }, recordOverlayOpen.value ? {} : {}, { A: !chartHasDisplayData.value && !loading.value && !recordOverlayOpen.value }, !chartHasDisplayData.value && !loading.value && !recordOverlayOpen.value ? {} : {}, { B: common_vendor.o(onChartTap, "d7"), C: chartUseMockData.value }, chartUseMockData.value ? {} : {}, { D: common_vendor.p({ name: "sparkles", size: "sm", color: "#006c49" }), E: common_vendor.p({ name: "chevron-right", size: "sm", color: "#64748b" }), F: common_vendor.o(goEndlessGamePage, "69"), G: common_vendor.p({ inline: true, tone: "weekly" }), H: diagnosisId.value }, diagnosisId.value ? common_vendor.e({ I: common_vendor.p({ name: "sparkles", size: "sm", color: "#ffffff" }), J: common_vendor.unref(dietAiLoading) ? 1 : "", K: common_vendor.p({ name: "refresh", size: "sm", color: "#006c49" }), L: common_vendor.t(common_vendor.unref(dietAiLoading) ? "生成中" : "换一换"), M: common_vendor.unref(dietAiLoading) ? 1 : "", N: common_vendor.o((...args) => common_vendor.unref(refreshDietAiRecommend) && common_vendor.unref(refreshDietAiRecommend)(...args), "f5"), O: common_vendor.unref(dietAiLoading) && !common_vendor.unref(hasDietAiMealContent) && !common_vendor.unref(dietAiStreaming) && !common_vendor.unref(dietAiReplacing) }, common_vendor.unref(dietAiLoading) && !common_vendor.unref(hasDietAiMealContent) && !common_vendor.unref(dietAiStreaming) && !common_vendor.unref(dietAiReplacing) ? { P: common_vendor.f(2, (n, k0, i0) => { return { a: "sk-" + n }; }) } : common_vendor.e({ Q: common_vendor.p({ name: "sun", size: "sm", color: "#fb923c" }), R: common_vendor.t(common_vendor.unref(dietAiRecommend).breakfast || (common_vendor.unref(dietAiLoading) ? "…" : "—")), S: common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "breakfast" }, common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "breakfast" ? {} : {}, { T: common_vendor.unref(dietAiRecommend).drinks || common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "drinks" }, common_vendor.unref(dietAiRecommend).drinks || common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "drinks" ? common_vendor.e({ U: common_vendor.p({ name: "droplet", size: "sm", color: "#0ea5e9" }), V: common_vendor.t(common_vendor.unref(dietAiRecommend).drinks || "…"), W: common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "drinks" }, common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "drinks" ? {} : {}) : {}, { X: common_vendor.p({ name: "leaf", size: "sm", color: "#006c49" }), Y: common_vendor.t(common_vendor.unref(dietAiRecommend).lunch || (common_vendor.unref(dietAiLoading) ? "…" : "—")), Z: common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "lunch" }, common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "lunch" ? {} : {}, { aa: common_vendor.unref(dietAiRecommend).dinner || common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "dinner" }, common_vendor.unref(dietAiRecommend).dinner || common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "dinner" ? common_vendor.e({ ab: common_vendor.p({ name: "moon", size: "sm", color: "#7e85cc" }), ac: common_vendor.t(common_vendor.unref(dietAiRecommend).dinner || "…"), ad: common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "dinner" }, common_vendor.unref(dietAiLoading) && common_vendor.unref(dietAiStreamField) === "dinner" ? {} : {}) : {})) : {}, { ae: diagnosisId.value && floatAskClosed.value }, diagnosisId.value && floatAskClosed.value ? { af: common_vendor.p({ name: "sparkles", size: "sm", color: "#ffffff" }), ag: common_vendor.o(($event) => floatAskClosed.value = false, "e1") } : {}, { ah: diagnosisId.value && !floatAskClosed.value }, diagnosisId.value && !floatAskClosed.value ? common_vendor.e({ ai: common_vendor.o(($event) => floatAskClosed.value = true, "16"), aj: common_vendor.p({ name: "sparkles", size: "sm", color: "#006c49" }), ak: common_vendor.unref(dietAiAskText), al: -1, am: common_vendor.o((...args) => common_vendor.unref(onDietAiAskInput) && common_vendor.unref(onDietAiAskInput)(...args), "67"), an: common_vendor.o((...args) => common_vendor.unref(askDietAiFood) && common_vendor.unref(askDietAiFood)(...args), "3e"), ao: common_vendor.o(onDietAiAskFocus, "58"), ap: common_vendor.o(onDietAiAskBlur, "ac"), aq: common_vendor.p({ name: "mic", size: "sm", color: common_vendor.unref(sttListening) || common_vendor.unref(sttHolding) ? "#ffffff" : "#006c49" }), ar: common_vendor.unref(sttListening) || common_vendor.unref(sttHolding) ? 1 : "", as: common_vendor.unref(dietAiAsking) || !common_vendor.unref(sttSupported) ? 1 : "", at: common_vendor.o(onDietAiSpeechStart, "69"), av: common_vendor.o(onDietAiSpeechEnd, "a9"), aw: common_vendor.o(onDietAiSpeechEnd, "78"), ax: common_vendor.p({ name: "send", size: "sm", color: "#ffffff" }), ay: common_vendor.unref(dietAiAsking) ? 1 : "", az: common_vendor.o((...args) => common_vendor.unref(askDietAiFood) && common_vendor.unref(askDietAiFood)(...args), "8f"), aA: dietAiAskExpanded.value ? 1 : "", aB: common_vendor.unref(sttHolding) && !common_vendor.unref(dietAiAsking) }, common_vendor.unref(sttHolding) && !common_vendor.unref(dietAiAsking) ? { aC: common_vendor.t(common_vendor.unref(sttListening) ? "松手结束" : "准备录音…") } : {}, { aD: common_vendor.unref(dietAiAsking) || common_vendor.unref(dietAiAskResult).advice }, common_vendor.unref(dietAiAsking) || common_vendor.unref(dietAiAskResult).advice ? common_vendor.e({ aE: common_vendor.unref(dietAiAskResult).level_label }, common_vendor.unref(dietAiAskResult).level_label ? { aF: common_vendor.t(common_vendor.unref(dietAiAskResult).level_label) } : {}, { aG: common_vendor.t(common_vendor.unref(dietAiAskResult).advice || (common_vendor.unref(dietAiAsking) ? "正在想…" : "")), aH: common_vendor.unref(dietAiAsking) }, common_vendor.unref(dietAiAsking) ? {} : {}) : {}) : {}, { aI: common_vendor.unref(sttHolding) && !common_vendor.unref(dietAiAsking) }, common_vendor.unref(sttHolding) && !common_vendor.unref(dietAiAsking) ? { aJ: common_vendor.p({ name: "mic", size: "lg", color: "#ffffff" }), aK: common_vendor.unref(sttListening) ? 1 : "", aL: common_vendor.t(common_vendor.unref(sttListening) ? "正在聆听…" : "准备录音…") } : {}, { aM: phoneGateVisible.value }, phoneGateVisible.value ? common_vendor.e({ aN: common_vendor.o(closePhoneGate, "0f"), aO: dailyContextError.value }, dailyContextError.value ? { aP: common_vendor.t(dailyContextError.value) } : {}, { aQ: phoneGateSex.value === 1 ? 1 : "", aR: common_vendor.o(($event) => phoneGateSex.value = 1, "ac"), aS: phoneGateSex.value === 2 ? 1 : "", aT: common_vendor.o(($event) => phoneGateSex.value = 2, "32"), aU: phoneBinding.value, aV: phoneBinding.value || !phoneGateSex.value, aW: common_vendor.o(onPhoneGateAuthorize, "7f"), aX: common_vendor.o(() => { }, "8f"), aY: common_vendor.o(closePhoneGate, "54") }) : {}, { aZ: inputForm.value.visible }, inputForm.value.visible ? common_vendor.e({ ba: common_vendor.t(inputForm.value.id ? "修改今日血糖" : "录入今日血糖"), bb: common_vendor.t(todayText.value), bc: common_vendor.t(patientName.value || "本人"), bd: common_vendor.o(closeInputForm, "0a"), be: inputForm.value.loading }, inputForm.value.loading ? {} : { bf: inputForm.value.fasting_blood_sugar, bg: common_vendor.o(($event) => onInputField("fasting_blood_sugar", $event), "49"), bh: inputForm.value.postprandial_blood_sugar, bi: common_vendor.o(($event) => onInputField("postprandial_blood_sugar", $event), "1a"), bj: inputForm.value.other_blood_sugar, bk: common_vendor.o(($event) => onInputField("other_blood_sugar", $event), "e2"), bl: inputForm.value.remark, bm: common_vendor.o(($event) => onInputField("remark", $event), "50") }, { bn: inputForm.value.id }, inputForm.value.id ? { bo: inputForm.value.submitting ? 1 : "", bp: common_vendor.o(deleteInputRecord, "47") } : {}, { bq: common_vendor.t(inputForm.value.submitting ? "提交中…" : inputForm.value.id ? "保存修改" : "提交录入"), br: inputForm.value.submitting || inputForm.value.loading ? 1 : "", bs: common_vendor.o(submitInputForm, "b4"), bt: common_vendor.s(keyboardHeight.value ? { marginBottom: keyboardHeight.value + "px" } : {}), bv: common_vendor.o(() => { }, "e5"), bw: common_vendor.o(closeInputForm, "b3") }) : {}) : {}, { bx: common_vendor.p({ active: 0 }) }); }; } }; const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-b5693e32"]]); _sfc_main.__runtimeHooks = 2; wx.createPage(MiniProgramPage); //# sourceMappingURL=../../../.sourcemap/mp-weixin/tongji/pages/weekly.js.map