2117 lines
77 KiB
JavaScript
2117 lines
77 KiB
JavaScript
"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
|