<template>
    <view class="daily-page">
        <!-- 顶部 Hero（智能问候 + 今日状态）-->
        <view class="hero">
            <view class="hero-bg" />
            <!-- 漂浮装饰圆 —— 增添视觉层次 -->
            <view class="hero-orb hero-orb-1" />
            <view class="hero-orb hero-orb-2" />
            <view class="hero-orb hero-orb-3" />
            <view class="hero-content">
                <view class="hero-row">
                    <view class="hero-main">
                        <view class="hero-greet-row">
                            <text class="hero-greet-emoji">{{ timeGreetingEmoji }}</text>
                            <text class="hero-greet-text">{{ timeGreeting }}</text>
                        </view>
                        <text class="hero-title">{{ patientName || '健康档案' }}</text>
                        <view class="hero-subtitle-row">
                            <text class="hero-subtitle">
                                {{ ageText }}<text v-if="genderText"> · {{ genderText }}</text>
                            </text>
                            <view
                                class="hero-status-chip"
                                :class="todayChecked ? 'done' : 'pending'"
                            >
                                <text class="hero-status-icon">{{ todayChecked ? '✓' : '!' }}</text>
                                <text class="hero-status-text">{{ todayChecked ? '今日已记录' : '今日待记录' }}</text>
                            </view>
                        </view>
                    </view>
                    <view class="hero-pill-group">
                        <view class="hero-pill input" @click="openInputForm">
                            <text class="hero-pill-icon">＋</text>
                            <text class="hero-pill-text">录入今日</text>
                        </view>
                        <view
                            class="hero-pill voice"
                            :class="{ speaking: ttsSpeaking, disabled: !ttsEnabled }"
                            @click="ttsSpeaking ? ttsStop() : speakLatestSummary()"
                        >
                            <text class="hero-pill-icon">{{ ttsSpeaking ? '⏸' : '🔊' }}</text>
                            <text class="hero-pill-text">{{ ttsSpeaking ? '停止' : '朗读' }}</text>
                        </view>
                        <view class="hero-pill" @click="onRefresh">
                            <text class="hero-pill-icon">↻</text>
                            <text class="hero-pill-text">刷新</text>
                        </view>
                    </view>
                </view>
            </view>
        </view>

        <!-- 三大核心指标卡（白底悬浮，强调最近健康数值）-->
        <view class="stat-grid">
            <view
                class="stat-card stat-fasting"
                :class="{ 'has-data': latestStat.fasting.value !== '—', 'is-high': latestStat.fasting.isHigh }"
            >
                <view class="stat-card-head">
                    <view class="stat-card-tag">
                        <text class="stat-card-tag-dot" />
                        <text class="stat-card-tag-text">空腹血糖</text>
                    </view>
                    <view
                        v-if="latestStat.fasting.isHigh"
                        class="stat-card-flag"
                    >
                        <text class="stat-card-flag-icon">▲</text>
                        <text class="stat-card-flag-text">偏高</text>
                    </view>
                </view>
                <view class="stat-card-value-row">
                    <text class="stat-card-value" :class="{ 'is-high': latestStat.fasting.isHigh }">
                        {{ latestStat.fasting.value }}
                    </text>
                    <text class="stat-card-unit">mmol/L</text>
                </view>
                <view class="stat-card-foot">
                    <text class="stat-card-date">{{ latestStat.fasting.date }}</text>
                    <view
                        v-if="latestStat.fasting.trend"
                        class="stat-card-trend"
                        :class="`trend-${latestStat.fasting.trend.dir}`"
                    >
                        <text class="stat-card-trend-arrow">
                            {{ latestStat.fasting.trend.dir === 'up' ? '↑' : latestStat.fasting.trend.dir === 'down' ? '↓' : '→' }}
                        </text>
                        <text class="stat-card-trend-delta">{{ latestStat.fasting.trend.deltaText }}</text>
                    </view>
                </view>
            </view>

            <view
                class="stat-card stat-postprandial"
                :class="{ 'has-data': latestStat.postprandial.value !== '—', 'is-high': latestStat.postprandial.isHigh }"
            >
                <view class="stat-card-head">
                    <view class="stat-card-tag">
                        <text class="stat-card-tag-dot" />
                        <text class="stat-card-tag-text">餐后血糖</text>
                    </view>
                    <view
                        v-if="latestStat.postprandial.isHigh"
                        class="stat-card-flag"
                    >
                        <text class="stat-card-flag-icon">▲</text>
                        <text class="stat-card-flag-text">偏高</text>
                    </view>
                </view>
                <view class="stat-card-value-row">
                    <text class="stat-card-value" :class="{ 'is-high': latestStat.postprandial.isHigh }">
                        {{ latestStat.postprandial.value }}
                    </text>
                    <text class="stat-card-unit">mmol/L</text>
                </view>
                <view class="stat-card-foot">
                    <text class="stat-card-date">{{ latestStat.postprandial.date }}</text>
                    <view
                        v-if="latestStat.postprandial.trend"
                        class="stat-card-trend"
                        :class="`trend-${latestStat.postprandial.trend.dir}`"
                    >
                        <text class="stat-card-trend-arrow">
                            {{ latestStat.postprandial.trend.dir === 'up' ? '↑' : latestStat.postprandial.trend.dir === 'down' ? '↓' : '→' }}
                        </text>
                        <text class="stat-card-trend-delta">{{ latestStat.postprandial.trend.deltaText }}</text>
                    </view>
                </view>
            </view>

            <view
                class="stat-card stat-other"
                :class="{ 'has-data': latestStat.other.value !== '—', 'is-high': latestStat.other.isHigh }"
            >
                <view class="stat-card-head">
                    <view class="stat-card-tag">
                        <text class="stat-card-tag-dot" />
                        <text class="stat-card-tag-text">其他血糖</text>
                    </view>
                    <view
                        v-if="latestStat.other.isHigh"
                        class="stat-card-flag"
                    >
                        <text class="stat-card-flag-icon">▲</text>
                        <text class="stat-card-flag-text">偏高</text>
                    </view>
                </view>
                <view class="stat-card-value-row">
                    <text class="stat-card-value" :class="{ 'is-high': latestStat.other.isHigh }">
                        {{ latestStat.other.value }}
                    </text>
                    <text class="stat-card-unit">mmol/L</text>
                </view>
                <view class="stat-card-foot">
                    <text class="stat-card-date">{{ latestStat.other.date }}</text>
                    <view
                        v-if="latestStat.other.trend"
                        class="stat-card-trend"
                        :class="`trend-${latestStat.other.trend.dir}`"
                    >
                        <text class="stat-card-trend-arrow">
                            {{ latestStat.other.trend.dir === 'up' ? '↑' : latestStat.other.trend.dir === 'down' ? '↓' : '→' }}
                        </text>
                        <text class="stat-card-trend-delta">{{ latestStat.other.trend.deltaText }}</text>
                    </view>
                </view>
            </view>
        </view>

        <!-- 打卡条：连续天数 + 近 7 天 dots —— 增加坚持感 -->
        <view class="streak-strip" @click="openInputForm">
            <view class="streak-strip-left">
                <view class="streak-flame" :class="{ active: streakDays.days >= 3 }">
                    <text class="streak-flame-icon">{{ streakDays.days === 0 ? '✨' : '🔥' }}</text>
                </view>
                <view class="streak-info">
                    <view class="streak-num-row">
                        <text class="streak-num">{{ streakDays.days }}</text>
                        <text class="streak-num-unit">天</text>
                        <text v-if="streakDays.days > 0 && streakDays.startedFromYesterday" class="streak-hint">截至昨天</text>
                    </view>
                    <text class="streak-label">{{ streakDays.days === 0 ? '记录身体每一天的变化' : '连续记录，坚持就是胜利' }}</text>
                </view>
            </view>
            <view class="streak-strip-right">
                <view class="streak-dots-row">
                    <view
                        v-for="dot in last7Dots"
                        :key="dot.date"
                        class="streak-dot"
                        :class="{ done: dot.hasRecord, today: dot.isToday }"
                    >
                        <text class="streak-dot-label">{{ dot.label }}</text>
                        <view class="streak-dot-mark" />
                    </view>
                </view>
                <text class="streak-week-text">本周 {{ last7CheckedCount }} / 7</text>
            </view>
        </view>

        <!-- 就诊卡选择器（精简为药丸 chip）-->
        <view v-if="cards.length > 0" class="card-switcher">
            <view class="card-switcher-head">
                <text class="card-switcher-label">就诊卡</text>
                <text class="card-switcher-count">{{ cards.length }} 张</text>
            </view>
            <scroll-view scroll-x class="card-switcher-scroll" :show-scrollbar="false">
                <view class="card-chip-row">
                    <view
                        v-for="card in cards"
                        :key="card.id"
                        class="card-chip"
                        :class="{ active: Number(card.id) === Number(diagnosisId) }"
                        @click="onSelectCard(card)"
                    >
                        <text class="card-chip-name">{{ card.patient_name || '未填' }}</text>
                        <text v-if="card.age" class="card-chip-meta">{{ card.age }}岁</text>
                    </view>
                </view>
            </scroll-view>
        </view>

        <!-- 时间范围 -->
        <view class="range-bar">
            <view
                v-for="opt in rangeOptions"
                :key="opt.value"
                class="range-item"
                :class="{ active: rangeMode === opt.value }"
                @click="onChangeRange(opt.value)"
            >
                <text>{{ opt.label }}</text>
            </view>
        </view>

        <!-- 语音播报设置（适老化）-->
        <view class="voice-bar">
            <view class="voice-bar-left">
                <text class="voice-bar-icon">🔊</text>
                <view class="voice-bar-text">
                    <text class="voice-bar-title">语音播报</text>
                    <text class="voice-bar-hint">为长辈准备 · 大字号 · 慢语速</text>
                </view>
            </view>
            <view class="voice-bar-actions">
                <view
                    class="voice-switch"
                    :class="{ on: ttsAutoPlay }"
                    @click="ttsToggleAutoPlay"
                >
                    <text class="voice-switch-label">自动</text>
                    <view class="voice-switch-track">
                        <view class="voice-switch-knob" />
                    </view>
                </view>
                <view
                    class="voice-toggle"
                    :class="{ off: !ttsEnabled }"
                    @click="ttsToggleEnabled"
                >
                    <text class="voice-toggle-icon">{{ ttsEnabled ? '🔊' : '🔇' }}</text>
                </view>
            </view>
        </view>

        <!-- 血糖波浪图 -->
        <view class="card chart-card">
            <view class="card-head">
                <view class="card-head-left">
                    <text class="card-title">血糖趋势</text>
                    <text class="card-subtitle">{{ rangeText }} · mmol/L</text>
                </view>
                <view class="card-legend">
                    <view class="legend-item">
                        <view class="legend-dot legend-dot-fasting" />
                        <text>空腹</text>
                    </view>
                    <view class="legend-item">
                        <view class="legend-dot legend-dot-postprandial" />
                        <text>餐后</text>
                    </view>
                </view>
            </view>
            <view v-if="bloodHasData && bloodHighDayCount > 0" class="chart-summary">
                <view class="chart-summary-item">
                    <text class="chart-summary-num">{{ bloodHighDayCount }}</text>
                    <text class="chart-summary-label">天偏高</text>
                </view>
                <view class="chart-summary-item">
                    <text class="chart-summary-num">{{ bloodNormalDayCount }}</text>
                    <text class="chart-summary-label">天正常</text>
                </view>
                <view class="chart-summary-item">
                    <text class="chart-summary-num">{{ bloodRecordDayCount }}</text>
                    <text class="chart-summary-label">天有记录</text>
                </view>
            </view>

            <view class="chart-canvas-wrap">
                <!--
                  注意：微信小程序 <canvas> 是原生组件，z-index 无法被普通 view 覆盖，
                  打开任何弹层时必须 v-if 卸载它，避免穿透到弹层上方。
                -->
                <canvas
                    v-if="!inputForm.visible"
                    id="bloodWaveChart"
                    type="2d"
                    class="chart-canvas"
                    @touchstart="onChartTouch"
                    @touchmove="onChartTouch"
                />
                <view v-if="inputForm.visible" class="chart-canvas-placeholder">
                    <text>图表已暂停显示</text>
                </view>
                <view v-if="!bloodHasData && !loading && !inputForm.visible" class="chart-empty">
                    <text class="chart-empty-icon">📈</text>
                    <text class="chart-empty-text">当前时间范围暂无血糖数据</text>
                </view>
                <view v-if="hoverInfo && !inputForm.visible" class="chart-tip" :style="{ left: hoverInfo.left + 'px', top: hoverInfo.top + 'px' }">
                    <text class="chart-tip-date">{{ hoverInfo.date }}</text>
                    <view v-if="hoverInfo.fasting != null" class="chart-tip-row">
                        <view class="legend-dot legend-dot-fasting" />
                        <text class="chart-tip-text">空腹 {{ hoverInfo.fasting }}</text>
                    </view>
                    <view v-if="hoverInfo.postprandial != null" class="chart-tip-row">
                        <view class="legend-dot legend-dot-postprandial" />
                        <text class="chart-tip-text">餐后 {{ hoverInfo.postprandial }}</text>
                    </view>
                </view>
            </view>
        </view>

        <!-- 健康日历：与手机日历一致的月历布局，老人一眼能看懂 -->
        <view v-if="heatmapWeeks.length > 0" class="card calendar-card">
            <view class="calendar-head">
                <view class="calendar-head-left">
                    <text class="card-title">健康日历</text>
                    <text class="card-subtitle">{{ heatmapMonthLabel }} · 已记录 {{ heatmapTotalRecords }} / {{ heatmapInRangeDays }} 天</text>
                </view>
                <view class="calendar-stat">
                    <text class="calendar-stat-num">{{ heatmapTotalRecords }}</text>
                    <text class="calendar-stat-label">天有记录</text>
                </view>
            </view>

            <!-- 星期表头 -->
            <view class="calendar-weekdays">
                <text class="calendar-weekday">一</text>
                <text class="calendar-weekday">二</text>
                <text class="calendar-weekday">三</text>
                <text class="calendar-weekday">四</text>
                <text class="calendar-weekday">五</text>
                <text class="calendar-weekday">六</text>
                <text class="calendar-weekday calendar-weekday-weekend">日</text>
            </view>

            <!-- 月历主体：周→行，星期→列 -->
            <view class="calendar-grid">
                <view
                    v-for="(week, wi) in heatmapWeeks"
                    :key="`wk-${wi}`"
                    class="calendar-row"
                >
                    <view
                        v-for="cell in week"
                        :key="cell.date"
                        class="calendar-cell"
                        :class="[
                            cell.level < 0 ? 'off' : `level-${cell.level}`,
                            {
                                today: cell.isToday,
                                clickable: cell.inRange && cell.level > 0
                            }
                        ]"
                        @click="cell.inRange && cell.level > 0 ? jumpToDayRecord(cell.date) : null"
                    >
                        <text class="calendar-cell-day">{{ cell.day }}</text>
                        <view v-if="cell.level > 0" class="calendar-cell-mark" />
                        <view v-if="cell.isToday" class="calendar-cell-today-tag">
                            <text>今天</text>
                        </view>
                    </view>
                </view>
            </view>

            <!-- 图例 + 行动号召 -->
            <view class="calendar-legend">
                <view class="calendar-legend-row">
                    <view class="calendar-legend-item">
                        <view class="calendar-legend-cell level-1" />
                        <text class="calendar-legend-text">少量</text>
                    </view>
                    <view class="calendar-legend-item">
                        <view class="calendar-legend-cell level-2" />
                        <text class="calendar-legend-text">较多</text>
                    </view>
                    <view class="calendar-legend-item">
                        <view class="calendar-legend-cell level-3" />
                        <text class="calendar-legend-text">完整</text>
                    </view>
                    <view class="calendar-legend-item">
                        <view class="calendar-legend-cell legend-today" />
                        <text class="calendar-legend-text">今日</text>
                    </view>
                </view>
            </view>

            <view class="heatmap-foot">
                <text class="heatmap-foot-text">{{ todayChecked ? '今日已完成记录' : '今日还没有记录，加油' }}</text>
                <view v-if="!todayChecked" class="heatmap-foot-cta" @click="openInputForm">
                    <text>立即录入</text>
                </view>
            </view>
        </view>

        <!-- 日常记录列表 -->
        <view class="records-section">
            <view class="records-head">
                <text class="card-title">日常记录</text>
                <text class="records-count">共 {{ recordDays.length }} 天</text>
            </view>

            <view v-if="loading" class="state-card">
                <text class="state-text">加载中...</text>
            </view>
            <view v-else-if="recordDays.length === 0" class="state-card">
                <text class="state-icon">📭</text>
                <text class="state-text">该时段暂无任何记录</text>
            </view>

            <view
                v-for="day in recordDays"
                :key="day.date"
                :id="`day-${day.date.replace(/-/g, '')}`"
                class="day-card"
            >
                <view class="day-head">
                    <view class="day-head-left">
                        <text class="day-date">{{ day.dateLabel }}</text>
                        <text v-if="day.isToday" class="day-today-pill">今天</text>
                    </view>
                    <view class="day-head-right">
                        <text class="day-weekday">{{ day.weekday }}</text>
                        <view class="day-speak-btn" @click="speakDay(day)">
                            <text class="day-speak-icon">🔊</text>
                            <text class="day-speak-text">朗读</text>
                        </view>
                    </view>
                </view>

                <!-- 血糖血压 -->
                <view v-if="day.blood" class="day-block">
                    <view class="day-block-head">
                        <view class="day-block-tag tag-blood">血糖血压</view>
                        <text v-if="day.blood.record_time" class="day-block-time">{{ day.blood.record_time }}</text>
                    </view>
                    <view class="metric-grid">
                        <view class="metric-cell">
                            <view class="metric-row-head">
                                <text class="metric-label">空腹</text>
                                <view
                                    v-if="day.blood.isHighFasting"
                                    class="metric-flag up"
                                >
                                    <text class="metric-flag-icon">▲</text>
                                    <text class="metric-flag-text">偏高</text>
                                </view>
                            </view>
                            <view class="metric-value-row">
                                <text class="metric-value" :class="{ 'is-high': day.blood.isHighFasting }">
                                    {{ formatValue(day.blood.fasting_blood_sugar) }}
                                </text>
                                <text class="metric-unit">mmol/L</text>
                            </view>
                            <view
                                v-if="getDayTrend(day.date, 'fasting_blood_sugar')"
                                class="metric-trend"
                                :class="`trend-${getDayTrend(day.date, 'fasting_blood_sugar').dir}`"
                            >
                                <text class="metric-trend-arrow">
                                    {{ getDayTrend(day.date, 'fasting_blood_sugar').dir === 'up' ? '↑' : getDayTrend(day.date, 'fasting_blood_sugar').dir === 'down' ? '↓' : '→' }}
                                </text>
                                <text class="metric-trend-text">{{ getDayTrend(day.date, 'fasting_blood_sugar').deltaText }}</text>
                            </view>
                        </view>
                        <view class="metric-cell">
                            <view class="metric-row-head">
                                <text class="metric-label">餐后</text>
                                <view
                                    v-if="day.blood.isHighPostprandial"
                                    class="metric-flag up"
                                >
                                    <text class="metric-flag-icon">▲</text>
                                    <text class="metric-flag-text">偏高</text>
                                </view>
                            </view>
                            <view class="metric-value-row">
                                <text class="metric-value" :class="{ 'is-high': day.blood.isHighPostprandial }">
                                    {{ formatValue(day.blood.postprandial_blood_sugar) }}
                                </text>
                                <text class="metric-unit">mmol/L</text>
                            </view>
                            <view
                                v-if="getDayTrend(day.date, 'postprandial_blood_sugar')"
                                class="metric-trend"
                                :class="`trend-${getDayTrend(day.date, 'postprandial_blood_sugar').dir}`"
                            >
                                <text class="metric-trend-arrow">
                                    {{ getDayTrend(day.date, 'postprandial_blood_sugar').dir === 'up' ? '↑' : getDayTrend(day.date, 'postprandial_blood_sugar').dir === 'down' ? '↓' : '→' }}
                                </text>
                                <text class="metric-trend-text">{{ getDayTrend(day.date, 'postprandial_blood_sugar').deltaText }}</text>
                            </view>
                        </view>
                        <view class="metric-cell">
                            <view class="metric-row-head">
                                <text class="metric-label">血压</text>
                                <view
                                    v-if="day.blood.isHighBp"
                                    class="metric-flag up"
                                >
                                    <text class="metric-flag-icon">▲</text>
                                    <text class="metric-flag-text">偏高</text>
                                </view>
                            </view>
                            <view class="metric-value-row">
                                <text class="metric-value" :class="{ 'is-high': day.blood.isHighBp }">
                                    {{ day.blood.bpText }}
                                </text>
                                <text class="metric-unit">mmHg</text>
                            </view>
                            <view
                                v-if="getDayBpTrend(day.date)"
                                class="metric-trend"
                                :class="`trend-${getDayBpTrend(day.date).dir}`"
                            >
                                <text class="metric-trend-arrow">
                                    {{ getDayBpTrend(day.date).dir === 'up' ? '↑' : getDayBpTrend(day.date).dir === 'down' ? '↓' : '→' }}
                                </text>
                                <text class="metric-trend-text">收缩压 {{ getDayBpTrend(day.date).deltaText }}</text>
                            </view>
                        </view>
                        <view v-if="day.blood.other_blood_sugar" class="metric-cell">
                            <view class="metric-row-head">
                                <text class="metric-label">其他血糖</text>
                                <view
                                    v-if="day.blood.isHighOther"
                                    class="metric-flag up"
                                >
                                    <text class="metric-flag-icon">▲</text>
                                    <text class="metric-flag-text">偏高</text>
                                </view>
                            </view>
                            <view class="metric-value-row">
                                <text class="metric-value" :class="{ 'is-high': day.blood.isHighOther }">
                                    {{ formatValue(day.blood.other_blood_sugar) }}
                                </text>
                                <text class="metric-unit">mmol/L</text>
                            </view>
                            <view
                                v-if="getDayTrend(day.date, 'other_blood_sugar')"
                                class="metric-trend"
                                :class="`trend-${getDayTrend(day.date, 'other_blood_sugar').dir}`"
                            >
                                <text class="metric-trend-arrow">
                                    {{ getDayTrend(day.date, 'other_blood_sugar').dir === 'up' ? '↑' : getDayTrend(day.date, 'other_blood_sugar').dir === 'down' ? '↓' : '→' }}
                                </text>
                                <text class="metric-trend-text">{{ getDayTrend(day.date, 'other_blood_sugar').deltaText }}</text>
                            </view>
                        </view>
                    </view>
                    <view v-if="day.blood.western_medicine || day.blood.insulin" class="day-meta-row">
                        <text v-if="day.blood.western_medicine" class="meta-chip">💊 {{ day.blood.western_medicine }}</text>
                        <text v-if="day.blood.insulin" class="meta-chip">💉 {{ day.blood.insulin }}</text>
                    </view>
                    <view v-if="day.blood.remark" class="day-remark">
                        <text>{{ day.blood.remark }}</text>
                    </view>
                </view>

                <!-- 饮食 -->
                <view v-if="day.diet" class="day-block">
                    <view class="day-block-head">
                        <view class="day-block-tag tag-diet">饮食</view>
                    </view>
                    <view class="diet-rows">
                        <view v-if="day.diet.breakfast_foods" class="diet-row">
                            <text class="diet-meal">🌞 早餐</text>
                            <text class="diet-text">{{ day.diet.breakfast_foods }}</text>
                        </view>
                        <view v-if="day.diet.lunch_foods" class="diet-row">
                            <text class="diet-meal">🍱 午餐</text>
                            <text class="diet-text">{{ day.diet.lunch_foods }}</text>
                        </view>
                        <view v-if="day.diet.dinner_foods" class="diet-row">
                            <text class="diet-meal">🌙 晚餐</text>
                            <text class="diet-text">{{ day.diet.dinner_foods }}</text>
                        </view>
                    </view>
                    <view v-if="day.diet.note" class="day-remark">
                        <text>{{ day.diet.note }}</text>
                    </view>
                </view>

                <!-- 运动 -->
                <view v-if="day.exercise" class="day-block">
                    <view class="day-block-head">
                        <view class="day-block-tag tag-exercise">运动</view>
                    </view>
                    <view class="exercise-row">
                        <text class="exercise-type">{{ day.exercise.exercise_type || '运动' }}</text>
                        <text v-if="day.exercise.duration" class="exercise-duration">{{ day.exercise.duration }} 分钟</text>
                        <text v-if="day.exercise.intensityText" class="exercise-intensity">{{ day.exercise.intensityText }}</text>
                    </view>
                    <view v-if="day.exercise.note" class="day-remark">
                        <text>{{ day.exercise.note }}</text>
                    </view>
                </view>

                <!-- 跟踪备注 -->
                <view v-if="day.tracking && day.tracking.content" class="day-block">
                    <view class="day-block-head">
                        <view class="day-block-tag tag-tracking">备注</view>
                    </view>
                    <view class="tracking-content">
                        <text
                            v-for="(line, idx) in splitTrackingLines(day.tracking.content)"
                            :key="idx"
                            class="tracking-line"
                        >
                            {{ line }}
                        </text>
                    </view>
                </view>
            </view>
        </view>

        <view class="footer-tip">
            <text>数据每日同步 · 阈值仅作参考，请遵医嘱</text>
        </view>

        <!-- 录入今日数据弹层 -->
        <view v-if="inputForm.visible" class="input-modal-mask" @click="closeInputForm">
            <view class="input-modal" @click.stop>
                <view class="input-modal-grip" />
                <view class="input-modal-head">
                    <view class="input-modal-title-wrap">
                        <text class="input-modal-title">{{ inputForm.id ? '修改今日数据' : '录入今日数据' }}</text>
                        <text class="input-modal-sub">{{ todayText }} · {{ patientName || '本人' }}</text>
                    </view>
                    <view class="input-modal-close" @click="closeInputForm">
                        <text class="input-modal-close-icon">×</text>
                    </view>
                </view>

                <view v-if="inputForm.loading" class="input-modal-loading">
                    <text>加载今日数据中…</text>
                </view>
                <scroll-view v-else scroll-y class="input-modal-body">
                    <view class="input-modal-tip">
                        <text>请如实填写，留空表示该项不上传。当天可多次修改。</text>
                    </view>

                    <view class="input-section-title">血糖（mmol/L）</view>
                    <view class="input-field">
                        <view class="input-field-label">
                            <view class="input-field-dot input-field-dot-fasting" />
                            <text>空腹血糖</text>
                        </view>
                        <view class="input-field-input">
                            <input
                                type="digit"
                                :value="inputForm.fasting_blood_sugar"
                                placeholder="如 6.5"
                                placeholder-class="input-placeholder"
                                @input="onInputField('fasting_blood_sugar', $event)"
                            />
                        </view>
                    </view>
                    <view class="input-field">
                        <view class="input-field-label">
                            <view class="input-field-dot input-field-dot-postprandial" />
                            <text>餐后血糖</text>
                        </view>
                        <view class="input-field-input">
                            <input
                                type="digit"
                                :value="inputForm.postprandial_blood_sugar"
                                placeholder="如 8.0"
                                placeholder-class="input-placeholder"
                                @input="onInputField('postprandial_blood_sugar', $event)"
                            />
                        </view>
                    </view>
                    <view class="input-field">
                        <view class="input-field-label">
                            <view class="input-field-dot input-field-dot-other" />
                            <text>其他血糖</text>
                        </view>
                        <view class="input-field-input">
                            <input
                                type="digit"
                                :value="inputForm.other_blood_sugar"
                                placeholder="如 7.2"
                                placeholder-class="input-placeholder"
                                @input="onInputField('other_blood_sugar', $event)"
                            />
                        </view>
                    </view>

                    <view class="input-section-title">血压（mmHg）</view>
                    <view class="input-field-row">
                        <view class="input-field input-field-half">
                            <view class="input-field-label">
                                <text>高压</text>
                            </view>
                            <view class="input-field-input">
                                <input
                                    type="number"
                                    :value="inputForm.systolic_pressure"
                                    placeholder="如 120"
                                    placeholder-class="input-placeholder"
                                    @input="onInputField('systolic_pressure', $event)"
                                />
                            </view>
                        </view>
                        <view class="input-field input-field-half">
                            <view class="input-field-label">
                                <text>低压</text>
                            </view>
                            <view class="input-field-input">
                                <input
                                    type="number"
                                    :value="inputForm.diastolic_pressure"
                                    placeholder="如 80"
                                    placeholder-class="input-placeholder"
                                    @input="onInputField('diastolic_pressure', $event)"
                                />
                            </view>
                        </view>
                    </view>

                    <view class="input-section-title">备注</view>
                    <view class="input-field input-field-textarea">
                        <textarea
                            :value="inputForm.remark"
                            placeholder="如：饭前空腹测量、餐后2小时测量等（选填）"
                            placeholder-class="input-placeholder"
                            :show-confirm-bar="false"
                            :auto-height="true"
                            @input="onInputField('remark', $event)"
                        />
                    </view>
                </scroll-view>

                <view class="input-modal-actions">
                    <view
                        v-if="inputForm.id"
                        class="input-modal-btn danger"
                        :class="{ disabled: inputForm.submitting }"
                        @click="deleteInputRecord"
                    >
                        <text>删除</text>
                    </view>
                    <view
                        class="input-modal-btn primary"
                        :class="{ disabled: inputForm.submitting || inputForm.loading }"
                        @click="submitInputForm"
                    >
                        <text>{{ inputForm.submitting ? '提交中…' : (inputForm.id ? '保存修改' : '提交录入') }}</text>
                    </view>
                </view>
            </view>
        </view>
    </view>
