Files
Blizzard b854d2873e feat: require couple binding and refine miniapp settings
Gate record features and storage entry points with invitation guidance; enforce record access in the backend. Add homepage greetings, right-swipe profile dismissal, and move the smoking switch to settings. Simplify API base configuration and remove the unused legacy tab bar component.
2026-11-04 21:27:03 +08:00

448 lines
24 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<view wx:if="{{!recordAccessBlocked}}" capture-bind:tap="onLoginSubscribeTap" class="app-shell app-shell--secondary" style="{{layoutStyle}}">
<launch-transition />
<!-- pages/period/period.wxml - 姨姨(美柚风格日历模式) -->
<app-nav title="姨姨" back="{{true}}" /><view class="period-loading" wx:if="{{accessLoading}}"><page-state kind="loading" title="正在加载姨姨记录" /></view>
<view class="j-denied" wx:if="{{!accessLoading && accessDenied}}"><view class="j-denied-card"><image style="width:56rpx;height:56rpx" src="/images/j-flower.svg" /><text class="j-page-title">对方暂时关闭了可见</text><text class="j-caption">这份生理记录目前不可查看,需要对方重新开启后才能访问。</text><button class="j-button" bindtap="loadData">刷新权限</button></view><view class="j-permission" wx:if="{{hasCouple}}"><view><text class="j-small-title">对方可见</text><text class="j-caption">你的开关只决定对方能否查看</text></view><switch checked="{{periodVisible}}" disabled="{{visibilitySaving}}" bindchange="onPeriodVisibleChange" color="#B74768" /></view></view>
<view class="j-denied" wx:if="{{!accessLoading && selectionRequired}}"><view class="j-denied-card"><text class="j-page-title">选择保留的生理记录</text><text class="j-caption">绑定前双方都有记录,请选择一份作为当前记录。另一份将删除。</text><button class="j-button" bindtap="choosePeriodSource">选择当前记录</button></view></view>
<scroll-view class="page-bg" wx:if="{{!accessLoading && !accessDenied && !selectionRequired}}" scroll-y show-scrollbar="{{false}}" bounces="{{true}}">
<view class="j-body">
<view class="j-period-summary"><view class="j-row"><text class="j-eyebrow">BODY JOURNAL</text><view class="j-link" bindtap="showPeriodSettings">周期设置</view></view><text class="j-period-title">{{cycles.length ? '周期第 ' + cycleDay + ' 天' : '慢慢了解自己的节奏'}}</text><text class="j-period-description">{{cycles.length ? '周期长度 ' + avgCycle + ' 天 · 距下次预计 ' + predictedDays + ' 天' : '从记录一次生理期开始'}}</text><text wx:if="{{cycles.length}}" class="j-caption">{{periodStatusText}}</text></view>
<view wx:if="{{!cycles.length}}" class="j-denied-card" style="margin-bottom:36rpx"><text class="j-small-title">还没有生理期记录</text><text class="j-caption">填写最近一次生理期,开始记录身体状态。</text><button class="j-button" bindtap="showPeriodSettings">设置生理期</button></view>
<!-- ===== 日历卡片 ===== -->
<view class="calendar-card">
<!-- 日历导航 -->
<view class="calendar-nav">
<text class="nav-btn" bindtap="prevMonth">‹</text>
<text class="nav-title">{{currentYear}}年{{currentMonth}}月</text>
<text class="nav-btn" bindtap="nextMonth">›</text>
</view>
<!-- 日历星期头 -->
<view class="calendar-weekdays">
<text wx:for="{{weekdays}}" wx:key="index" class="weekday {{item === '日' || item === '六' ? 'weekend' : ''}}">{{item}}</text>
</view>
<!-- 日历格子 -->
<view class="calendar-grid">
<view wx:for="{{calendarDays}}" wx:key="index" class="calendar-cell {{item.className}}" catchtap="{{(item.isFuture || !item.dateStr) ? '' : 'onDayTap'}}" bindlongpress="{{(item.isFuture || !item.dateStr) ? '' : 'onDayLongPress'}}" data-date="{{item.dateStr}}" data-index="{{index}}">
<!-- 爱爱图标(日期数字上方) -->
<view wx:if="{{item.hasSex}}" class="sex-icon-top">
<text class="sex-icon-top-text">💕</text>
<text wx:if="{{item.sexCount > 1}}" class="sex-icon-top-count">{{item.sexCount}}</text>
</view>
<text class="cell-day">{{item.isToday ? '今' : item.day}}</text>
<view class="cell-dots-row">
<!-- 经期指示 -->
<view wx:if="{{item.periodLevel > 0}}" class="cell-dot period-dot level-{{item.periodLevel}}"></view>
<!-- 有记录指示(无经期时显示) -->
<view wx:if="{{item.hasRecord && item.periodLevel === 0}}" class="cell-dot record-dot"></view>
</view>
</view>
</view>
<!-- 日历图例(卡片内最下方) -->
<view class="calendar-legend">
<view class="legend-item"><view class="legend-dot legend-period"></view><text>经期</text></view>
<view class="legend-item"><view class="legend-dot legend-ovulation-day"></view><text>排卵日</text></view>
<view class="legend-item"><view class="legend-dot legend-ovulation"></view><text>排卵期</text></view>
<view class="legend-item"><view class="legend-dot legend-predicted"></view><text>预测经期</text></view>
<view class="legend-item"><view class="legend-dot legend-safe"></view><text>安全期</text></view>
</view>
</view>
<!-- ===== 快捷记录卡片(8个按钮,日历下方) ===== -->
<page-state wx:if="{{!selectedDate}}" title="选择一天,留意身体的感受" description="点选上方日历中的日期,查看或填写当天记录" />
<view class="quick-events-card" wx:if="{{selectedDate}}"><view class="section-heading"><text>{{selectedDateLabel}}</text><text class="section-link">{{selectedWeekday}}</text></view>
<view class="quick-events-grid">
<view class="quick-event-btn {{dailyRecord.period > 0 ? 'active period' : ''}}" catchtap="onPeriodTap">
<image class="qe-icon" src="/images/j-flower.svg" />
<text class="qe-label">月经</text>
<text wx:if="{{dailyRecord.period > 0}}" class="qe-check">✓</text>
</view>
<view class="quick-event-btn {{sexRecords.length > 0 ? 'active sex' : ''}}" catchtap="showSexRecords">
<image class="qe-icon" src="/images/heart.svg" />
<text class="qe-label">爱爱</text>
<text wx:if="{{sexRecords.length > 0}}" class="sex-count-badge">{{sexRecords.length}}</text>
</view>
<view class="quick-event-btn {{dailyRecord.symptoms ? 'active' : ''}}" catchtap="showSymptomPicker">
<image class="qe-icon" src="/images/j-activity.svg" />
<text class="qe-label">症状</text>
<text wx:if="{{dailyRecord.symptoms}}" class="qe-check">✓</text>
</view>
<view class="quick-event-btn {{dailyRecord.mood ? 'active' : ''}}" catchtap="showMoodPicker">
<image class="qe-icon" src="/images/j-smile.svg" />
<text class="qe-label">心情</text>
<text wx:if="{{dailyRecord.mood}}" class="qe-check">✓</text>
</view>
<view class="quick-event-btn {{dailyRecord.discharge ? 'active' : ''}}" catchtap="onDischargeTap">
<image class="qe-icon" src="/images/j-droplet.svg" />
<text class="qe-label">白带</text>
<text wx:if="{{dailyRecord.discharge}}" class="qe-check">✓</text>
</view>
<view class="quick-event-btn {{dailyRecord.temperature ? 'active' : ''}}" catchtap="onTempTap">
<image class="qe-icon" src="/images/j-thermometer.svg" />
<text class="qe-label">体温</text>
<text wx:if="{{dailyRecord.temperature}}" class="qe-check">✓</text>
</view>
<view class="quick-event-btn {{dailyRecord.weight ? 'active' : ''}}" catchtap="onWeightTap">
<image class="qe-icon" src="/images/j-scale.svg" />
<text class="qe-label">体重</text>
<text wx:if="{{dailyRecord.weight}}" class="qe-check">✓</text>
</view>
<view class="quick-event-btn {{dailyRecord.note ? 'active' : ''}}" catchtap="showNoteInput">
<image class="qe-icon" src="/images/book.svg" />
<text class="qe-label">备注</text>
<text wx:if="{{dailyRecord.note}}" class="qe-check">✓</text>
</view>
</view>
</view>
<view class="j-permission" wx:if="{{hasCouple}}"><view><text class="j-small-title">对方可见</text><text class="j-caption">关闭后,对方无法查看这份生理记录</text></view><switch checked="{{periodVisible}}" disabled="{{visibilitySaving}}" bindchange="onPeriodVisibleChange" color="#B74768" /></view>
<!-- ===== 历史周期列表 ===== -->
<view class="section-title">历史周期<text class="section-hint">长按可删除</text></view>
<view class="cycle-list" wx:if="{{cycles.length > 0}}">
<view class="cycle-card" wx:for="{{cycles}}" wx:key="start_date" bindlongpress="onCycleLongPress" data-id="{{item.id}}" data-start="{{item.start_date}}" data-end="{{item.end_date}}">
<view class="cycle-header">
<image class="cycle-icon" src="/images/j-flower.svg" style="width:28rpx;height:28rpx" />
<text class="cycle-date">{{item.start_date}} ~ {{item.end_date}}</text>
</view>
<view class="cycle-details">
<text class="cycle-detail">周期 {{item.cycle_length}}天</text>
<text class="cycle-detail">持续 {{item.period_length}}天</text>
<text wx:if="{{item.symptoms}}" class="cycle-detail">{{item.symptoms}}</text>
</view>
<view wx:if="{{item.note}}" class="cycle-note">{{item.note}}</view>
</view>
</view>
<view class="empty-state" wx:else>
<text class="empty-icon">🌸</text>
<text class="empty-title">还没有周期记录</text>
<text class="empty-desc">点击上方周期设置,记录最近一次生理期</text>
</view>
</view>
</scroll-view>
<!-- 浮动按钮已移至日历卡片内 -->
<!-- ===== 姨姨设置弹窗 ===== -->
<view class="modal-overlay" wx:if="{{showCycleModal}}" catchtouchmove="preventMove">
<scroll-view scroll-y class="modal period-modal settings-modal" show-scrollbar="{{false}}">
<text class="modal-title">姨姨设置</text>
<text class="modal-subtitle">填写上次姨妈信息,帮你推算下次</text>
<view class="modal-form">
<!-- 持续天数 -->
<view class="form-row">
<text class="form-label">持续天数</text>
<view class="form-stepper">
<text class="stepper-btn" catchtap="adjustPeriod" data-dir="-1">−</text>
<text class="stepper-value">{{cycleForm.period_length}}天</text>
<text class="stepper-btn" catchtap="adjustPeriod" data-dir="1">+</text>
</view>
</view>
<!-- 上一次来时 -->
<view class="form-row">
<text class="form-label">上一次来时</text>
<picker mode="date" value="{{cycleForm.start_date}}" bindchange="onCycleStartChange">
<view class="form-picker">{{cycleForm.start_date}}</view>
</picker>
</view>
<!-- 结束时 -->
<view class="form-row">
<text class="form-label">结束时</text>
<picker mode="date" value="{{cycleForm.end_date}}" bindchange="onCycleEndChange">
<view class="form-picker">{{cycleForm.end_date}}</view>
</picker>
</view>
<!-- 周期长度(用于推算) -->
<view class="form-row">
<text class="form-label">周期长度</text>
<view class="form-stepper">
<text class="stepper-btn" catchtap="adjustCycle" data-dir="-1">−</text>
<text class="stepper-value">{{cycleForm.cycle_length}}天</text>
<text class="stepper-btn" catchtap="adjustCycle" data-dir="1">+</text>
</view>
</view>
</view>
<!-- 推算结果 -->
<view class="prediction-box">
<text class="prediction-title">📅 推算结果</text>
<view class="prediction-row">
<text class="prediction-label">下次姨妈</text>
<text class="prediction-value">{{cycleForm.predicted_start}} ~ {{cycleForm.predicted_end}}</text>
</view>
<view class="prediction-row">
<text class="prediction-label">预计排卵日</text>
<text class="prediction-value ovulation">{{cycleForm.predicted_ovulation_day}}</text>
</view>
<view class="prediction-row">
<text class="prediction-label">排卵期</text>
<text class="prediction-value ovulation-range">{{cycleForm.predicted_ovulation_start}} ~ {{cycleForm.predicted_ovulation_end}}</text>
</view>
<!-- 推算公式说明 -->
<view class="formula-toggle" bindtap="toggleFormula">
<text class="formula-toggle-text">{{showFormula ? '收起' : '查看'}}推算公式</text>
<text class="formula-toggle-arrow">{{showFormula ? '▲' : '▼'}}</text>
</view>
<view class="formula-detail" wx:if="{{showFormula}}">
<view class="formula-item">
<text class="formula-label">下次姨妈:</text>
<text class="formula-text">上次来时 + 周期长度({{cycleForm.cycle_length}}天)</text>
</view>
<view class="formula-item">
<text class="formula-label">下次结束:</text>
<text class="formula-text">下次来时 + 持续天数({{cycleForm.period_length}}天) − 1</text>
</view>
<view class="formula-item">
<text class="formula-label">排卵日:</text>
<text class="formula-text">下次姨妈开始日 − 14天</text>
</view>
<view class="formula-item">
<text class="formula-label">排卵期:</text>
<text class="formula-text">排卵日 ± 3天(共7天)</text>
</view>
<view class="formula-item">
<text class="formula-label">安全期:</text>
<text class="formula-text">经期 & 排卵期之外的日期</text>
</view>
</view>
<!-- 免责声明 -->
<view class="disclaimer-box">
<text class="disclaimer-icon">⚠️</text>
<text class="disclaimer-text">以上推算基于标准周期公式,<text class="disclaimer-bold">仅供参考</text>。实际周期受个人身体状态、作息规律、情绪压力、饮食运动等多种因素影响,可能存在偏差。如有疑问请咨询专业医生。</text>
</view>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeCycleModal">取消</view>
<view class="btn btn-primary" bindtap="saveCycle">保存设置</view>
</view>
</scroll-view>
</view>
<!-- ===== 经期详情弹窗(量、颜色、疼痛) ===== -->
<view class="modal-overlay" wx:if="{{showPeriodModal}}" catchtouchmove="preventMove">
<view class="modal period-modal">
<view class="modal-heart">🌸</view>
<text class="modal-title">月经详情</text>
<text class="modal-subtitle">{{selectedDateLabel}} {{selectedWeekday}}</text>
<!-- 量 -->
<view class="period-section">
<text class="period-section-label">量</text>
<view class="period-chip-group">
<view wx:for="{{periodAmountOptions}}" wx:key="value" class="period-chip {{periodForm.amount === item.value ? 'active' : ''}}" catchtap="onPeriodAmount" data-value="{{item.value}}">{{item.label}}</view>
</view>
</view>
<!-- 颜色 -->
<view class="period-section">
<text class="period-section-label">颜色</text>
<view class="period-chip-group">
<view wx:for="{{periodColorOptions}}" wx:key="value" class="period-chip {{periodForm.color === item.value ? 'active' : ''}}" catchtap="onPeriodColor" data-value="{{item.value}}">
<view class="color-dot" style="background:{{item.dot}};"></view>
<text>{{item.label}}</text>
</view>
</view>
</view>
<!-- 疼痛 -->
<view class="period-section">
<text class="period-section-label">疼痛</text>
<view class="period-chip-group">
<view wx:for="{{periodPainOptions}}" wx:key="value" class="period-chip {{periodForm.pain === item.value ? 'active' : ''}}" catchtap="onPeriodPain" data-value="{{item.value}}">{{item.label}}</view>
</view>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closePeriodModal">取消</view>
<view class="btn btn-primary" bindtap="confirmPeriodDetail">确定</view>
</view>
</view>
</view>
<!-- ===== 症状选择弹窗 ===== -->
<view class="modal-overlay" wx:if="{{showSymptomModal}}" catchtouchmove="preventMove">
<view class="modal period-modal">
<view class="modal-heart">🤒</view>
<text class="modal-title">选择症状</text>
<view class="symptom-grid">
<text wx:for="{{symptomOptions}}" wx:key="*this" class="symptom-tag-lg {{symptomMap[item] ? 'active' : ''}}" catchtap="toggleSymptom" data-symptom="{{item}}">{{item}}</text>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeSymptomModal">取消</view>
<view class="btn btn-primary" bindtap="confirmSymptoms">确定</view>
</view>
</view>
</view>
<!-- ===== 心情选择弹窗 ===== -->
<view class="modal-overlay" wx:if="{{showMoodModal}}" catchtouchmove="preventMove">
<view class="modal period-modal">
<view class="modal-heart">😊</view>
<text class="modal-title">选择心情</text>
<view class="mood-grid">
<view wx:for="{{moodOptions}}" wx:key="*this" class="mood-item {{dailyRecord.mood === item.label ? 'active' : ''}}" catchtap="selectMood" data-mood="{{item.label}}">
<text class="mood-emoji">{{item.emoji}}</text>
<text class="mood-label">{{item.label}}</text>
</view>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeMoodModal">取消</view>
</view>
</view>
</view>
<!-- ===== 白带详情弹窗(状态、流量、异常) ===== -->
<view class="modal-overlay" wx:if="{{showDischargeModal}}" catchtouchmove="preventMove">
<view class="modal period-modal">
<view class="modal-heart">💧</view>
<text class="modal-title">白带详情</text>
<text class="modal-subtitle">{{selectedDateLabel}} {{selectedWeekday}}</text>
<!-- 状态 -->
<view class="period-section">
<text class="period-section-label">状态</text>
<view class="period-chip-group">
<view wx:for="{{dischargeStatusOptions}}" wx:key="value" class="period-chip {{dischargeForm.status === item.value ? 'active' : ''}}" catchtap="onDischargeStatus" data-value="{{item.value}}">{{item.label}}</view>
</view>
</view>
<!-- 流量 -->
<view class="period-section">
<text class="period-section-label">流量</text>
<view class="period-chip-group">
<view wx:for="{{dischargeFlowOptions}}" wx:key="value" class="period-chip {{dischargeForm.flow === item.value ? 'active' : ''}}" catchtap="onDischargeFlow" data-value="{{item.value}}">{{item.label}}</view>
</view>
</view>
<!-- 异常(多选) -->
<view class="period-section">
<text class="period-section-label">异常(可多选)</text>
<view class="period-chip-group">
<view wx:for="{{dischargeAbnormalOptions}}" wx:key="value" class="period-chip {{dischargeAbnormalMap[item.value] ? 'active' : ''}}" catchtap="onDischargeAbnormal" data-value="{{item.value}}">{{item.label}}</view>
</view>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeDischargeModal">取消</view>
<view class="btn btn-primary" bindtap="confirmDischargeDetail">确定</view>
</view>
</view>
</view>
<!-- ===== 体温输入弹窗 ===== -->
<view class="modal-overlay" wx:if="{{showTempModal}}" catchtouchmove="preventMove">
<view class="modal period-modal">
<view class="modal-heart">🌡️</view>
<text class="modal-title">记录体温</text>
<view class="temp-input-row">
<input placeholder-class="handwritten-placeholder" class="temp-input" type="digit" placeholder="36.5" value="{{tempValue}}" bindinput="onTempInput" />
<text class="temp-unit">°C</text>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeTempModal">取消</view>
<view class="btn btn-primary" bindtap="confirmTemp">保存</view>
</view>
</view>
</view>
<!-- ===== 体重输入弹窗 ===== -->
<view class="modal-overlay" wx:if="{{showWeightModal}}" catchtouchmove="preventMove">
<view class="modal period-modal">
<view class="modal-heart">⚖️</view>
<text class="modal-title">记录体重</text>
<view class="temp-input-row">
<input placeholder-class="handwritten-placeholder" class="temp-input" type="digit" placeholder="50.0" value="{{weightValue}}" bindinput="onWeightInput" />
<text class="temp-unit">kg</text>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeWeightModal">取消</view>
<view class="btn btn-primary" bindtap="confirmWeight">保存</view>
</view>
</view>
</view>
<!-- ===== 备注输入弹窗 ===== -->
<view class="modal-overlay" wx:if="{{showNoteModal}}" catchtouchmove="preventMove">
<view class="modal period-modal">
<view class="modal-heart">📝</view>
<text class="modal-title">添加备注</text>
<textarea placeholder-class="handwritten-placeholder" class="note-textarea" placeholder="记录一下今天的状态..." value="{{noteValue}}" bindinput="onNoteInput" maxlength="500" />
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeNoteModal">取消</view>
<view class="btn btn-primary" bindtap="confirmNote">确定</view>
</view>
</view>
</view>
<!-- ===== 爱爱记录弹窗 ===== -->
<view class="modal-overlay" wx:if="{{showSexModal}}" catchtouchmove="preventMove">
<view class="modal period-modal sex-modal">
<view class="modal-heart">💕</view>
<text class="modal-title">爱爱记录</text>
<text class="modal-subtitle">{{selectedDateLabel}} {{selectedWeekday}}</text>
<!-- 已记录列表 -->
<scroll-view class="sex-record-list" wx:if="{{sexRecords.length > 0}}" scroll-y show-scrollbar="{{false}}">
<view class="sex-record-item" wx:for="{{sexRecords}}" wx:key="id">
<view class="sex-record-info">
<text class="sex-time">{{item.record_time ? item.record_time.substring(0, 5) : '--:--'}}</text>
<text class="sex-measure">{{item.measure || '未知'}}</text>
<text wx:if="{{item.remark}}" class="sex-remark">{{item.remark}}</text>
</view>
<view class="sex-record-time">{{item.record_time || '--:--'}}</view>
<view class="sex-record-del" catchtap="deleteSexRecord" data-id="{{item.id}}">
<text class="sex-del-icon">✕</text>
</view>
</view>
</scroll-view>
<!-- 添加新记录 -->
<view class="sex-add-section">
<text class="sex-add-title">添加记录</text>
<!-- 时间选择 -->
<view class="sex-form-row">
<text class="sex-form-label">时间</text>
<picker mode="time" value="{{sexForm.record_time}}" bindchange="onSexTimeChange">
<view class="sex-form-picker">{{sexForm.record_time || '选择时间'}}</view>
</picker>
</view>
<!-- 措施选择 -->
<text class="sex-form-label" style="margin-top:16rpx;display:block;">措施</text>
<view class="sex-measure-grid">
<view wx:for="{{measureOptions}}" wx:key="value" class="sex-measure-item {{sexForm.measure === item.value ? 'active' : ''}}" catchtap="onSexMeasureSelect" data-measure="{{item.value}}">
<text class="sex-measure-icon">{{item.icon}}</text>
<text class="sex-measure-label">{{item.label}}</text>
</view>
</view>
<!-- 备注 -->
<view class="sex-form-row">
<text class="sex-form-label">备注</text>
<input placeholder-class="handwritten-placeholder" class="sex-form-input" placeholder="选填" value="{{sexForm.remark}}" bindinput="onSexRemarkInput" />
</view>
</view>
<view class="modal-actions">
<view class="btn btn-secondary" bindtap="closeSexModal">关闭</view>
<view class="btn btn-primary" bindtap="confirmSexAdd">添加记录</view>
</view>
</view>
</view>
</view>