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