</template>

<script setup>
import { ref, computed, getCurrentInstance, nextTick, watch } from 'vue'
import { onLoad, onShow, onHide, onUnload, onPullDownRefresh } from '@dcloudio/uni-app'

const { proxy } = getCurrentInstance()

const diagnosisId = ref(0)
const patientId = ref(0)
const patientName = ref('')
const age = ref(0)
const gender = ref(0)

const loading = ref(false)
const cardsLoading = ref(false)
const cards = ref([])
const rangeMode = ref('7')
const rangeOptions = [
    { value: '7', label: '最近 7 天' },
    { value: '30', label: '最近 30 天' }
]

const diagnosisTypeMap = {
    first_visit: '初诊',
    follow_up: '复诊',
    consultation: '会诊'
}

const syndromeTypeMap = {
    qi_deficiency: '气虚',
    blood_deficiency: '血虚',
    yin_deficiency: '阴虚',
    yang_deficiency: '阳虚',
    qi_stagnation: '气滞',
    blood_stasis: '血瘀',
    phlegm_dampness: '痰湿',
    damp_heat: '湿热',
    cold_dampness: '寒湿',
    wind_cold: '风寒',
    wind_heat: '风热'
}

function getCardSubtitle(card) {
    if (!card) return ''
    const parts = []
    if (card.gender_desc) parts.push(card.gender_desc)
    else if (Number(card.gender) === 1) parts.push('男')
    else if (Number(card.gender) === 2 || Number(card.gender) === 0) parts.push('女')
    if (card.age) parts.push(`${card.age}岁`)
    const dt = diagnosisTypeMap[card.diagnosis_type] || card.diagnosis_type
    if (dt) parts.push(dt)
    const st = syndromeTypeMap[card.syndrome_type] || card.syndrome_type
    if (st) parts.push(st)
    return parts.join(' · ')
}

