首页工具区下新增「我的孕期数据」:体重/血压/血糖自动读取 v26103104
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
99
js/app.js
99
js/app.js
@@ -3,7 +3,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
/* 页面版本号(YYMMDD+序号):显示在「关于兜爸心选」页底部;bump 时同步 head 注释与 ?v= */
|
||||
var VER = '26103103';
|
||||
var VER = '26103104';
|
||||
const $ = (s, c) => (c || document).querySelector(s);
|
||||
const $$ = (s, c) => Array.from((c || document).querySelectorAll(s));
|
||||
|
||||
@@ -3355,6 +3355,7 @@
|
||||
applyRemoteAll();
|
||||
applyRemoteFootprint();
|
||||
applyRemoteCardsFetch();
|
||||
loadVitals();
|
||||
syncLaixinReminder();
|
||||
loadBlindbox().then(() => { // 孕期盲盒:要等 ② 的预产期落地(服务端按同一份上游视图算孕周)
|
||||
/* 2026-10-07 用户要盲盒直达链接:地址带 open=box(# 或 ? 里都行)→ 数据到了直接弹盲盒面板。
|
||||
@@ -5148,6 +5149,102 @@
|
||||
],
|
||||
} : null;
|
||||
|
||||
/* ---------- 我的孕期数据(体重 / 血压 / 血糖,2026-10-31 用户定) ----------
|
||||
上游 /user/app-cards 只聚合了五张工具卡,没有血压、血糖 → 这里直接读三个工具自己的数据(只读):
|
||||
· 体重 yunqi-tizhong:GET /taidong/api/state → pw_data_v1.weights(跳过 demo:true 示例记录)
|
||||
· 血糖 yunqi-xuetang:GET /taidong/api/state → tmrj.v1.readings(跳过 demo:1 与老种子 id 如 r29fasting)
|
||||
· 血压 yunqi-xueya :GET /taidong/api/xueya/records(服务端只存真实记录)
|
||||
每个工具单独 openid-login 换 token(state 按 app_id 分区),token 在 sessionStorage 缓存 20 分钟。
|
||||
回到前台 / bfcache 恢复时重拉:妈妈在工具里刚填完,回首页就看到新数。 */
|
||||
const VT_API = '/taidong/api';
|
||||
const VT_SLOT = { fasting: '空腹', b1: '早餐后1h', b2: '早餐后2h', l1: '午餐后1h', l2: '午餐后2h', d1: '晚餐后1h', d2: '晚餐后2h', bed: '睡前', night: '夜间' };
|
||||
const VT_TGT = { fasting: [3.3, 5.3], b1: [3.3, 7.8], l1: [3.3, 7.8], d1: [3.3, 7.8], b2: [3.3, 6.7], l2: [3.3, 6.7], d2: [3.3, 6.7], bed: [4.4, 6.7], night: [3.3, 6.7] };
|
||||
let _vtAt = 0;
|
||||
function vtToken(app) {
|
||||
const openid = hashParam('openid'), unionid = hashParam('unionid');
|
||||
const ck = 'mmbox.vt.tok.' + app;
|
||||
try { const c = JSON.parse(sessionStorage.getItem(ck) || 'null'); if (c && c.o === openid && Date.now() - c.t < 20 * 60e3) return Promise.resolve(c.k); } catch (e) {}
|
||||
return fetch(VT_API + '/auth/openid-login', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ openid: openid, unionid: unionid || null, app_id: app })
|
||||
}).then(r => (r.ok ? r.json() : null)).then(j => {
|
||||
const k = j && (j.token || (j.data && j.data.token)); if (!k) return null;
|
||||
try { sessionStorage.setItem(ck, JSON.stringify({ o: openid, k: k, t: Date.now() })); } catch (e) {}
|
||||
return k;
|
||||
});
|
||||
}
|
||||
function vtGet(app, path) {
|
||||
return vtToken(app).then(k => k ? fetch(VT_API + path, { headers: { Authorization: 'Bearer ' + k } }).then(r => (r.ok ? r.json() : null)) : null).catch(() => null);
|
||||
}
|
||||
function vtVal(items, key) { /* state 条目可能是 {value:"…json…"} 或直接值 */
|
||||
let v = items && items[key];
|
||||
if (v && typeof v === 'object' && 'value' in v) v = v.value;
|
||||
if (typeof v === 'string') { try { v = JSON.parse(v); } catch (e) { v = null; } }
|
||||
return v && typeof v === 'object' ? v : null;
|
||||
}
|
||||
function vtDay(d) { /* 'YYYY-MM-DD…' 或时间戳 → 今天 / 昨天 / M月D日 */
|
||||
const t = typeof d === 'number' ? new Date(d) : parseDay(String(d || '').slice(0, 10));
|
||||
if (!t || isNaN(t)) return '';
|
||||
const a = new Date(); a.setHours(0, 0, 0, 0);
|
||||
const b = new Date(t); b.setHours(0, 0, 0, 0);
|
||||
const n = Math.round((a - b) / DAY_MS);
|
||||
return n === 0 ? '今天' : n === 1 ? '昨天' : (b.getMonth() + 1) + '月' + b.getDate() + '日';
|
||||
}
|
||||
function vtSet(key, val, pill, cls, when) {
|
||||
const it = $('.vt-it[data-vt="' + key + '"]'); if (!it) return;
|
||||
const id = { weight: 'vtW', bp: 'vtP', glu: 'vtG' }[key];
|
||||
it.classList.toggle('empty', !val);
|
||||
$('#' + id).textContent = val || '--';
|
||||
const s = $('#' + id + 's'); s.textContent = val ? (pill || '') : '去记一笔 ›'; s.className = 'vt-s' + (val && cls ? ' ' + cls : '');
|
||||
$('#' + id + 'd').textContent = val ? (when || '') : '';
|
||||
}
|
||||
function loadVitals() {
|
||||
const box = $('#vitals'); if (!box) return;
|
||||
if (!hashParam('openid') || state.postpartum) { box.classList.add('hide'); return; }
|
||||
box.classList.remove('hide');
|
||||
_vtAt = Date.now();
|
||||
vtGet('yunqi-tizhong', '/state').then(j => {
|
||||
const pw = vtVal(j && j.items, 'pw_data_v1');
|
||||
const ws = ((pw && pw.weights) || []).filter(w => w && !w.demo && +w.weight > 0).sort((a, b) => String(a.date).localeCompare(String(b.date)) || (a.updatedAt || 0) - (b.updatedAt || 0));
|
||||
const last = ws[ws.length - 1];
|
||||
if (!last) { vtSet('weight', ''); return; }
|
||||
const pre = pw && !pw.demo && pw.profile && +pw.profile.preWeight;
|
||||
const gain = pre ? Math.round((last.weight - pre) * 10) / 10 : null;
|
||||
vtSet('weight', (+last.weight).toFixed(1), gain != null ? '比孕前 ' + (gain >= 0 ? '+' : '') + gain : '', '', vtDay(last.date) + '记录');
|
||||
});
|
||||
vtGet('yunqi-xueya', '/xueya/records?limit=5').then(j => {
|
||||
const rs = ((j && j.items) || []).filter(r => r && r.systolic && r.diastolic).sort((a, b) => String(b.measured_at).localeCompare(String(a.measured_at)));
|
||||
const r = rs[0];
|
||||
if (!r) { vtSet('bp', ''); return; }
|
||||
const sy = +r.systolic, di = +r.diastolic;
|
||||
/* 孕期口径:≥140/90 偏高(≥160/110 很高),<90/60 偏低 */
|
||||
const st = (sy >= 160 || di >= 110) ? ['很高,请就医', 'hi'] : (sy >= 140 || di >= 90) ? ['偏高', 'hi'] : (sy < 90 || di < 60) ? ['偏低', 'lo'] : ['正常', 'ok'];
|
||||
vtSet('bp', sy + '/' + di, st[0], st[1], vtDay(r.measured_at) + '记录');
|
||||
});
|
||||
vtGet('yunqi-xuetang', '/state').then(j => {
|
||||
const tm = vtVal(j && j.items, 'tmrj.v1');
|
||||
const rs = ((tm && tm.readings) || []).filter(r => r && !r.demo && !/^r\d{1,3}[a-z]/.test(String(r.id || '')) && +r.value > 0).sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
||||
const r = rs[rs.length - 1];
|
||||
if (!r) { vtSet('glu', ''); return; }
|
||||
const t = (tm.targets && tm.targets[r.slot]) || VT_TGT[r.slot] || [3.3, 7.8];
|
||||
const st = r.value > t[1] ? ['偏高', 'hi'] : r.value < t[0] ? ['偏低', 'lo'] : ['达标', 'ok'];
|
||||
vtSet('glu', (+r.value).toFixed(1), st[0], st[1], vtDay(r.ts) + (VT_SLOT[r.slot] ? ' · ' + VT_SLOT[r.slot] : ''));
|
||||
});
|
||||
}
|
||||
$$('.vt-it[data-vt]').forEach(b => b.addEventListener('click', () => {
|
||||
const k = b.dataset.vt;
|
||||
if (k === 'weight') { const c = $('.mini-card[data-mini="weight"]'); if (c) { c.click(); return; } }
|
||||
const id = { weight: 'yunqitizhong', bp: 'yun', glu: 'yunqixuetang' }[k];
|
||||
const app = APP_BY_ID[id];
|
||||
if (app && app.url) { jumpApp(app.url, app.identifier); return; }
|
||||
const path = { weight: '/yunqi-tizhong/', bp: '/yunqi-xueya/', glu: '/yunqi-xuetang/' }[k];
|
||||
haptic(); location.href = withIdentity(location.origin + path);
|
||||
}));
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (document.visibilityState === 'visible' && _vtAt && Date.now() - _vtAt > 3000) loadVitals();
|
||||
});
|
||||
window.addEventListener('pageshow', function (e) { if (e.persisted && _vtAt) loadVitals(); });
|
||||
|
||||
function applyRemoteCardsFetch() {
|
||||
if (DEMO_CARDS) { applyRemoteCards(DEMO_CARDS); return; }
|
||||
const openid = hashParam('openid');
|
||||
|
||||
Reference in New Issue
Block a user