const bloodRecords = ref([])
const dietRecords = ref([])
const exerciseRecords = ref([])
const trackingNotes = ref([])

const hoverInfo = ref(null)
let hoverTimer = null

// 画布上下文相关
let canvasNode = null
let canvasCtx = null
let canvasWidth = 0
let canvasHeight = 0
let canvasDpr = 1
let chartCoords = null

const ageText = computed(() => (age.value > 0 ? `${age.value} 岁` : ''))
const genderText = computed(() => {
    const map = { 1: '男', 2: '女' }
    return map[Number(gender.value)] || ''
})
const rangeText = 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 new Date()
    const [y, m, d] = str.split('-').map(Number)
    return new Date(y, (m || 1) - 1, d || 1)
}

const currentRange = computed(() => {
    const days = rangeMode.value === '30' ? 30 : 7
    const end = 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 = 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 = computed(() => [...visibleDates.value].reverse())

// ============ 数据聚合 ============
function toNumber(v) {
    if (v === null || v === undefined || 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 null
    if (a < 50) return { fasting: 7, postprandial: 9 }
    return { fasting: 8, postprandial: 10 }
}

function isHighFasting(v) {
    const n = toNumber(v)
    const t = getBloodSugarThresholds(age.value)
    return n !== null && t !== null && n >= t.fasting
}

function isHighPostprandial(v) {
    const n = toNumber(v)
    const t = getBloodSugarThresholds(age.value)
    return n !== null && t !== null && n >= t.postprandial
}

function isHighBp(row) {
    if (!row) return false
    const s = toNumber(row.systolic_pressure)
    const d = toNumber(row.diastolic_pressure)
    return (s !== null && s > 140) || (d !== null && d > 90)
}

function formatBp(row) {
    const s = row?.systolic_pressure
    const d = row?.diastolic_pressure
    if ((s === null || s === undefined || s === '' || s === 0) && (d === null || d === undefined || d === '' || d === 0)) return '—'
    return `${s || '—'} / ${d || '—'}`
}

function formatValue(v) {
    const n = toNumber(v)
    return n === null ? '—' : String(n)
}

const intensityTextMap = { 1: '低强度', 2: '中强度', 3: '高强度' }

const bloodByDate = 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] === undefined) && r[f] !== '' && r[f] !== null && r[f] !== undefined) {
                merged[f] = r[f]
            }
        }
    }
    for (const date in map) {
        const m = map[date]
        m.isHighFasting = isHighFasting(m.fasting_blood_sugar)
        m.isHighPostprandial = isHighPostprandial(m.postprandial_blood_sugar)
        m.isHighOther = isHighPostprandial(m.other_blood_sugar)
        m.isHighBp = isHighBp(m)
        m.bpText = formatBp(m)
    }
    return map
})

const dietByDate = 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 = 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 = 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 = computed(() => formatDate(new Date()))

const recordDays = computed(() => {
    const result = []
    for (const date of visibleDates.value) {
        const blood = bloodByDate.value[date]
        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 bloodHasData = 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 = 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 = computed(() => {
    return chartDates.value.filter((date) => {
        const b = bloodByDate.value[date]
        return b && (b.isHighFasting || b.isHighPostprandial)
    }).length
})

const bloodNormalDayCount = computed(() => bloodRecordDayCount.value - bloodHighDayCount.value)

// 趋势比较：dir = up/down/flat, delta = 与前一次的绝对差
// 注意：对于血糖/血压，「up = 升高 = 风险」，所以语义颜色按健康角度而非数学方向
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
}

function findPrevBp(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 s = toNumber(b.systolic_pressure)
        const d = toNumber(b.diastolic_pressure)
        if (s !== null || d !== null) return { date: dates[i], systolic: s, diastolic: d }
    }
    return null
}

const latestStat = 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
})

// 给某天某字段计算 day-over-day 趋势（用于日常记录列表的指标小标签）
function getDayTrend(date, field, precision = 1) {
    const b = bloodByDate.value[date]
    if (!b) return null
    const cur = toNumber(b[field])
    if (cur === null) return null
    const prev = findPrev(field, date)
    if (!prev) return null
    return makeTrend(cur, prev.value, precision)
}

function getDayBpTrend(date) {
    const b = bloodByDate.value[date]
    if (!b) return null
    const cur = toNumber(b.systolic_pressure)
    if (cur === null) return null
    const prev = findPrevBp(date)
    if (!prev || prev.systolic == null) return null
    return makeTrend(cur, prev.systolic, 0)
}

// ============ 就诊卡 ============
async function fetchCardList() {
    const token = uni.getStorageSync('token')
    if (!token) 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
    }
}

function applyCard(card) {
    if (!card) return
    diagnosisId.value = Number(card.id) || 0
    patientId.value = Number(card.patient_id) || 0
    patientName.value = card.patient_name || ''
    age.value = Number(card.age) || 0
    gender.value = Number(card.gender) || 0
}

function onSelectCard(card) {
    if (!card || Number(card.id) === Number(diagnosisId.value)) return
    // 切换前清空当前数据，避免列表残留
    bloodRecords.value = []
    dietRecords.value = []
    exerciseRecords.value = []
    trackingNotes.value = []
    hoverInfo.value = null
    applyCard(card)
    fetchAll()
}

// ============ 接口请求 ============
async function fetchAll() {
    if (!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?.msg) uni.showToast({ title: winRes.msg, icon: 'none' })
        }

        if (noteRes && noteRes.code === 1) {
            const list = Array.isArray(noteRes.data) ? noteRes.data : []
            trackingNotes.value = list.filter((it) => visibleDates.value.includes(String(it?.note_date || '')))
        } else {
            trackingNotes.value = []
        }
    } catch (e) {
        uni.showToast({ title: '加载失败，请稍后再试', icon: 'none' })
    } finally {
        loading.value = false
        await nextTick()
        scheduleChartRedraw()
        // 自动播报：数据就绪后朗读一次最新摘要
        if (ttsAutoPlay.value) {
            const token = ++ttsAutoToken
            setTimeout(() => {
                if (token === ttsAutoToken) speakLatestSummary()
            }, 350)
        }
    }
}

function onChangeRange(value) {
    if (rangeMode.value === value) return
    rangeMode.value = value
    fetchAll()
}

function onRefresh() {
    fetchAll()
}

// ============ 时段问候 / 打卡 / 健康日历 ============
const timeGreeting = computed(() => {
    const h = new Date().getHours()
    if (h < 5) return '夜深了'
    if (h < 11) return '早上好'
    if (h < 13) return '中午好'
    if (h < 18) return '下午好'
    if (h < 22) return '晚上好'
    return '夜深了'
})

const timeGreetingEmoji = computed(() => {
    const h = new Date().getHours()
    if (h < 5) return '🌙'
    if (h < 11) return '☀️'
    if (h < 13) return '🌤'
    if (h < 18) return '🌅'
    if (h < 22) return '🌆'
    return '🌙'
})

// 复用已有的 formatDate(date) -> 'YYYY-MM-DD'

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
}

// 给一天打个完整度分（0-3）：0 无 / 1 仅 1 项 / 2 有 2~3 项 / 3 有 4 项以上
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
}

// todayStr 已在上方定义，这里直接复用
const todayChecked = computed(() => dayHasBloodRecord(todayStr.value))

// 连续打卡天数：从今天往前回溯，连续有 blood 记录的天数；今天没打卡时从昨天起算（保留 streak 心态）
const streakDays = computed(() => {
    const today = 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) {
                // 今天还没测：从昨天开始算 streak（不打断）
                startedFromYesterday = true
                continue
            }
            break
        }
    }
    return { days: count, startedFromYesterday }
})

// 近 7 天打卡情况：从 6 天前到今天
const last7Dots = computed(() => {
    const arr = []
    const today = 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 = computed(() => last7Dots.value.filter((d) => d.hasRecord).length)

// 健康日历热力图：取当前数据范围内所有日期，按周组装成 6x7（最多）网格
const heatmapWeeks = 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])
    // JS getDay: 0=Sun..6=Sat；我们以周一为起点
    const firstDow = (first.getDay() + 6) % 7 // 0=Mon..6=Sun
    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) / 86400000) + 1
    const weeks = []
    let cur = new Date(gridStart)
    for (let w = 0; w < Math.ceil(totalDays / 7); w++) {
        const week = []
        for (let i = 0; i < 7; i++) {
            const key = formatDate(cur)
            const inRange = sorted.includes(key)
            week.push({
                date: key,
                day: cur.getDate(),
                inRange,
                isToday: key === todayStr.value,
                level: inRange ? dayCoverageLevel(key) : -1
            })
            cur.setDate(cur.getDate() + 1)
        }
        weeks.push(week)
    }
    return weeks
})

const heatmapTotalRecords = 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 = computed(() => {
    let c = 0
    for (const w of heatmapWeeks.value) for (const c2 of w) if (c2.inRange) c++
    return c
})

// 月历的"月份标签"：取热力图覆盖范围里跨度最大的月份
const heatmapMonthLabel = computed(() => {
    const weeks = heatmapWeeks.value
    if (!weeks.length) return ''
    const counter = new Map()
    for (const w of weeks) {
        for (const c of w) {
            if (!c.inRange) continue
            const [y, m] = c.date.split('-')
            const key = `${y}-${m}`
            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) {
    // 滚动到下方对应日期 records 列表项；若不在 visible 列表则提示
    if (!recordDays.value.some((d) => d.date === date)) {
        uni.showToast({ title: '该日无记录', icon: 'none' })
        return
    }
    uni.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
            uni.pageScrollTo({ scrollTop: top, duration: 280 })
        })
}

// ============ 录入今日数据 ============
const todayText = computed(() => {
    const d = 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: '',
    systolic_pressure: '',
    diastolic_pressure: '',
    remark: ''
})
const inputForm = ref(emptyInputForm())

function onInputField(field, e) {
    const val = e?.detail?.value ?? ''
    inputForm.value[field] = val
}

async function openInputForm() {
    if (!diagnosisId.value) {
        uni.showToast({ title: '请先选择就诊卡', icon: 'none' })
        return
    }
    const token = uni.getStorageSync('token')
    if (!token) {
        uni.showToast({ title: '请先登录后再录入', icon: 'none' })
        return
    }
    // 微信小程序 canvas 是原生组件、z-index 无法被弹层覆盖，
    // 这里通过 v-if 卸载它；下次显示时 selectorQuery 拿到的会是新节点，
    // 因此先清空缓存的 ctx / node，避免下次 ensureCanvas 复用旧引用。
    canvasCtx = null
    canvasNode = null
    // 打开弹层，先拉取今日已有记录预填
    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.systolic_pressure = numToStr(r.systolic_pressure)
            inputForm.value.diastolic_pressure = numToStr(r.diastolic_pressure)
            inputForm.value.remark = String(r.remark || '')
        }
    } catch (e) {
        // 静默：拉取失败时仍允许新增录入
    } finally {
        inputForm.value.loading = false
    }
}

function numToStr(v) {
    if (v === null || v === undefined || v === '' || v === 0 || v === '0') return ''
    return String(v)
}

function closeInputForm() {
    if (inputForm.value.submitting) return
    inputForm.value.visible = false
    // 关闭后 canvas 会重新挂载，需触发一次重绘
    nextTick(() => scheduleChartRedraw())
}

async function submitInputForm() {
    if (inputForm.value.submitting || inputForm.value.loading) return

    // 至少填一个有效字段
    const f = inputForm.value
    const hasAny = [
        f.fasting_blood_sugar, f.postprandial_blood_sugar, f.other_blood_sugar,
        f.systolic_pressure, f.diastolic_pressure
    ].some((v) => v !== '' && v !== null && Number(v) > 0) || !!(f.remark && f.remark.trim())
    if (!hasAny) {
        uni.showToast({ title: '请至少填写一项数据', icon: 'none' })
        return
    }

    inputForm.value.submitting = true
    try {
        const res = await proxy.apiUrl({
            url: '/api/tcm/dailySaveBloodRecord',
            method: 'POST',
            data: {
                diagnosis_id: diagnosisId.value,
                fasting_blood_sugar: f.fasting_blood_sugar,
                postprandial_blood_sugar: f.postprandial_blood_sugar,
                other_blood_sugar: f.other_blood_sugar,
                systolic_pressure: f.systolic_pressure,
                diastolic_pressure: f.diastolic_pressure,
                remark: f.remark
            }
        }, false)
        if (res && res.code === 1) {
            uni.showToast({ title: res.msg || '已保存', icon: 'success' })
            inputForm.value.visible = false
            fetchAll()
        } else {
            uni.showToast({ title: (res && res.msg) || '保存失败', icon: 'none' })
        }
    } catch (e) {
        uni.showToast({ title: '网络异常，请稍后再试', icon: 'none' })
    } finally {
        inputForm.value.submitting = false
    }
}

function deleteInputRecord() {
    if (!inputForm.value.id || inputForm.value.submitting) return
    uni.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) {
                    uni.showToast({ title: '已删除', icon: 'success' })
                    inputForm.value.visible = false
                    fetchAll()
                } else {
                    uni.showToast({ title: (res && res.msg) || '删除失败', icon: 'none' })
                }
            } catch (e) {
                uni.showToast({ title: '网络异常，请稍后再试', icon: 'none' })
            } finally {
                inputForm.value.submitting = false
            }
        }
    })
}

function splitTrackingLines(content) {
    return String(content || '')
        .split('\n')
        .map((s) => s.trim())
        .filter(Boolean)
}

// ============ 语音播报（适老化）============
// 优先用 微信同声传译插件 (mp-weixin)；H5 走 window.speechSynthesis；
// 都不可用则降级为 modal 弹窗显示文本，保证可读性。
const ttsEnabled = ref(true)
const ttsAutoPlay = ref(false)
const ttsSpeaking = ref(false)
let ttsAudioCtx = null
let ttsAutoToken = 0

function teardownTtsAudio() {
    if (ttsAudioCtx) {
        try { ttsAudioCtx.stop() } catch (e) {}
        try { ttsAudioCtx.destroy && ttsAudioCtx.destroy() } catch (e) {}
        ttsAudioCtx = null
    }
}

function ttsStop() {
    teardownTtsAudio()
    // #ifdef H5
    try {
        if (typeof window !== 'undefined' && window.speechSynthesis) {
            window.speechSynthesis.cancel()
        }
    } catch (e) {}
    // #endif
    ttsSpeaking.value = false
}

function ttsSpeak(text) {
    if (!ttsEnabled.value) return
    const content = String(text || '').replace(/\s+/g, ' ').trim()
    if (!content) return

    ttsStop()

    // H5：浏览器 SpeechSynthesis（慢速适老化）
    // #ifdef H5
    try {
        if (typeof window !== 'undefined' && window.speechSynthesis) {
            const u = new window.SpeechSynthesisUtterance(content)
            u.lang = 'zh-CN'
            u.rate = 0.85
            u.pitch = 1
            u.volume = 1
            u.onstart = () => { ttsSpeaking.value = true }
            u.onend = () => { ttsSpeaking.value = false }
            u.onerror = () => { ttsSpeaking.value = false }
            window.speechSynthesis.speak(u)
            return
        }
    } catch (e) {}
    // #endif

    // 微信小程序：同声传译插件 TTS → mp3 → innerAudioContext 播放
    // #ifdef MP-WEIXIN
    try {
        // eslint-disable-next-line no-undef
        const plugin = requirePlugin('WechatSI')
        ttsSpeaking.value = true
        plugin.textToSpeech({
            lang: 'zh_CN',
            tts: true,
            content,
            success: (res) => {
                if (!res || !res.filename) {
                    ttsSpeaking.value = false
                    return
                }
                teardownTtsAudio()
                ttsAudioCtx = uni.createInnerAudioContext()
                ttsAudioCtx.src = res.filename
                ttsAudioCtx.obeyMuteSwitch = false
                ttsAudioCtx.onPlay(() => { ttsSpeaking.value = true })
                ttsAudioCtx.onEnded(() => { ttsSpeaking.value = false })
                ttsAudioCtx.onError(() => { ttsSpeaking.value = false })
                ttsAudioCtx.onStop(() => { ttsSpeaking.value = false })
                ttsAudioCtx.play()
            },
            fail: (err) => {
                ttsSpeaking.value = false
                console.warn('WechatSI textToSpeech failed', err)
                uni.showToast({ title: '语音不可用，请检查网络/插件', icon: 'none', duration: 2000 })
            }
        })
        return
    } catch (e) {
        console.warn('WechatSI plugin not available', e)
    }
    // #endif

    // 其它端兜底：用 modal 显示要播报的内容
    uni.showModal({
        title: '语音内容',
        content,
        showCancel: false,
        confirmText: '我知道了'
    })
}

function ttsToggleEnabled() {
    ttsEnabled.value = !ttsEnabled.value
    if (!ttsEnabled.value) {
        ttsAutoPlay.value = false
        ttsStop()
    }
}

function ttsToggleAutoPlay() {
    if (!ttsEnabled.value) {
        ttsEnabled.value = true
    }
    ttsAutoPlay.value = !ttsAutoPlay.value
    if (ttsAutoPlay.value) {
        const text = buildLatestSummaryText()
        if (text) ttsSpeak(text)
    } else {
        ttsStop()
    }
}

// ============ 播报文案构造 ============
function readableNumber(v) {
    if (v === null || v === undefined || 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 buildDayText(day) {
    if (!day) return ''
    const dateLabel = `${day.dateLabel.replace('-', '月')}日${day.weekday ? ' ' + day.weekday : ''}`
    const lines = [`${dateLabel} 的健康记录：`]
    if (day.blood) {
        const b = day.blood
        if (toNumber(b.fasting_blood_sugar) !== null) {
            lines.push(`空腹血糖 ${readableNumber(b.fasting_blood_sugar)} 毫摩每升${b.isHighFasting ? '，偏高。' : '。'}`)
        }
        if (toNumber(b.postprandial_blood_sugar) !== null) {
            lines.push(`餐后血糖 ${readableNumber(b.postprandial_blood_sugar)} 毫摩每升${b.isHighPostprandial ? '，偏高。' : '。'}`)
        }
        if (toNumber(b.other_blood_sugar) !== null) {
            lines.push(`其他血糖 ${readableNumber(b.other_blood_sugar)} 毫摩每升${b.isHighOther ? '，偏高。' : '。'}`)
        }
        const sys = toNumber(b.systolic_pressure)
        const dia = toNumber(b.diastolic_pressure)
        if (sys !== null || dia !== null) {
            let line = '血压'
            if (sys !== null) line += ` 高压 ${readableNumber(sys)}`
            if (dia !== null) line += `，低压 ${readableNumber(dia)}`
            line += ' 毫米汞柱'
            line += b.isHighBp ? '，偏高。' : '。'
            lines.push(line)
        }
        if (b.western_medicine) lines.push(`西药：${b.western_medicine}。`)
        if (b.insulin) lines.push(`胰岛素：${b.insulin}。`)
    }
    if (day.diet) {
        const d = day.diet
        if (d.breakfast_foods) lines.push(`早餐：${d.breakfast_foods}。`)
        if (d.lunch_foods) lines.push(`午餐：${d.lunch_foods}。`)
        if (d.dinner_foods) lines.push(`晚餐：${d.dinner_foods}。`)
    }
    if (day.exercise) {
        const ex = day.exercise
        const parts = []
        if (ex.exercise_type) parts.push(ex.exercise_type)
        if (ex.duration) parts.push(`${ex.duration} 分钟`)
        if (ex.intensityText) parts.push(ex.intensityText)
        if (parts.length) lines.push(`运动：${parts.join('，')}。`)
    }
    if (day.tracking && day.tracking.content) {
        lines.push(`备注：${String(day.tracking.content).replace(/\n+/g, '，')}。`)
    }
    return lines.join(' ')
}

function speakLatestSummary() {
    const text = buildLatestSummaryText()
    if (text) ttsSpeak(text)
}

function speakDay(day) {
    const text = buildDayText(day)
    if (text) ttsSpeak(text)
}

// ============ 波浪图绘制 ============
function ensureCanvas() {
    return new Promise((resolve) => {
        if (canvasCtx) {
            resolve()
            return
        }
        const query = uni.createSelectorQuery().in(proxy)
        query.select('#bloodWaveChart')
            .fields({ node: true, size: true })
            .exec((res) => {
                const item = res && res[0]
                if (!item || !item.node) {
                    resolve()
                    return
                }
                canvasNode = item.node
                canvasCtx = canvasNode.getContext('2d')
                try {
                    canvasDpr = uni.getSystemInfoSync().pixelRatio || 1
                } catch (e) {
                    canvasDpr = 1
                }
                canvasWidth = item.width
                canvasHeight = item.height
                canvasNode.width = canvasWidth * canvasDpr
                canvasNode.height = canvasHeight * canvasDpr
                canvasCtx.scale(canvasDpr, canvasDpr)
                resolve()
            })
    })
}

let redrawScheduled = false
function scheduleChartRedraw() {
    if (redrawScheduled) return
    redrawScheduled = true
    setTimeout(async () => {
        redrawScheduled = false
        await ensureCanvas()
        drawChart()
    }, 60)
}

function buildSeries(field) {
    return chartDates.value.map((date) => {
        const b = bloodByDate.value[date]
        return b ? toNumber(b[field]) : null
    })
}

function computeYRange(series1, series2) {
    const values = [...series1, ...series2].filter((v) => v !== null)
    if (!values.length) return { yMin: 4, yMax: 12 }
    let min = Math.min(...values)
    let max = Math.max(...values)
    // 给上下留一点空间
    const span = max - min
    if (span < 2) {
        min = Math.max(0, min - 1)
        max = max + 1
    } else {
        min = Math.max(0, min - span * 0.2)
        max = max + span * 0.2
    }
    // 不低于4，不高于25
    min = Math.max(2, Math.floor(min))
    max = Math.min(28, Math.ceil(max))
    if (max - min < 4) max = min + 4
    return { yMin: min, yMax: max }
}

function drawChart() {
    if (!canvasCtx || !canvasWidth || !canvasHeight) return
    const ctx = canvasCtx
    const w = canvasWidth
    const h = canvasHeight

    ctx.clearRect(0, 0, w, h)

    const padding = { left: 40, right: 18, top: 26, bottom: 36 }
    const cw = w - padding.left - padding.right
    const ch = h - padding.top - padding.bottom

    const fastingSeries = buildSeries('fasting_blood_sugar')
    const postSeries = buildSeries('postprandial_blood_sugar')
    const { yMin, yMax } = computeYRange(fastingSeries, postSeries)
    const n = chartDates.value.length
    const threshold = getBloodSugarThresholds(age.value)
    const valueToY = (v) => padding.top + ch * (1 - (v - yMin) / (yMax - yMin))

    // 1) 背景：纯净浅色 + 阈值以上一抹极淡红
    ctx.fillStyle = '#fbfcfd'
    ctx.fillRect(padding.left, padding.top, cw, ch)
    if (threshold) {
        const yThr = valueToY(threshold.fasting)
        const top = Math.max(padding.top, Math.min(yThr, padding.top + ch))
        if (top > padding.top) {
            // 渐变让红色从顶部往阈值线方向过渡，更柔和
            const dangerGrad = ctx.createLinearGradient(0, padding.top, 0, top)
            dangerGrad.addColorStop(0, 'rgba(254, 226, 226, 0.55)')
            dangerGrad.addColorStop(1, 'rgba(254, 226, 226, 0.15)')
            ctx.fillStyle = dangerGrad
            ctx.fillRect(padding.left, padding.top, cw, top - padding.top)
        }
    }

    // 2) 网格 —— 极淡，不喧宾夺主
    ctx.strokeStyle = '#eef2f5'
    ctx.lineWidth = 1
    const yTicks = 4
    for (let i = 0; i <= yTicks; i++) {
        const y = padding.top + (ch * i) / yTicks
        ctx.beginPath()
        ctx.moveTo(padding.left, y)
        ctx.lineTo(padding.left + cw, y)
        ctx.stroke()
    }

    // Y 轴刻度（适老化：加大 + 加深）
    ctx.font = 'bold 12px sans-serif'
    ctx.fillStyle = '#64748b'
    ctx.textAlign = 'right'
    ctx.textBaseline = 'middle'
    for (let i = 0; i <= yTicks; i++) {
        const y = padding.top + (ch * i) / yTicks
        const v = yMax - ((yMax - yMin) * i) / yTicks
        ctx.fillText(v.toFixed(1), padding.left - 8, y)
    }

    // 3) X 轴日期 —— 最多 5 个（适老化：加大、加深）
    ctx.font = 'bold 12px sans-serif'
    ctx.fillStyle = '#475569'
    ctx.textAlign = 'center'
    ctx.textBaseline = 'top'
    if (n > 0) {
        const maxLabels = n > 20 ? 5 : 6
        const labelStep = Math.max(1, Math.ceil(n / maxLabels))
        chartDates.value.forEach((date, i) => {
            if (i !== 0 && i !== n - 1 && i % labelStep !== 0) return
            const x = n === 1 ? padding.left + cw / 2 : padding.left + (cw * i) / (n - 1)
            ctx.fillText(date.slice(5), x, padding.top + ch + 8)
        })
    }

    // 4) 阈值参考线
    if (threshold) {
        const y = valueToY(threshold.fasting)
        if (y >= padding.top && y <= padding.top + ch) {
            ctx.strokeStyle = '#fda4af'
            ctx.lineWidth = 1
            try { ctx.setLineDash([6, 5]) } catch (e) {}
            ctx.beginPath()
            ctx.moveTo(padding.left, y)
            ctx.lineTo(padding.left + cw, y)
            ctx.stroke()
            try { ctx.setLineDash([]) } catch (e) {}
            ctx.font = 'bold 11px sans-serif'
            ctx.fillStyle = '#dc2626'
            ctx.textAlign = 'left'
            ctx.textBaseline = 'middle'
            ctx.fillText(`空腹阈 ${threshold.fasting}`, padding.left + 6, y - 9)
        }
    }

    // 5) 绘制曲线
    const seriesMeta = [
        {
            data: fastingSeries,
            color: '#0ea5a4',
            fillTop: 'rgba(14,165,164,0.20)',
            fillBottom: 'rgba(14,165,164,0)',
            label: '空腹',
            highThreshold: threshold ? threshold.fasting : null
        },
        {
            data: postSeries,
            color: '#f59e0b',
            fillTop: 'rgba(245,158,11,0.16)',
            fillBottom: 'rgba(245,158,11,0)',
            label: '餐后',
            highThreshold: threshold ? threshold.postprandial : null
        }
    ]

    const xAt = (i) => (n === 1 ? padding.left + cw / 2 : padding.left + (cw * i) / (n - 1))
    const coordCache = []
    // 7 天内显示圆点，30 天以纯曲线为主
    const showDots = n <= 10

    seriesMeta.forEach((meta) => {
        const projected = meta.data.map((v, i) => ({
            x: xAt(i),
            y: v === null ? null : valueToY(v),
            value: v
        }))
        coordCache.push(projected)

        const segments = []
        let cur = []
        projected.forEach((p) => {
            if (p.y !== null) cur.push(p)
            else if (cur.length) { segments.push(cur); cur = [] }
        })
        if (cur.length) segments.push(cur)

        segments.forEach((seg) => {
            if (seg.length === 1) {
                ctx.fillStyle = meta.color
                ctx.beginPath()
                ctx.arc(seg[0].x, seg[0].y, 4, 0, Math.PI * 2)
                ctx.fill()
                return
            }

            const drawPath = () => {
                ctx.beginPath()
                ctx.moveTo(seg[0].x, seg[0].y)
                for (let i = 0; i < seg.length - 1; i++) {
                    const p0 = seg[i - 1] || seg[i]
                    const p1 = seg[i]
                    const p2 = seg[i + 1]
                    const p3 = seg[i + 2] || seg[i + 1]
                    const cp1x = p1.x + (p2.x - p0.x) / 6
                    const cp1y = p1.y + (p2.y - p0.y) / 6
                    const cp2x = p2.x - (p3.x - p1.x) / 6
                    const cp2y = p2.y - (p3.y - p1.y) / 6
                    ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y)
                }
            }

            // 渐变填充
            drawPath()
            ctx.lineTo(seg[seg.length - 1].x, padding.top + ch)
            ctx.lineTo(seg[0].x, padding.top + ch)
            ctx.closePath()
            const grad = ctx.createLinearGradient(0, padding.top, 0, padding.top + ch)
            grad.addColorStop(0, meta.fillTop)
            grad.addColorStop(1, meta.fillBottom)
            ctx.fillStyle = grad
            ctx.fill()

            // 描线
            drawPath()
            ctx.strokeStyle = meta.color
            ctx.lineWidth = 2.4
            ctx.lineCap = 'round'
            ctx.lineJoin = 'round'
            ctx.stroke()

            // 圆点（短窗口期）
            if (showDots) {
                seg.forEach((p) => {
                    ctx.beginPath()
                    ctx.arc(p.x, p.y, 3, 0, Math.PI * 2)
                    ctx.fillStyle = '#ffffff'
                    ctx.fill()
                    ctx.strokeStyle = meta.color
                    ctx.lineWidth = 1.8
                    ctx.stroke()
                })
            }
        })

        // 标注「最高点」—— 仅一个，简洁
        let maxIdx = -1
        let maxVal = -Infinity
        projected.forEach((p, i) => {
            if (p.value != null && p.value > maxVal) {
                maxVal = p.value
                maxIdx = i
            }
        })
        if (maxIdx >= 0) {
            const p = projected[maxIdx]
            const isHigh = meta.highThreshold != null && p.value >= meta.highThreshold

            // 强调点
            ctx.beginPath()
            ctx.arc(p.x, p.y, 5, 0, Math.PI * 2)
            ctx.fillStyle = isHigh ? '#dc2626' : meta.color
            ctx.fill()
            ctx.strokeStyle = '#ffffff'
            ctx.lineWidth = 2.5
            ctx.stroke()

            // 标签（适老化：加大峰值标签）
            const labelText = String(p.value)
            ctx.font = 'bold 12px sans-serif'
            const textWidth = ctx.measureText(labelText).width
            const padX = 8
            const padY = 5
            const boxWidth = textWidth + padX * 2
            const boxHeight = 14 + padY * 2
            let labelX = p.x
            let labelAbove = true
            if (p.y - 12 - boxHeight - 4 < padding.top) labelAbove = false
            let labelY = labelAbove ? p.y - 12 - boxHeight / 2 : p.y + 12 + boxHeight / 2
            // 边界保护
            if (labelX - boxWidth / 2 < padding.left + 2) labelX = padding.left + boxWidth / 2 + 2
            if (labelX + boxWidth / 2 > padding.left + cw - 2) labelX = padding.left + cw - boxWidth / 2 - 2
            // 标签底色
            ctx.fillStyle = isHigh ? '#dc2626' : meta.color
            ctx.fillRect(labelX - boxWidth / 2, labelY - boxHeight / 2, boxWidth, boxHeight)
            // 标签文字
            ctx.fillStyle = '#ffffff'
            ctx.textAlign = 'center'
            ctx.textBaseline = 'middle'
            ctx.fillText(labelText, labelX, labelY)
        }
    })

    chartCoords = {
        padding,
        cw,
        ch,
        n,
        fasting: coordCache[0] || [],
        postprandial: coordCache[1] || []
    }
}

function onChartTouch(e) {
    if (!chartCoords || !canvasWidth) return
    if (!chartDates.value.length) return
    let touchX = 0
    let touchY = 0
    if (e && e.touches && e.touches[0]) {
        touchX = e.touches[0].x ?? e.touches[0].clientX ?? 0
        touchY = e.touches[0].y ?? e.touches[0].clientY ?? 0
    }
    const { padding, cw, n } = chartCoords
    if (touchX < padding.left || touchX > padding.left + cw) {
        return
    }
    const ratio = n <= 1 ? 0 : (touchX - padding.left) / cw
    let idx = Math.round(ratio * (n - 1))
    if (idx < 0) idx = 0
    if (idx > n - 1) idx = n - 1
    const date = chartDates.value[idx]
    const fp = chartCoords.fasting[idx]
    const pp = chartCoords.postprandial[idx]
    const anchorX = fp?.x ?? pp?.x ?? padding.left + cw * (idx / Math.max(1, n - 1))

    const left = Math.max(8, Math.min(canvasWidth - 130, anchorX - 60))
    const top = Math.max(4, touchY - 70)

    hoverInfo.value = {
        date: date.slice(5),
        fasting: fp?.value ?? null,
        postprandial: pp?.value ?? null,
        left,
        top
    }
    if (hoverTimer) clearTimeout(hoverTimer)
    hoverTimer = setTimeout(() => {
        hoverInfo.value = null
    }, 2500)
}

watch(rangeMode, () => {
    scheduleChartRedraw()
})

// ============ 生命周期 ============
function readUserContext() {
    try {
        const userData = uni.getStorageSync('userData')
        if (userData && userData.diagnosis) {
            if (!patientId.value) patientId.value = Number(userData.diagnosis.patient_id) || 0
            if (!diagnosisId.value) diagnosisId.value = Number(userData.diagnosis.id) || 0
            if (!patientName.value) patientName.value = userData.diagnosis.patient_name || ''
            if (!age.value) age.value = Number(userData.diagnosis.age) || 0
            if (!gender.value) gender.value = Number(userData.diagnosis.gender) || 0
        }
    } catch (e) {}
}

async function bootstrap(options) {
    options = options || {}
    // 1. 仅采纳来自页面参数的 diagnosis_id（例如 Card.vue 点「日常记录」时带的）
    //    storage 里的 userData.diagnosis.id 不再作为优先来源，避免覆盖默认逻辑
    const passedDiagnosisId = Number(options.diagnosis_id || options.id || 0)
    diagnosisId.value = passedDiagnosisId
    patientId.value = Number(options.patient_id || 0)
    if (options.patient_name) patientName.value = decodeURIComponent(options.patient_name)
    if (options.age) age.value = Number(options.age) || 0
    if (options.gender) gender.value = Number(options.gender) || 0

    // 2. 永远拉一次就诊卡列表 —— 作为切换器数据源 + 默认选第一张的来源
    const list = await fetchCardList()

    if (Array.isArray(list) && list.length > 0) {
        // 3. 默认选第一张；只有当 URL 明确传了 diagnosis_id 且能在列表中匹配时才用指定卡
        let target = null
        if (passedDiagnosisId) {
            target = list.find((c) => Number(c.id) === Number(passedDiagnosisId))
        }
        if (!target) target = list[0]
        applyCard(target)
    } else {
        // 4. 兜底：列表拉不到时再回退到 storage（极少见）
        if (!diagnosisId.value || !patientId.value) {
            readUserContext()
        }
        if (!diagnosisId.value) {
            uni.showToast({ title: '暂无就诊卡，请先创建', icon: 'none' })
            return
        }
    }

    fetchAll()
}

onLoad((options) => {
    bootstrap(options)
})

onShow(() => {
    scheduleChartRedraw()
})

onPullDownRefresh(async () => {
    await fetchAll()
    uni.stopPullDownRefresh()
})

onHide(() => {
    ttsStop()
})

onUnload(() => {
    ttsStop()
})
</script>

<style lang="scss" scoped>
.daily-page {
    min-height: 100vh;
    background: #f1f5f9;
    padding-bottom: 64rpx;
}

.hero {
    position: relative;
    padding: 40rpx 32rpx 100rpx; /* 适老化：增加 hero 上下空间 */
    color: #ffffff;
    overflow: hidden;
}
.hero-bg {
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, #0ea5a4 0%, #0891b2 60%, #1e40af 120%);
    z-index: 0;
}
.hero-content {
    position: relative;
    z-index: 1;
}

.hero-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12rpx;
}

.hero-main {
    flex: 1;
    min-width: 0;
    padding-top: 4rpx;
}

.hero-title {
    font-size: 56rpx; /* 适老化：放大 */
    font-weight: 800;
    color: #ffffff;
    line-height: 1.2;
    letter-spacing: 1rpx;
}
.hero-subtitle {
    display: block;
    font-size: 30rpx; /* 适老化：放大 */
    color: rgba(255, 255, 255, 0.92); /* 提升对比度 */
    margin-top: 6rpx;
    font-weight: 500;
}

.hero-pill-group {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 12rpx;
    max-width: 380rpx;
}

.hero-pill {
    display: flex;
    align-items: center;
    gap: 10rpx;
    background: rgba(255, 255, 255, 0.22);
    border-radius: 999rpx;
    padding: 18rpx 30rpx;
    backdrop-filter: blur(8rpx);
    min-height: 80rpx; /* 适老化：>= 44pt 触控目标 */
    box-sizing: border-box;
    &:active {
        background: rgba(255, 255, 255, 0.32);
    }
    &.voice {
        background: rgba(255, 255, 255, 0.32);
        border: 2rpx solid rgba(255, 255, 255, 0.55);
        padding: 16rpx 30rpx;
    }
    &.voice.speaking {
        background: #fbbf24;
        border-color: #fde68a;
        animation: voice-pulse 1.4s ease-in-out infinite;
    }
    &.voice.disabled {
        opacity: 0.55;
    }
    &.input {
        background: #ffffff;
        border: 2rpx solid #ffffff;
        padding: 16rpx 32rpx;
        box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.18);
        .hero-pill-icon {
            color: #0ea5a4;
            font-weight: 800;
            font-size: 38rpx;
        }
        .hero-pill-text {
            color: #0ea5a4;
            font-weight: 700;
        }
        &:active {
            background: #f0fdfa;
        }
    }
}

@keyframes voice-pulse {
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.55);
    }
    50% {
        box-shadow: 0 0 0 12rpx rgba(251, 191, 36, 0);
    }
}

.hero-pill-icon {
    font-size: 34rpx;
    color: #ffffff;
    line-height: 1;
}
.hero-pill-text {
    font-size: 32rpx; /* 适老化：放大 */
    color: #ffffff;
    font-weight: 600;
}

/* === 三大核心指标卡 === */
.stat-grid {
    display: flex;
    gap: 14rpx;
    margin: -60rpx 24rpx 0;
    position: relative;
    z-index: 3;
}

.stat-card {
    flex: 1;
    min-width: 0;
    background: #ffffff;
    border-radius: 24rpx;
    padding: 26rpx 18rpx 22rpx;
    box-shadow: 0 14rpx 36rpx rgba(15, 23, 42, 0.12);
    display: flex;
    flex-direction: column;
    gap: 12rpx;
    position: relative;
    overflow: hidden;
    border-top: 6rpx solid transparent;
}

.stat-card::before {
    content: '';
    position: absolute;
    top: 0;
    right: -30rpx;
    width: 120rpx;
    height: 120rpx;
    border-radius: 999rpx;
    opacity: 0.08;
    pointer-events: none;
}

.stat-fasting {
    border-top-color: #0ea5a4;
}
.stat-fasting::before {
    background: #0ea5a4;
}

.stat-postprandial {
    border-top-color: #f59e0b;
}
.stat-postprandial::before {
    background: #f59e0b;
}

.stat-other {
    border-top-color: #8b5cf6;
}
.stat-other::before {
    background: #8b5cf6;
}

.stat-card.is-high {
    box-shadow: 0 14rpx 36rpx rgba(220, 38, 38, 0.22);
}

.stat-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8rpx;
}

.stat-card-tag {
    display: flex;
    align-items: center;
    gap: 6rpx;
}

.stat-card-tag-dot {
    width: 16rpx;
    height: 16rpx;
    border-radius: 999rpx;
}

.stat-fasting .stat-card-tag-dot {
    background: #0ea5a4;
}
.stat-postprandial .stat-card-tag-dot {
    background: #f59e0b;
}
.stat-other .stat-card-tag-dot {
    background: #8b5cf6;
}

.stat-card-tag-text {
    font-size: 26rpx; /* 适老化：放大 */
    color: #334155; /* 提升对比度 */
    font-weight: 700;
}

.stat-card-flag {
    display: inline-flex;
    align-items: center;
    gap: 4rpx;
    padding: 4rpx 12rpx 4rpx 8rpx;
    border-radius: 999rpx;
    background: linear-gradient(135deg, #ef4444, #dc2626);
    box-shadow: 0 3rpx 10rpx rgba(220, 38, 38, 0.28);
}

.stat-card-flag-icon {
    font-size: 20rpx;
    color: #ffffff;
    font-weight: 700;
    line-height: 1;
}

.stat-card-flag-text {
    font-size: 22rpx; /* 适老化：放大 */
    color: #ffffff;
    font-weight: 700;
}

.stat-card-value-row {
    display: flex;
    align-items: baseline;
    gap: 8rpx;
    flex-wrap: wrap;
}

.stat-card-value {
    font-size: 56rpx; /* 适老化：放大数字主角 */
    font-weight: 800;
    color: #0f172a;
    line-height: 1.05;
    letter-spacing: -1rpx;
    &.is-high {
        color: #dc2626;
    }
}

.stat-card-unit {
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b; /* 提升对比度 */
    font-weight: 600;
}

.stat-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8rpx;
    margin-top: 4rpx;
}

.stat-card-date {
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b; /* 提升对比度 */
    font-weight: 500;
}

.stat-card-trend {
    display: inline-flex;
    align-items: center;
    gap: 4rpx;
    padding: 6rpx 12rpx 6rpx 9rpx;
    border-radius: 999rpx;
    background: #f1f5f9;
    &.trend-up {
        background: #fef3c7;
        .stat-card-trend-arrow,
        .stat-card-trend-delta {
            color: #b45309;
        }
    }
    &.trend-down {
        background: #dcfce7;
        .stat-card-trend-arrow,
        .stat-card-trend-delta {
            color: #15803d;
        }
    }
    &.trend-flat {
        .stat-card-trend-arrow,
        .stat-card-trend-delta {
            color: #475569;
        }
    }
}

.stat-card-trend-arrow {
    font-size: 24rpx; /* 适老化：放大 */
    font-weight: 800;
    line-height: 1;
}

.stat-card-trend-delta {
    font-size: 24rpx;
    font-weight: 600;
}

/* === 就诊卡切换器（精简版）=== */
.card-switcher {
    display: flex;
    align-items: center;
    gap: 16rpx;
    margin: 22rpx 24rpx 0;
    padding: 14rpx 22rpx;
    background: #ffffff;
    border-radius: 999rpx;
    box-shadow: 0 4rpx 14rpx rgba(15, 23, 42, 0.05);
}

.card-switcher-head {
    display: flex;
    align-items: center;
    gap: 8rpx;
    flex-shrink: 0;
    padding-right: 10rpx;
    border-right: 2rpx solid #e2e8f0;
}

.card-switcher-label {
    font-size: 28rpx; /* 适老化：放大 */
    color: #0f172a;
    font-weight: 700;
}

.card-switcher-count {
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b;
    background: #f1f5f9;
    padding: 4rpx 14rpx;
    border-radius: 999rpx;
    font-weight: 600;
}

.card-switcher-scroll {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
}

.card-chip-row {
    display: inline-flex;
    flex-wrap: nowrap;
    gap: 8rpx;
    padding: 2rpx;
}

.card-chip {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 8rpx;
    padding: 16rpx 24rpx; /* 适老化：扩大触控区 */
    border-radius: 999rpx;
    background: #f1f5f9;
    border: 2rpx solid transparent;
    transition: all 0.2s;
    min-height: 68rpx;
    box-sizing: border-box;
    &.active {
        background: linear-gradient(135deg, #0ea5a4, #0891b2);
        border-color: transparent;
        box-shadow: 0 4rpx 12rpx rgba(14, 165, 164, 0.32);
        .card-chip-name,
        .card-chip-meta {
            color: #ffffff;
        }
    }
    &:active {
        transform: scale(0.96);
    }
}

.card-chip-name {
    font-size: 28rpx; /* 适老化：放大 */
    font-weight: 700;
    color: #1e293b;
    line-height: 1.1;
}

.card-chip-meta {
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b;
    line-height: 1.1;
}

.range-bar {
    display: flex;
    gap: 12rpx;
    margin: 22rpx 24rpx 0;
    background: #ffffff;
    border-radius: 999rpx;
    padding: 10rpx;
    box-shadow: 0 4rpx 14rpx rgba(15, 23, 42, 0.05);
    position: relative;
    z-index: 2;
}

.voice-bar {
    margin: 22rpx 24rpx 0;
    background: #ffffff;
    border-radius: 24rpx;
    padding: 26rpx 26rpx;
    box-shadow: 0 6rpx 20rpx rgba(15, 23, 42, 0.04);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18rpx;
    border: 2rpx solid #e0f2fe;
}

.voice-bar-left {
    display: flex;
    align-items: center;
    gap: 16rpx;
    flex: 1;
    min-width: 0;
}

.voice-bar-icon {
    font-size: 48rpx;
    line-height: 1;
}

.voice-bar-text {
    display: flex;
    flex-direction: column;
    gap: 4rpx;
    min-width: 0;
}

.voice-bar-title {
    font-size: 34rpx; /* 适老化：放大 */
    font-weight: 700;
    color: #0f172a;
}

.voice-bar-hint {
    font-size: 26rpx; /* 适老化：放大 */
    color: #475569; /* 提升对比度 */
    margin-top: 4rpx;
}

.voice-bar-actions {
    display: flex;
    align-items: center;
    gap: 16rpx;
    flex-shrink: 0;
}

.voice-switch {
    display: flex;
    align-items: center;
    gap: 12rpx;
    padding: 10rpx 12rpx 10rpx 18rpx;
    border-radius: 999rpx;
    background: #f1f5f9;
    transition: background 0.2s;
    min-height: 76rpx;
    box-sizing: border-box;
    &.on {
        background: linear-gradient(135deg, #fef3c7, #fde68a);
    }
}

.voice-switch-label {
    font-size: 28rpx; /* 适老化：放大 */
    color: #1e293b;
    font-weight: 700;
}

.voice-switch-track {
    width: 76rpx;
    height: 40rpx;
    background: #cbd5e1;
    border-radius: 999rpx;
    position: relative;
    transition: background 0.2s;
    .voice-switch.on & {
        background: #f59e0b;
    }
}

.voice-switch-knob {
    position: absolute;
    width: 32rpx;
    height: 32rpx;
    border-radius: 999rpx;
    background: #ffffff;
    top: 4rpx;
    left: 4rpx;
    box-shadow: 0 2rpx 6rpx rgba(15, 23, 42, 0.2);
    transition: transform 0.2s;
    .voice-switch.on & {
        transform: translateX(36rpx);
    }
}

.voice-toggle {
    width: 76rpx;
    height: 76rpx;
    border-radius: 999rpx;
    background: linear-gradient(135deg, #0ea5a4, #0891b2);
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 6rpx 16rpx rgba(14, 165, 164, 0.28);
    &.off {
        background: #cbd5e1;
        box-shadow: none;
    }
    &:active {
        transform: scale(0.95);
    }
}

.voice-toggle-icon {
    font-size: 36rpx;
    color: #ffffff;
    line-height: 1;
}
.range-item {
    flex: 1;
    text-align: center;
    padding: 22rpx 0; /* 适老化：扩大触控区 */
    border-radius: 999rpx;
    font-size: 32rpx; /* 适老化：放大 */
    color: #334155;
    font-weight: 600;
    min-height: 76rpx;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    &.active {
        background: linear-gradient(135deg, #0ea5a4, #0891b2);
        color: #ffffff;
        font-weight: 700;
        box-shadow: 0 4rpx 12rpx rgba(14, 165, 164, 0.35);
    }
    &:active:not(.active) {
        background: #f1f5f9;
    }
}

.card {
    background: #ffffff;
    border-radius: 24rpx;
    margin: 28rpx 24rpx 0;
    padding: 30rpx 28rpx; /* 适老化：增加内边距 */
    box-shadow: 0 6rpx 20rpx rgba(15, 23, 42, 0.04);
    animation: card-rise 0.45s ease-out both;
}

/* 让各卡片错位出现，营造层次感 */
.streak-strip { animation: card-rise 0.45s ease-out 0.05s both; }
.card-switcher { animation: card-rise 0.45s ease-out 0.10s both; }
.range-bar { animation: card-rise 0.4s ease-out 0.14s both; }
.voice-bar { animation: card-rise 0.4s ease-out 0.18s both; }
.chart-card { animation: card-rise 0.5s ease-out 0.20s both; }
.calendar-card { animation: card-rise 0.5s ease-out 0.25s both; }
.records-section { animation: card-rise 0.5s ease-out 0.30s both; }

@keyframes card-rise {
    from {
        opacity: 0;
        transform: translateY(8rpx); /* 适老化：减小位移 */
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes stat-card-pop {
    from {
        opacity: 0;
        transform: translateY(20rpx) scale(0.96);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

.stat-card {
    animation: stat-card-pop 0.5s ease-out both;
}
.stat-card.stat-fasting { animation-delay: 0.06s; }
.stat-card.stat-postprandial { animation-delay: 0.14s; }
.stat-card.stat-other { animation-delay: 0.22s; }

.card-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.card-title {
    font-size: 36rpx; /* 适老化：放大区块标题 */
    font-weight: 800;
    color: #0f172a;
    letter-spacing: 0.5rpx;
}

.card-subtitle {
    display: block;
    font-size: 26rpx; /* 适老化：放大 */
    color: #64748b; /* 提升对比度 */
    margin-top: 6rpx;
}

.card-legend {
    display: flex;
    gap: 18rpx;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 8rpx;
    font-size: 22rpx;
    color: #475569;
}

.legend-dot {
    width: 16rpx;
    height: 16rpx;
    border-radius: 999rpx;
}

.legend-dot-fasting {
    background: #0ea5a4;
}

.legend-dot-postprandial {
    background: #f59e0b;
}

.chart-summary {
    display: flex;
    align-items: stretch;
    gap: 0;
    margin-top: 18rpx;
    padding: 16rpx 4rpx;
    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
    border-radius: 18rpx;
    border: 1rpx solid #eef2f5;
}
.chart-summary-item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4rpx;
    position: relative;
}
.chart-summary-item + .chart-summary-item::before {
    content: '';
    position: absolute;
    left: 0;
    top: 18%;
    bottom: 18%;
    width: 1rpx;
    background: #e2e8f0;
}
.chart-summary-num {
    font-size: 44rpx; /* 适老化：放大 */
    font-weight: 800;
    color: #0f172a;
    line-height: 1.1;
    letter-spacing: -0.5rpx;
}
.chart-summary-item:first-child .chart-summary-num {
    color: #dc2626;
}
.chart-summary-item:nth-child(2) .chart-summary-num {
    color: #0ea5a4;
}
.chart-summary-label {
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b; /* 提升对比度 */
    letter-spacing: 1rpx;
    font-weight: 500;
    margin-top: 4rpx;
}

.chart-canvas-wrap {
    position: relative;
    width: 100%;
    height: 460rpx;
    margin-top: 22rpx;
}

.chart-canvas-placeholder {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
    border-radius: 18rpx;
    color: #64748b;
    font-size: 28rpx;
    letter-spacing: 1rpx;
    font-weight: 500;
}

.chart-canvas {
    width: 100%;
    height: 100%;
    display: block;
}

.chart-empty {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14rpx;
    color: #64748b;
}
.chart-empty-icon {
    font-size: 72rpx;
}
.chart-empty-text {
    font-size: 30rpx; /* 适老化：放大空状态文案 */
    color: #475569;
    font-weight: 500;
}

.chart-tip {
    position: absolute;
    min-width: 220rpx;
    padding: 16rpx 20rpx;
    background: rgba(15, 23, 42, 0.94);
    color: #ffffff;
    border-radius: 12rpx;
    box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.18);
    pointer-events: none;
    z-index: 10;
}
.chart-tip-date {
    font-size: 26rpx; /* 适老化：放大 */
    color: rgba(255, 255, 255, 0.9);
    font-weight: 600;
}
.chart-tip-row {
    display: flex;
    align-items: center;
    gap: 10rpx;
    margin-top: 8rpx;
}
.chart-tip-text {
    font-size: 28rpx; /* 适老化：放大气泡正文 */
    color: #ffffff;
    font-weight: 500;
}

.records-section {
    margin: 28rpx 24rpx 0;
}

.records-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 20rpx;
    padding: 0 6rpx;
}
.records-count {
    font-size: 28rpx; /* 适老化：放大 */
    color: #475569;
    font-weight: 600;
}

.day-card {
    background: #ffffff;
    border-radius: 24rpx;
    padding: 30rpx 28rpx;
    margin-bottom: 22rpx;
    box-shadow: 0 6rpx 20rpx rgba(15, 23, 42, 0.04);
}

.day-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14rpx;
    border-bottom: 1rpx dashed #e2e8f0;
    padding-bottom: 12rpx;
}

.day-head-left {
    display: flex;
    align-items: center;
    gap: 10rpx;
}

.day-date {
    font-size: 36rpx; /* 适老化：放大 */
    font-weight: 800;
    color: #0f172a;
}

.day-today-pill {
    font-size: 24rpx; /* 适老化：放大 */
    padding: 4rpx 16rpx;
    border-radius: 999rpx;
    background: #0ea5a4;
    color: #ffffff;
    font-weight: 600;
}

.day-head-right {
    display: flex;
    align-items: center;
    gap: 14rpx;
}

.day-weekday {
    font-size: 26rpx; /* 适老化：放大 */
    color: #64748b;
    font-weight: 500;
}

.day-speak-btn {
    display: flex;
    align-items: center;
    gap: 8rpx;
    padding: 16rpx 24rpx; /* 适老化：扩大触控区 */
    border-radius: 999rpx;
    background: linear-gradient(135deg, #0ea5a4, #0891b2);
    box-shadow: 0 4rpx 12rpx rgba(14, 165, 164, 0.22);
    min-height: 72rpx;
    box-sizing: border-box;
    &:active {
        transform: scale(0.96);
    }
}

.day-speak-icon {
    font-size: 30rpx;
    color: #ffffff;
    line-height: 1;
}

.day-speak-text {
    font-size: 28rpx; /* 适老化：放大 */
    color: #ffffff;
    font-weight: 700;
}

.day-block {
    margin-top: 16rpx;
    padding: 16rpx 18rpx;
    background: #f8fafc;
    border-radius: 16rpx;
}

.day-block-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.day-block-tag {
    font-size: 26rpx; /* 适老化：放大 */
    padding: 6rpx 20rpx;
    border-radius: 999rpx;
    color: #ffffff;
    font-weight: 600;
    &.tag-blood {
        background: #0ea5a4;
    }
    &.tag-diet {
        background: #f59e0b;
    }
    &.tag-exercise {
        background: #6366f1;
    }
    &.tag-tracking {
        background: #64748b;
    }
}

.day-block-time {
    font-size: 26rpx; /* 适老化：放大 */
    color: #64748b;
    font-weight: 500;
}

.metric-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 12rpx;
    margin-top: 14rpx;
}

.metric-cell {
    flex: 1 1 calc(50% - 6rpx);
    background: #ffffff;
    border-radius: 14rpx;
    padding: 18rpx 18rpx 16rpx;
    position: relative;
}

.metric-row-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8rpx;
}

.metric-label {
    font-size: 26rpx; /* 适老化：放大 */
    color: #475569; /* 提升对比度 */
    font-weight: 600;
}

.metric-flag {
    display: inline-flex;
    align-items: center;
    gap: 4rpx;
    padding: 2rpx 10rpx 2rpx 6rpx;
    border-radius: 999rpx;
    background: #fee2e2;
    border: 1rpx solid #fecaca;
    &.up {
        background: linear-gradient(135deg, #ef4444, #dc2626);
        border-color: transparent;
        box-shadow: 0 4rpx 12rpx rgba(220, 38, 38, 0.28);
    }
}

.metric-flag-icon {
    font-size: 16rpx;
    color: #ffffff;
    font-weight: 700;
    line-height: 1;
}

.metric-flag-text {
    font-size: 18rpx;
    color: #ffffff;
    font-weight: 600;
}

.metric-value-row {
    display: flex;
    align-items: baseline;
    gap: 6rpx;
    margin-top: 6rpx;
}

.metric-value {
    font-size: 40rpx; /* 适老化：放大主角数字 */
    font-weight: 800;
    color: #0f172a;
    line-height: 1.1;
    &.is-high {
        color: #dc2626;
    }
}

.metric-unit {
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b; /* 提升对比度 */
    font-weight: 500;
}

.metric-trend {
    display: inline-flex;
    align-items: center;
    gap: 4rpx;
    margin-top: 10rpx;
    padding: 2rpx 12rpx 2rpx 8rpx;
    border-radius: 999rpx;
    background: #f1f5f9;
    border: 1rpx solid transparent;
    &.trend-up {
        background: #fef3c7;
        border-color: #fde68a;
        .metric-trend-arrow,
        .metric-trend-text {
            color: #b45309;
        }
    }
    &.trend-down {
        background: #dcfce7;
        border-color: #bbf7d0;
        .metric-trend-arrow,
        .metric-trend-text {
            color: #15803d;
        }
    }
    &.trend-flat {
        background: #f1f5f9;
        border-color: #e2e8f0;
        .metric-trend-arrow,
        .metric-trend-text {
            color: #64748b;
        }
    }
}

.metric-trend-arrow {
    font-size: 20rpx;
    font-weight: 700;
    line-height: 1;
}

.metric-trend-text {
    font-size: 24rpx; /* 适老化：放大 */
    font-weight: 700;
}

.day-meta-row {
    display: flex;
    flex-wrap: wrap;
    gap: 10rpx;
    margin-top: 14rpx;
}

.meta-chip {
    font-size: 22rpx;
    color: #475569;
    background: #ffffff;
    border-radius: 999rpx;
    padding: 6rpx 18rpx;
    border: 1rpx solid #e2e8f0;
}

.day-remark {
    margin-top: 14rpx;
    font-size: 28rpx; /* 适老化：放大正文 */
    color: #334155; /* 提升对比度 */
    line-height: 1.7;
}

.diet-rows {
    margin-top: 14rpx;
    display: flex;
    flex-direction: column;
    gap: 10rpx;
}

.diet-row {
    display: flex;
    gap: 10rpx;
    align-items: flex-start;
}

.diet-meal {
    flex-shrink: 0;
    width: 140rpx;
    font-size: 28rpx; /* 适老化：放大 */
    color: #b45309; /* 提升对比度 */
    font-weight: 700;
    font-weight: 600;
}

.diet-text {
    flex: 1;
    font-size: 28rpx; /* 适老化：放大正文 */
    color: #0f172a;
    line-height: 1.7;
}

.exercise-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12rpx;
    margin-top: 14rpx;
}

.exercise-type {
    font-size: 26rpx;
    color: #1f2937;
    font-weight: 600;
}

.exercise-duration {
    font-size: 24rpx;
    color: #6366f1;
    background: #eef2ff;
    border-radius: 999rpx;
    padding: 4rpx 16rpx;
}

.exercise-intensity {
    font-size: 24rpx;
    color: #475569;
}

.tracking-content {
    margin-top: 12rpx;
    display: flex;
    flex-direction: column;
    gap: 6rpx;
}

.tracking-line {
    font-size: 28rpx; /* 适老化：放大正文 */
    color: #0f172a;
    line-height: 1.7;
    word-break: break-all;
}

.state-card {
    background: #ffffff;
    border-radius: 24rpx;
    padding: 60rpx 24rpx;
    text-align: center;
    box-shadow: 0 6rpx 20rpx rgba(15, 23, 42, 0.04);
}

.state-icon {
    display: block;
    font-size: 76rpx;
    margin-bottom: 16rpx;
}

.state-text {
    font-size: 30rpx; /* 适老化：放大空状态 */
    color: #475569;
    font-weight: 500;
}

.footer-tip {
    text-align: center;
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b;
    margin-top: 40rpx;
    line-height: 1.6;
}

/* === Hero 装饰圆（静态，适老化：去掉漂浮动画避免视觉干扰）=== */
.hero-orb {
    position: absolute;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.10);
    pointer-events: none;
    filter: blur(2rpx);
}
.hero-orb-1 {
    width: 220rpx;
    height: 220rpx;
    top: -60rpx;
    right: -40rpx;
    background: rgba(255, 255, 255, 0.14);
}
.hero-orb-2 {
    width: 140rpx;
    height: 140rpx;
    top: 40rpx;
    right: 240rpx;
    background: rgba(253, 224, 71, 0.14);
}
.hero-orb-3 {
    width: 90rpx;
    height: 90rpx;
    bottom: 30rpx;
    left: 40%;
    background: rgba(255, 255, 255, 0.10);
}

.hero-greet-row {
    display: flex;
    align-items: center;
    gap: 12rpx;
    margin-bottom: 6rpx;
}
.hero-greet-emoji {
    font-size: 36rpx;
}
.hero-greet-text {
    font-size: 30rpx;
    color: rgba(255, 255, 255, 0.95);
    letter-spacing: 2rpx;
    font-weight: 500;
}

.hero-subtitle-row {
    display: flex;
    align-items: center;
    gap: 16rpx;
    margin-top: 10rpx;
    flex-wrap: wrap;
}

.hero-status-chip {
    display: inline-flex;
    align-items: center;
    gap: 8rpx;
    padding: 8rpx 18rpx 8rpx 12rpx;
    border-radius: 999rpx;
    font-size: 28rpx; /* 适老化：放大 */
    font-weight: 600;
    backdrop-filter: blur(8rpx);
    &.done {
        background: rgba(34, 197, 94, 0.28);
        color: #ffffff;
        border: 1rpx solid rgba(187, 247, 208, 0.6);
    }
    &.pending {
        background: rgba(251, 191, 36, 0.32);
        color: #ffffff;
        border: 1rpx solid rgba(253, 230, 138, 0.6);
    }
}
.hero-status-icon {
    width: 36rpx;
    height: 36rpx;
    border-radius: 999rpx;
    background: rgba(255, 255, 255, 0.42);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24rpx;
    line-height: 1;
    font-weight: 800;
}
.hero-status-text {
    line-height: 1.2;
}

/* === 打卡条 streak strip === */
.streak-strip {
    position: relative;
    margin: 22rpx 24rpx 0;
    background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
    border: 1rpx solid #eef2f5;
    border-radius: 24rpx;
    padding: 28rpx 28rpx;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20rpx;
    box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.06);
    overflow: hidden;
    min-height: 132rpx; /* 适老化：保证点击区足够大 */
    box-sizing: border-box;
    &:active {
        transform: scale(0.99);
    }
    /* 右上角装饰光晕 */
    &::after {
        content: '';
        position: absolute;
        top: -40rpx;
        right: -40rpx;
        width: 160rpx;
        height: 160rpx;
        border-radius: 50%;
        background: radial-gradient(circle, rgba(251, 191, 36, 0.18) 0%, rgba(251, 191, 36, 0) 70%);
        pointer-events: none;
    }
}
.streak-strip-left {
    display: flex;
    align-items: center;
    gap: 16rpx;
    flex-shrink: 0;
}
.streak-flame {
    width: 92rpx;
    height: 92rpx;
    border-radius: 24rpx;
    background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: inset 0 -3rpx 0 rgba(245, 158, 11, 0.18);
    flex-shrink: 0;
    &.active {
        background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
        box-shadow: 0 6rpx 14rpx rgba(249, 115, 22, 0.42), inset 0 -3rpx 0 rgba(0, 0, 0, 0.08);
        /* 适老化：去掉摇晃动画，避免视觉疲劳 */
    }
}
.streak-flame-icon {
    font-size: 52rpx;
    line-height: 1;
}
.streak-info {
    display: flex;
    flex-direction: column;
    gap: 4rpx;
}
.streak-num-row {
    display: flex;
    align-items: baseline;
    gap: 8rpx;
    flex-wrap: wrap;
}
.streak-num {
    font-size: 64rpx; /* 适老化：放大主角数字 */
    font-weight: 800;
    line-height: 1;
    letter-spacing: -1rpx;
    background: linear-gradient(135deg, #0ea5a4 0%, #0d9488 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
.streak-num-unit {
    font-size: 32rpx; /* 适老化：放大 */
    color: #475569;
    font-weight: 700;
}
.streak-hint {
    margin-left: 8rpx;
    font-size: 22rpx; /* 适老化：放大 */
    color: #64748b;
    background: #f1f5f9;
    border-radius: 999rpx;
    padding: 4rpx 14rpx;
    font-weight: 500;
}
.streak-label {
    font-size: 26rpx; /* 适老化：放大 */
    color: #475569; /* 提升对比度 */
    letter-spacing: 0.5rpx;
    margin-top: 2rpx;
}

.streak-strip-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 10rpx;
    flex-shrink: 0;
}
.streak-dots-row {
    display: flex;
    gap: 12rpx;
}
.streak-dot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6rpx;
}
.streak-dot-label {
    font-size: 22rpx; /* 适老化：放大 */
    color: #64748b;
    line-height: 1;
    font-weight: 600;
}
.streak-dot-mark {
    width: 26rpx; /* 适老化：放大点 */
    height: 26rpx;
    border-radius: 999rpx;
    background: #e2e8f0;
}
.streak-dot.done .streak-dot-mark {
    background: linear-gradient(135deg, #0ea5a4 0%, #14b8a6 100%);
    box-shadow: 0 2rpx 6rpx rgba(14, 165, 164, 0.4);
}
.streak-dot.today {
    .streak-dot-label {
        color: #0f172a;
        font-weight: 700;
    }
    .streak-dot-mark {
        width: 30rpx;
        height: 30rpx;
        border: 2rpx solid #0ea5a4;
    }
}
.streak-dot.today.done .streak-dot-mark {
    border-color: #ffffff;
    box-shadow: 0 0 0 3rpx #0ea5a4, 0 2rpx 8rpx rgba(14, 165, 164, 0.5);
}
.streak-week-text {
    font-size: 24rpx; /* 适老化：放大 */
    color: #64748b;
    letter-spacing: 1rpx;
    font-weight: 600;
}

/* === 健康日历（适老化：与手机日历一致的月历布局）=== */
.calendar-card {
    margin: 22rpx 24rpx 0;
}
.calendar-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16rpx;
}
.calendar-head-left {
    flex: 1;
    min-width: 0;
}
.calendar-stat {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2rpx;
    padding: 4rpx 18rpx 4rpx 22rpx;
    background: linear-gradient(135deg, #f0fdfa 0%, #ccfbf1 100%);
    border-radius: 20rpx;
    border-left: 6rpx solid #0ea5a4;
}
.calendar-stat-num {
    font-size: 48rpx;
    font-weight: 800;
    color: #0d9488;
    line-height: 1.1;
    letter-spacing: -1rpx;
}
.calendar-stat-label {
    font-size: 22rpx;
    color: #0f766e;
    font-weight: 600;
}

/* 星期表头：7 列均分 */
.calendar-weekdays {
    display: flex;
    margin-top: 22rpx;
    padding: 0 4rpx;
}
.calendar-weekday {
    flex: 1;
    text-align: center;
    font-size: 26rpx;
    color: #64748b;
    font-weight: 700;
    letter-spacing: 1rpx;
    line-height: 1.4;
    padding: 8rpx 0;
}
.calendar-weekday-weekend {
    color: #f59e0b;
}

/* 月历主体 */
.calendar-grid {
    margin-top: 6rpx;
    background: #f8fafc;
    border-radius: 20rpx;
    padding: 12rpx 6rpx;
    display: flex;
    flex-direction: column;
    gap: 6rpx;
}
.calendar-row {
    display: flex;
    gap: 6rpx;
}
.calendar-cell {
    flex: 1;
    aspect-ratio: 1 / 1; /* 正方形格子，符合月历习惯 */
    min-height: 84rpx; /* 兜底：保证触控目标 */
    border-radius: 14rpx;
    background: #ffffff;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    transition: transform 0.15s ease;
    box-sizing: border-box;
    overflow: hidden;
    border: 2rpx solid transparent;
    /* 默认（in-range, 无记录）：白底浅灰字 */
    &.level-0 {
        background: #ffffff;
        border-color: #f1f5f9;
    }
    /* 非本范围日期：极淡 */
    &.off {
        background: transparent;
        opacity: 0.4;
    }
    &.level-1 {
        background: #ccfbf1;
        border-color: #99f6e4;
    }
    &.level-2 {
        background: linear-gradient(135deg, #5eead4 0%, #2dd4bf 100%);
        border-color: #14b8a6;
    }
    &.level-3 {
        background: linear-gradient(135deg, #0ea5a4 0%, #0d9488 100%);
        box-shadow: 0 4rpx 10rpx rgba(14, 165, 164, 0.35);
    }
    /* 今日：粗橙色 ring，背景统一为深橙保证对比度 */
    &.today {
        background: linear-gradient(135deg, #f97316 0%, #ea580c 100%) !important;
        border: 4rpx solid #ffffff;
        box-shadow: 0 0 0 3rpx #ea580c, 0 6rpx 16rpx rgba(234, 88, 12, 0.4);
    }
    &.clickable:active {
        transform: scale(0.94);
    }
}
.calendar-cell-day {
    font-size: 32rpx; /* 适老化：醒目的日期数字 */
    color: #1e293b;
    font-weight: 700;
    line-height: 1;
}
.calendar-cell.level-2 .calendar-cell-day,
.calendar-cell.level-3 .calendar-cell-day {
    color: #ffffff;
}
.calendar-cell.today .calendar-cell-day {
    color: #ffffff;
    font-weight: 800;
    transform: translateY(-6rpx); /* 让出底部"今天"条带空间 */
}
.calendar-cell.off .calendar-cell-day {
    color: #cbd5e1;
    font-weight: 500;
}
/* 记录日：右下角小圆点 */
.calendar-cell-mark {
    position: absolute;
    right: 8rpx;
    bottom: 8rpx;
    width: 10rpx;
    height: 10rpx;
    border-radius: 999rpx;
    background: rgba(255, 255, 255, 0.85);
    box-shadow: 0 0 0 2rpx rgba(15, 23, 42, 0.08);
}
.calendar-cell.level-1 .calendar-cell-mark {
    background: #14b8a6;
    box-shadow: none;
}
/* 今日已被底部"今天"条带占用，隐藏角标点 */
.calendar-cell.today .calendar-cell-mark {
    display: none;
}
/* "今天"角标：放在底部居中条带，避免被 overflow:hidden 裁掉 */
.calendar-cell-today-tag {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(220, 38, 38, 0.92);
    color: #ffffff;
    font-size: 18rpx;
    font-weight: 700;
    padding: 3rpx 0;
    text-align: center;
    line-height: 1.2;
    letter-spacing: 2rpx;
}

/* 图例：放在月历下方水平排列，简洁清晰 */
.calendar-legend {
    margin-top: 18rpx;
    padding: 14rpx 6rpx 4rpx;
}
.calendar-legend-row {
    display: flex;
    align-items: center;
    justify-content: space-around;
    gap: 8rpx;
}
.calendar-legend-item {
    display: flex;
    align-items: center;
    gap: 8rpx;
}
.calendar-legend-cell {
    width: 28rpx;
    height: 28rpx;
    border-radius: 8rpx;
    background: #f1f5f9;
    border: 1rpx solid #e2e8f0;
    &.level-1 { background: #ccfbf1; border-color: #99f6e4; }
    &.level-2 { background: linear-gradient(135deg, #5eead4 0%, #2dd4bf 100%); border-color: #14b8a6; }
    &.level-3 { background: linear-gradient(135deg, #0ea5a4 0%, #0d9488 100%); border-color: transparent; }
    &.legend-today {
        background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
        border: 2rpx solid #ffffff;
        box-shadow: 0 0 0 2rpx #ea580c;
    }
}
.calendar-legend-text {
    font-size: 24rpx;
    color: #475569;
    font-weight: 600;
}

.heatmap-foot {
    margin-top: 22rpx;
    padding-top: 18rpx;
    border-top: 1rpx dashed #e2e8f0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12rpx;
}
.heatmap-foot-text {
    font-size: 28rpx; /* 适老化：放大 */
    color: #334155;
    font-weight: 600;
}
.heatmap-foot-cta {
    background: linear-gradient(135deg, #0ea5a4 0%, #14b8a6 100%);
    color: #ffffff;
    font-size: 28rpx; /* 适老化：放大 */
    font-weight: 700;
    padding: 18rpx 30rpx; /* 适老化：扩大触控区 */
    border-radius: 999rpx;
    box-shadow: 0 4rpx 12rpx rgba(14, 165, 164, 0.4);
    min-height: 76rpx;
    display: flex;
    align-items: center;
    &:active {
        transform: scale(0.96);
    }
}

/* === 录入今日数据弹层（bottom sheet）=== */
.input-modal-mask {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.55);
    z-index: 999;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}

.input-modal {
    width: 100%;
    max-width: 750rpx;
    background: #ffffff;
    border-radius: 32rpx 32rpx 0 0;
    padding: 16rpx 0 calc(20rpx + env(safe-area-inset-bottom));
    box-shadow: 0 -10rpx 30rpx rgba(15, 23, 42, 0.18);
    max-height: 88vh;
    display: flex;
    flex-direction: column;
    animation: input-modal-rise 0.22s ease-out;
    /* 微信小程序里 view 默认 z-index 无效，但配合上方 v-if 卸载 canvas，
       这里给一个高 z-index 进一步保证层级（防止其他普通 view 抢占）。 */
    position: relative;
    z-index: 1000;
}

@keyframes input-modal-rise {
    from { transform: translateY(40rpx); opacity: 0.6; }
    to { transform: translateY(0); opacity: 1; }
}

.input-modal-grip {
    width: 80rpx;
    height: 6rpx;
    border-radius: 999rpx;
    background: #e2e8f0;
    margin: 0 auto 8rpx;
}

.input-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12rpx 32rpx 16rpx;
    border-bottom: 1rpx solid #f1f5f9;
}
.input-modal-title-wrap {
    display: flex;
    flex-direction: column;
    gap: 4rpx;
}
.input-modal-title {
    font-size: 40rpx; /* 适老化：放大弹层标题 */
    font-weight: 800;
    color: #0f172a;
    letter-spacing: 0.5rpx;
}
.input-modal-sub {
    font-size: 26rpx; /* 适老化：放大 */
    color: #64748b;
    margin-top: 4rpx;
}
.input-modal-close {
    width: 80rpx; /* 适老化：扩大触控区 */
    height: 80rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 999rpx;
    background: #f1f5f9;
    &:active {
        background: #e2e8f0;
    }
}
.input-modal-close-icon {
    font-size: 42rpx;
    color: #475569;
    line-height: 1;
    font-weight: 700;
}

.input-modal-loading {
    padding: 80rpx 0;
    text-align: center;
    color: #64748b;
    font-size: 30rpx;
}

.input-modal-body {
    flex: 1;
    padding: 20rpx 32rpx 28rpx;
    overflow-y: auto;
    overflow-x: hidden;
    box-sizing: border-box;
    width: 100%;
}

.input-modal-tip {
    background: #f0fdfa;
    border: 1rpx dashed #99f6e4;
    border-radius: 14rpx;
    padding: 18rpx 20rpx;
    font-size: 26rpx; /* 适老化：放大提示 */
    color: #0f766e;
    line-height: 1.5;
    margin-bottom: 22rpx;
}

.input-section-title {
    font-size: 28rpx; /* 适老化：放大分段标题 */
    font-weight: 700;
    color: #334155;
    letter-spacing: 1rpx;
    margin: 18rpx 0 14rpx;
}

.input-field {
    display: flex;
    align-items: center;
    gap: 18rpx;
    padding: 14rpx 0;
    border-bottom: 1rpx solid #f1f5f9;
    min-width: 0; /* 防止 flex item 被内容撑开 */
    width: 100%;
    box-sizing: border-box;
    min-height: 88rpx;
}
.input-field-label {
    display: flex;
    align-items: center;
    gap: 10rpx;
    flex: 0 0 auto;
    width: 180rpx;
    font-size: 30rpx; /* 适老化：放大字段标签 */
    color: #0f172a;
    font-weight: 700;
}
.input-field-dot {
    width: 14rpx;
    height: 14rpx;
    border-radius: 999rpx;
    background: #94a3b8;
    flex-shrink: 0;
}
.input-field-dot-fasting { background: #0ea5a4; }
.input-field-dot-postprandial { background: #f59e0b; }
.input-field-dot-other { background: #8b5cf6; }

.input-field-input {
    flex: 1 1 0;
    min-width: 0; /* 关键：允许 flex item 收缩到比 placeholder 还窄 */
    background: #f8fafc;
    border-radius: 14rpx;
    padding: 18rpx 20rpx; /* 适老化：增加输入框高度 */
    box-sizing: border-box;
    overflow: hidden;
    min-height: 76rpx;
    display: flex;
    align-items: center;
    input {
        width: 100%;
        min-width: 0;
        font-size: 34rpx; /* 适老化：放大输入字体 */
        color: #0f172a;
        text-align: right;
        box-sizing: border-box;
        font-weight: 600;
    }
}

.input-placeholder {
    color: #94a3b8; /* 提升对比度 */
    font-size: 28rpx;
}

.input-field-row {
    display: flex;
    gap: 18rpx;
    min-width: 0;
    width: 100%;
    box-sizing: border-box;
}
.input-field-half {
    flex: 1 1 0;
    min-width: 0;
    border-bottom: 1rpx solid #f1f5f9;
}
.input-field-half .input-field-label {
    width: 80rpx;
    flex: 0 0 auto;
}
/* 双栏血压时输入框允许更窄，避免与小标签争夺空间 */
.input-field-row .input-field-input {
    padding: 16rpx 16rpx;
    input {
        font-size: 32rpx;
    }
}

.input-field-textarea {
    flex-direction: column;
    align-items: stretch;
    border-bottom: none;
    padding: 10rpx 0 0;
    textarea {
        width: 100%;
        min-height: 140rpx;
        background: #f8fafc;
        border-radius: 14rpx;
        padding: 20rpx 22rpx;
        font-size: 30rpx; /* 适老化：放大 */
        color: #0f172a;
        box-sizing: border-box;
        line-height: 1.6;
    }
}

.input-modal-actions {
    display: flex;
    gap: 18rpx;
    padding: 20rpx 32rpx 0;
    border-top: 1rpx solid #f1f5f9;
}
.input-modal-btn {
    flex: 1;
    height: 108rpx; /* 适老化：扩大主要按钮 */
    border-radius: 18rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 34rpx; /* 适老化：放大按钮文字 */
    font-weight: 700;
    letter-spacing: 2rpx;
    &.primary {
        background: linear-gradient(135deg, #0ea5a4 0%, #14b8a6 100%);
        color: #ffffff;
        box-shadow: 0 8rpx 20rpx rgba(14, 165, 164, 0.32);
        &:active {
            transform: translateY(1rpx);
        }
    }
    &.danger {
        flex: 0 0 200rpx;
        background: #fff1f2;
        color: #dc2626;
        &:active {
            background: #ffe4e6;
        }
    }
    &.disabled {
        opacity: 0.55;
        pointer-events: none;
    }
}
</style>
