Files
2026-10-26 17:32:47 +08:00

1682 lines
54 KiB
JavaScript
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.
const { getLayout } = require('../../utils/layout');
// pages/period/period.js - 姨姨(美柚风格日历模式)
const { api } = require('../../utils/request');
Page({
onResize: function() { this.setData(getLayout()); },
data: {
// 日历
currentYear: 0,
currentMonth: 0,
calendarDays: [],
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
selectedDate: '',
selectedDateLabel: '',
selectedWeekday: '',
// 统计数据
cycleDay: 0,
predictedDays: 0,
avgCycle: 0,
periodStatusText: '暂无数据',
periodStatusClass: '',
// 每日记录
dailyRecord: {},
hasDetailContent: false,
// 周期列表
cycles: [],
savedCycles: [], // period_records 原始周期记录(用于姨姨设置弹窗预填,还原真实周期参数)
dailyRecordsMap: {}, // key: dateStr -> record
// 弹窗控制
showCycleModal: false,
showFormula: false,
showPeriodModal: false,
// 经期详情表单(量、颜色、疼痛)
periodForm: { amount: '', color: '', pain: '' },
periodAmountOptions: [
{ value: '极少', label: '极少' },
{ value: '少', label: '少' },
{ value: '中等', label: '中等' },
{ value: '多', label: '多' },
{ value: '极多', label: '极多' }
],
periodColorOptions: [
{ value: '浅红', label: '浅红', dot: '#FFCDD2' },
{ value: '鲜红', label: '鲜红', dot: '#FF1744' },
{ value: '深红', label: '深红', dot: '#B71C1C' },
{ value: '褐色', label: '褐色', dot: '#8D6E63' },
{ value: '黑色', label: '黑色', dot: '#424242' }
],
periodPainOptions: [
{ value: '无', label: '无' },
{ value: '轻微', label: '轻微' },
{ value: '中微', label: '中微' },
{ value: '剧烈', label: '剧烈' }
],
cycleForm: {
start_date: '',
end_date: '',
cycle_length: 28,
period_length: 5,
// 推算结果
predicted_start: '',
predicted_end: '',
predicted_ovulation_day: '',
predicted_ovulation_start: '',
predicted_ovulation_end: ''
},
// 症状
showSymptomModal: false,
selectedSymptoms: [],
symptomMap: {},
symptomOptions: ['腹痛', '腰酸', '乏力', '头痛', '情绪波动', '乳房胀痛', '恶心', '腹泻', '畏寒', '失眠', '食欲增加', '无不适'],
// 心情
showMoodModal: false,
moodOptions: [
{ emoji: '😊', label: '开心' },
{ emoji: '😌', label: '平静' },
{ emoji: '😔', label: '低落' },
{ emoji: '😤', label: '烦躁' },
{ emoji: '😰', label: '焦虑' },
{ emoji: '🥱', label: '疲倦' },
{ emoji: '😭', label: '难过' },
{ emoji: '🥰', label: '甜蜜' }
],
// 白带
showDischargeModal: false,
dischargeForm: { status: '', flow: '', abnormal: [] },
dischargeAbnormalMap: {},
dischargeStatusOptions: [
{ value: '干燥', label: '干燥' },
{ value: '粘稠', label: '粘稠' },
{ value: '稀糊状', label: '稀糊状' },
{ value: '水状', label: '水状' },
{ value: '蛋清状', label: '蛋清状' }
],
dischargeFlowOptions: [
{ value: '少量', label: '少量' },
{ value: '适中', label: '适中' },
{ value: '大量', label: '大量' }
],
dischargeAbnormalOptions: [
{ value: '带血丝', label: '带血丝' },
{ value: '白带增多', label: '白带增多' },
{ value: '泡沫状', label: '泡沫状' },
{ value: '渣渣多', label: '渣渣多' },
{ value: '偏黄', label: '偏黄' },
{ value: '偏绿', label: '偏绿' },
{ value: '异味', label: '异味' }
],
// 体温
showTempModal: false,
tempValue: '',
// 体重
showWeightModal: false,
weightValue: '',
// 备注
showNoteModal: false,
noteValue: '',
// 权限设置
periodVisible: true,
visibilitySaving: false,
hasCouple: false,
accessLoading: true,
accessDenied: false,
selectionRequired: false,
myUserId: 0,
partnerUserId: 0,
myPreview: {},
partnerPreview: {},
// 爱爱记录(支持一天多条)
showSexModal: false,
sexRecords: [], // 当前选中日期的爱爱记录列表
sexRecordsMap: {}, // key: dateStr -> [{id, record_time, measure, measure_custom}]
sexForm: {
record_time: '',
measure: '',
remark: ''
},
_lastFetchTime: 0,
measureOptions: [
{ value: '无措施', label: '无措施', icon: '🚫' },
{ value: '避孕套', label: '避孕套', icon: '🛡️' },
{ value: '短效避孕药', label: '短效避孕药', icon: '💊' },
{ value: '长效避孕药', label: '长效避孕药', icon: '💊' },
{ value: '紧急避孕药', label: '紧急避孕药', icon: '💊' },
{ value: '自慰', label: '自慰', icon: '🖐️' },
{ value: '体外排精', label: '体外排精', icon: '⚠️' },
{ value: '未射精', label: '未射精', icon: '⏹️' },
{ value: '节育环', label: '节育环', icon: '⭕' },
{ value: '其他措施', label: '其他措施', icon: '📌' }
]
},
onLoad: function() {
this.setData(getLayout());
var now = new Date();
this.setData({
currentYear: now.getFullYear(),
currentMonth: now.getMonth() + 1
});
// 权限必须先从服务端确认,避免缓存中的姨姨数据短暂泄露。
this.loadData();
},
onShow: function() {
// 返回本页时重新确认伴侣是否更改了可见权限。
if (!this.data.accessLoading) this.loadData();
if (!this._permissionTimer) {
var self = this;
this._permissionTimer = setInterval(function() {
api.periodVisibleGet().then(function(access) {
var denied = access.can_access === false;
if (denied !== self.data.accessDenied) {
if (denied) self.setData({
accessDenied: true, cycles: [], savedCycles: [], dailyRecordsMap: {}, sexRecordsMap: {},
showCycleModal: false, showPeriodModal: false
});
self.loadData();
}
}).catch(function() {});
}, 10000);
}
},
onHide: function() {
if (this._permissionTimer) clearInterval(this._permissionTimer);
this._permissionTimer = null;
},
onUnload: function() {
if (this._permissionTimer) clearInterval(this._permissionTimer);
this._permissionTimer = null;
},
onPullDownRefresh: async function() {
await this.loadData();
wx.stopPullDownRefresh();
},
// ========== 数据加载 ==========
loadData: async function() {
try {
const res = await api.periodList();
var daily = res.daily || [];
var sexRecords = res.sex_records || [];
// 构建每日记录映射
var dailyMap = {};
for (var i = 0; i < daily.length; i++) {
var item = daily[i];
// 解析经期详情 JSON(量/颜色/疼痛),供弹窗高亮
if (item.period_detail) {
try {
item.period_detail = JSON.parse(item.period_detail);
} catch (e) {
item.period_detail = null;
}
}
dailyMap[item.record_date] = item;
}
// 构建爱爱记录映射(按日期分组)
var sexMap = {};
for (var i = 0; i < sexRecords.length; i++) {
var dateStr = sexRecords[i].record_date;
if (!sexMap[dateStr]) sexMap[dateStr] = [];
sexMap[dateStr].push(sexRecords[i]);
}
// 读取权限设置
var periodVisible = res.period_visible !== undefined ? !!res.period_visible : this.data.periodVisible;
if (res.can_access === false) {
try {
} catch (e) {}
this.setData({
cycles: [], savedCycles: [], dailyRecordsMap: {}, sexRecordsMap: {},
dailyRecord: {}, hasDetailContent: false,
showCycleModal: false, showPeriodModal: false, showSymptomModal: false,
showMoodModal: false, showDischargeModal: false, showTempModal: false,
showWeightModal: false, showNoteModal: false, showSexModal: false,
periodVisible: periodVisible, hasCouple: true,
accessLoading: false, accessDenied: true, selectionRequired: false
});
this.calculateStats([]);
this.buildCalendar();
return;
}
if (res.selection_required) {
this.setData({
cycles: [], savedCycles: [], dailyRecordsMap: {}, sexRecordsMap: {},
dailyRecord: {}, hasDetailContent: false,
periodVisible: periodVisible, hasCouple: true,
accessLoading: false, accessDenied: false, selectionRequired: true,
myUserId: res.my_user_id, partnerUserId: res.partner_user_id,
myPreview: res.my_preview || {}, partnerPreview: res.partner_preview || {}
});
this.calculateStats([]);
if (!this._selectionPromptShown) {
this._selectionPromptShown = true;
setTimeout(() => this.choosePeriodSource(), 0);
}
return;
}
var savedCycles = res.cycles || [];
var cycles = this._buildDisplayCycles(dailyMap, savedCycles);
this.setData({
cycles: cycles,
savedCycles: savedCycles,
dailyRecordsMap: dailyMap,
sexRecordsMap: sexMap,
periodVisible: periodVisible,
hasCouple: !!res.has_couple,
accessLoading: false,
accessDenied: false,
selectionRequired: false,
_lastFetchTime: Date.now()
});
// 写入本地缓存
this.calculateStats(cycles);
this.buildCalendar();
// 如果选中日期存在,刷新详情
if (this.data.selectedDate) {
this.refreshSelectedDayDetail();
}
} catch(e) {
console.error('从服务器加载数据失败:', e);
this.setData({ accessLoading: false });
wx.showToast({ title: '姨姨数据加载失败', icon: 'none' });
}
},
// 从 dailyRecordsMap 动态生成周期列表
// 将有月经记录(period > 0)的连续日期合并为一个周期
_buildCyclesFromDaily: function(dailyMap) {
if (!dailyMap) return [];
// 收集所有有月经记录的日期,按日期排序
var periodDates = [];
for (var dateStr in dailyMap) {
if (dailyMap.hasOwnProperty(dateStr)) {
var record = dailyMap[dateStr];
if (record && parseInt(record.period) > 0) {
periodDates.push(dateStr);
}
}
}
if (periodDates.length === 0) return [];
periodDates.sort();
// 将连续日期合并为周期
var cycles = [];
var currentStart = periodDates[0];
var currentEnd = periodDates[0];
var prevDate = periodDates[0];
for (var i = 1; i < periodDates.length; i++) {
var curr = periodDates[i];
// 检查是否连续(相邻日期差1天)
var prev = new Date(prevDate);
var next = new Date(curr);
var diff = Math.round((next - prev) / (1000 * 60 * 60 * 24));
if (diff === 1) {
currentEnd = curr;
} else {
// 不连续,结束当前周期,开始新周期
cycles.push({
start_date: currentStart,
end_date: currentEnd,
cycle_length: 28,
period_length: this._dayDiff(currentStart, currentEnd) + 1
});
currentStart = curr;
currentEnd = curr;
}
prevDate = curr;
}
// 最后一个周期
cycles.push({
start_date: currentStart,
end_date: currentEnd,
cycle_length: 28,
period_length: this._dayDiff(currentStart, currentEnd) + 1
});
// 按 start_date 降序(最新的在前面)
cycles.sort(function(a, b) { return b.start_date.localeCompare(a.start_date); });
// 计算每个周期的 cycle_length(与前一个周期的间隔)
for (var i = 0; i < cycles.length; i++) {
if (i < cycles.length - 1) {
// 当前周期与下一个(更早的)周期的间隔
var diff = this._dayDiff(cycles[i + 1].start_date, cycles[i].start_date);
if (diff > 0) {
cycles[i].cycle_length = diff;
}
}
}
return cycles;
},
// 计算两个日期字符串之间的天数差(date2 - date1)
_dayDiff: function(dateStr1, dateStr2) {
var d1 = new Date(dateStr1);
var d2 = new Date(dateStr2);
return Math.round((d2 - d1) / (1000 * 60 * 60 * 24));
},
// ========== 统计数据计算 ==========
calculateStats: function(cycles) {
if (!cycles || cycles.length === 0) {
this.setData({
cycleDay: 0,
predictedDays: 0,
avgCycle: 0,
periodStatusText: '暂无数据',
periodStatusClass: ''
});
return;
}
// 排序副本,不修改原数组
var sorted = [].concat(cycles).sort(function(a, b) { return b.start_date.localeCompare(a.start_date); });
var now = new Date();
var today = this._formatDate(now);
var last = sorted[0];
var lastStart = new Date(last.start_date);
var lastEnd = new Date(last.end_date);
// 平均周期
var totalCycle = 0;
var cycleCount = 0;
for (var i = 0; i < sorted.length; i++) {
var c = parseInt(sorted[i].cycle_length);
if (c > 0) { totalCycle += c; cycleCount++; }
}
var avgCycle = cycleCount > 0 ? Math.round(totalCycle / cycleCount) : 28;
var predictionCycle = parseInt(last.cycle_length) || avgCycle;
// 周期天数计算
// 如果今天在经期内:显示"经期第X天"(从开始日算)
// 如果今天在经期后:显示"周期第X天"(从开始日算),同时显示"经期结束第Y天"
var diffFromStart = Math.floor((now - lastStart) / (1000 * 60 * 60 * 24));
var cycleDay = diffFromStart + 1;
if (cycleDay < 1) cycleDay = 1;
// 预计下次:从开始日算起,经过 avgCycle 天为下次经期开始
// 剩余天数 = avgCycle - cycleDay
var predictedDays = predictionCycle - cycleDay;
if (predictedDays < 0) predictedDays = 0;
// 状态
var statusText = '';
var statusClass = '';
if (today >= last.start_date && today <= last.end_date) {
statusText = '生理期 · 留意身体状态,适当休息';
statusClass = 'period';
} else if (predictedDays <= 0) {
statusText = '本次生理期已结束';
statusClass = 'safe';
} else if (predictedDays <= 7) {
statusText = '预计 ' + predictedDays + ' 天后生理期开始';
statusClass = 'ovulation';
} else {
statusText = '距离下次生理期还有 ' + predictedDays + ' 天';
statusClass = 'safe';
}
this.setData({
cycleDay: cycleDay,
predictedDays: predictedDays,
avgCycle: predictionCycle,
periodStatusText: statusText,
periodStatusClass: statusClass
});
},
// ========== 日历构建 ==========
buildCalendar: function() {
var year = this.data.currentYear;
var month = this.data.currentMonth;
var dailyMap = this.data.dailyRecordsMap;
var daysInMonth = new Date(year, month, 0).getDate();
// 当月 1 号是星期几(0=周日,1=周一 ... 6=周六),用于月首补齐空白格,使日期对齐到正确的星期列
var firstWeekday = new Date(year, month - 1, 1).getDay();
var today = this._formatDate(new Date());
var selectedDate = this.data.selectedDate;
var days = [];
// 月首补齐空白占位格,让 1 号落在正确的星期列(修复星期与日期错位问题)
for (var i = 0; i < firstWeekday; i++) {
days.push({
day: '',
dateStr: '',
className: 'empty',
periodLevel: 0,
hasSex: false,
sexCount: 0,
hasRecord: false,
isFuture: false,
isToday: false
});
}
// 只显示当月日期,不补位上月和下月
for (var d = 1; d <= daysInMonth; d++) {
var dateStr = year + '-' + this._pad(month) + '-' + this._pad(d);
var dayInfo = this._buildDayInfo(d, dateStr, today, selectedDate, dailyMap);
days.push(dayInfo);
}
this.setData({ calendarDays: days });
},
_buildDayInfo: function(dayNum, dateStr, today, selectedDate, dailyMap) {
var className = '';
if (dateStr === today) className += 'today ';
if (dateStr === selectedDate) className += 'selected ';
// 标记未来日期(仅用于点击限制)
var isFuture = dateStr > today;
var record = dailyMap[dateStr];
var sexMap = this.data.sexRecordsMap;
var sexRecords = sexMap[dateStr] || [];
var periodLevel = 0;
var hasSex = sexRecords.length > 0;
var sexCount = sexRecords.length;
var hasRecord = false;
var phaseClass = ''; // 周期阶段:period/safe/ovulation
if (record) {
periodLevel = parseInt(record.period) || 0;
hasRecord = record.symptoms || record.mood || record.discharge || record.temperature || record.weight || record.note;
if (periodLevel > 0) className += 'period-day ';
}
// 计算周期阶段背景色
phaseClass = this._getCyclePhase(dateStr);
if (phaseClass) className += phaseClass + ' ';
return {
day: dayNum,
dateStr: dateStr,
className: className,
periodLevel: periodLevel,
hasSex: hasSex,
sexCount: sexCount,
hasRecord: hasRecord,
isFuture: isFuture,
isToday: dateStr === today
};
},
// 判断某日期属于哪个周期阶段
// 返回值:'phase-period' 已记录经期 | 'phase-predicted' 预测经期 | 'phase-ovulation-day' 排卵日 | 'phase-ovulation' 排卵期 | 'phase-safe' 安全期
_getCyclePhase: function(dateStr) {
var cycles = this.data.cycles;
if (!cycles || cycles.length === 0) return '';
// 排序副本,不修改原数组
var sorted = [].concat(cycles).sort(function(a, b) { return b.start_date.localeCompare(a.start_date); });
var last = sorted[0];
var avgCycle = parseInt(last.cycle_length) || this.data.avgCycle || 28;
var periodLen = parseInt(last.period_length) || 5;
var current = new Date(dateStr);
var dateTime = current.getTime();
var lastStart = new Date(last.start_date);
var lastEnd = new Date(last.end_date);
// ----- 1. 检查是否在已记录的经期内(已知周期) -----
for (var i = 0; i < cycles.length; i++) {
var cs = new Date(cycles[i].start_date);
var ce = new Date(cycles[i].end_date);
if (dateTime >= cs.getTime() && dateTime <= ce.getTime()) {
return 'phase-period';
}
}
// ----- 2. 预测下一次经期(只预测下一次) -----
var nextPeriodStart = new Date(lastStart);
nextPeriodStart.setDate(nextPeriodStart.getDate() + avgCycle);
var nextPeriodEnd = new Date(nextPeriodStart);
nextPeriodEnd.setDate(nextPeriodEnd.getDate() + periodLen - 1);
if (dateTime >= nextPeriodStart.getTime() && dateTime <= nextPeriodEnd.getTime()) {
return 'phase-predicted';
}
// ----- 3. 计算排卵日 & 排卵期(基于下一次经期推算) -----
// 排卵日 = 下次经期前14天
var ovulationDay = new Date(nextPeriodStart);
ovulationDay.setDate(ovulationDay.getDate() - 14);
var ovDayTime = ovulationDay.getTime();
// 排卵期 = 排卵日前3天 ~ 排卵日后3天(共7天,排卵日居中)
var ovStart = new Date(ovulationDay);
ovStart.setDate(ovStart.getDate() - 3);
var ovEnd = new Date(ovulationDay);
ovEnd.setDate(ovEnd.getDate() + 3);
if (dateTime >= ovStart.getTime() && dateTime <= ovEnd.getTime()) {
// 如果是排卵日当天
if (dateStr === this._formatDate(ovulationDay)) {
return 'phase-ovulation-day';
}
return 'phase-ovulation';
}
// ----- 4. 其他为安全期 -----
return 'phase-safe';
},
// ========== 日历导航 ==========
prevMonth: function() {
var y = this.data.currentYear;
var m = this.data.currentMonth;
if (m === 1) { y--; m = 12; } else { m--; }
this.setData({ currentYear: y, currentMonth: m, selectedDate: '' });
this.buildCalendar();
},
nextMonth: function() {
var y = this.data.currentYear;
var m = this.data.currentMonth;
if (m === 12) { y++; m = 1; } else { m++; }
this.setData({ currentYear: y, currentMonth: m, selectedDate: '' });
this.buildCalendar();
},
// ========== 日期点击 ==========
onDayTap: function(e) {
var dateStr = e.currentTarget.dataset.date;
var today = this._formatDate(new Date());
// 禁止选择未来日期
if (dateStr > today) {
wx.showToast({ title: '无法记录未来的日期', icon: 'none' });
return;
}
var weekday = new Date(dateStr).getDay();
var weekNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
var parts = dateStr.split('-');
var label = parseInt(parts[0]) + '年' + parseInt(parts[1]) + '月' + parseInt(parts[2]) + '日';
this.setData({
selectedDate: dateStr,
selectedDateLabel: label,
selectedWeekday: weekNames[weekday]
});
this.refreshSelectedDayDetail();
this.buildCalendar(); // 刷新选中状态
},
refreshSelectedDayDetail: function() {
var dateStr = this.data.selectedDate;
if (!dateStr) return;
var dailyMap = this.data.dailyRecordsMap;
var record = dailyMap[dateStr] || {};
var sexMap = this.data.sexRecordsMap;
var sexRecords = sexMap[dateStr] || [];
this.setData({
dailyRecord: record,
sexRecords: sexRecords,
hasDetailContent: !!(parseInt(record.period) > 0 || sexRecords.length > 0 || record.symptoms || record.mood || record.discharge || record.temperature || record.weight || record.note)
});
},
// ========== 快捷事件操作 ==========
// 通用:清空每日记录中的单个字段并保存
_clearField: function(field) {
var dateStr = this.data.selectedDate;
if (!dateStr) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var mapRecord = this.data.dailyRecordsMap[dateStr] || {};
var record = JSON.parse(JSON.stringify(mapRecord));
if (field === 'period') {
record.period = '0';
record.period_detail = null;
} else if (field === 'discharge') {
record.discharge = '';
record.discharge_detail = null;
} else if (field === 'temperature') {
record.temperature = null;
} else if (field === 'weight') {
record.weight = null;
}
this._saveDailyRecord(record);
},
// 月经 - 有记录时打开详情弹窗可查看/修改,无记录时也打开弹窗
onPeriodTap: function() {
this.togglePeriod();
},
// 白带 - 有记录时打开详情弹窗可查看/修改,无记录时也打开弹窗
onDischargeTap: function() {
this.showDischargePicker();
},
// 体温 - 有记录时点击直接清空,无记录时打开输入弹窗
onTempTap: function() {
if (this.data.dailyRecord.temperature) {
this._clearField('temperature');
} else {
this.showTempInput();
}
},
// 体重 - 有记录时点击直接清空,无记录时打开输入弹窗
onWeightTap: function() {
if (this.data.dailyRecord.weight) {
this._clearField('weight');
} else {
this.showWeightInput();
}
},
// 经期 - 显示详情弹窗(量、颜色、疼痛)
togglePeriod: function() {
var dateStr = this.data.selectedDate;
if (!dateStr) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var today = this._formatDate(new Date());
if (dateStr > today) {
wx.showToast({ title: '无法记录未来的日期', icon: 'none' });
return;
}
var record = this.data.dailyRecord || {};
// 从已有记录解析经期详情
var periodDetail = record.period_detail || {};
this.setData({
showPeriodModal: true,
periodForm: {
amount: periodDetail.amount || '',
color: periodDetail.color || '',
pain: periodDetail.pain || ''
}
});
},
// 点击选项只切换选中状态,点确定才保存
onPeriodAmount: function(e) {
var val = e.currentTarget.dataset.value;
var form = this.data.periodForm;
if (form.amount === val) {
form.amount = '';
} else {
form.amount = val;
}
this.setData({ periodForm: form });
},
onPeriodColor: function(e) {
var val = e.currentTarget.dataset.value;
var form = this.data.periodForm;
if (form.color === val) {
form.color = '';
} else {
form.color = val;
}
this.setData({ periodForm: form });
},
onPeriodPain: function(e) {
var val = e.currentTarget.dataset.value;
var form = this.data.periodForm;
if (form.pain === val) {
form.pain = '';
} else {
form.pain = val;
}
this.setData({ periodForm: form });
},
confirmPeriodDetail: function() {
var form = this.data.periodForm;
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var record = this._cloneRecord(mapRecord);
if (form.amount || form.color || form.pain) {
record.period = '1';
record.period_detail = {
amount: form.amount,
color: form.color,
pain: form.pain
};
} else {
record.period = '0';
record.period_detail = null;
}
this.setData({ showPeriodModal: false });
this._saveDailyRecord(record);
},
closePeriodModal: function() {
this.setData({ showPeriodModal: false });
},
// 爱爱 - 显示爱爱记录弹窗
showSexRecords: function() {
var dateStr = this.data.selectedDate;
if (!dateStr) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var today = this._formatDate(new Date());
if (dateStr > today) {
wx.showToast({ title: '无法记录未来的日期', icon: 'none' });
return;
}
var sexMap = this.data.sexRecordsMap;
var records = sexMap[dateStr] || [];
// 设置默认时间(当前时间,精确到分钟)
var now = new Date();
var hh = this._pad(now.getHours());
var mm = this._pad(now.getMinutes());
var defaultTime = hh + ':' + mm;
this.setData({
showSexModal: true,
sexRecords: records,
'sexForm.record_time': defaultTime,
'sexForm.measure': '',
'sexForm.remark': ''
});
},
// 爱爱 - 时间选择
onSexTimeChange: function(e) {
this.setData({ 'sexForm.record_time': e.detail.value });
},
// 爱爱 - 措施选择
onSexMeasureSelect: function(e) {
var measure = e.currentTarget.dataset.measure;
this.setData({ 'sexForm.measure': measure });
},
// 爱爱 - 备注输入
onSexRemarkInput: function(e) {
this.setData({ 'sexForm.remark': e.detail.value });
},
// 爱爱 - 确认添加记录
confirmSexAdd: async function() {
var dateStr = this.data.selectedDate;
var form = this.data.sexForm;
if (!form.record_time) {
wx.showToast({ title: '请选择时间', icon: 'none' });
return;
}
if (!form.measure) {
wx.showToast({ title: '请选择措施', icon: 'none' });
return;
}
wx.showLoading({ title: '保存中...', mask: true });
try {
await api.periodSexAdd({
record_date: dateStr,
record_time: form.record_time,
measure: form.measure,
measure_custom: '',
remark: form.remark || ''
});
wx.hideLoading();
// 刷新本地数据
await this._refreshSexRecords(dateStr);
// 添加成功后关闭弹窗
this.setData({ showSexModal: false });
wx.showToast({ title: '添加成功', icon: 'success' });
} catch(e) {
wx.hideLoading();
console.error('添加爱爱记录失败:', e);
wx.showToast({ title: '添加失败', icon: 'none' });
}
},
// 删除每日记录中的单个字段(月经/白带/体温/体重)
deletePeriodField: async function(e) {
var field = e.currentTarget.dataset.field;
var dateStr = this.data.selectedDate;
if (!dateStr) return;
var fieldLabel = { period: '月经记录', discharge: '白带记录', temperature: '体温记录', weight: '体重记录' };
var label = fieldLabel[field] || '该记录';
wx.showModal({
title: '确认删除',
content: '确定删除' + label + '吗?',
success: async (res) => {
if (res.confirm) {
var mapRecord = this.data.dailyRecordsMap[dateStr] || {};
var record = JSON.parse(JSON.stringify(mapRecord));
if (field === 'period') {
record.period = '0';
record.period_detail = null;
} else if (field === 'discharge') {
record.discharge = '';
record.discharge_detail = null;
} else if (field === 'temperature') {
record.temperature = null;
} else if (field === 'weight') {
record.weight = null;
}
this._saveDailyRecord(record);
}
}
});
},
// 爱爱 - 删除记录
deleteSexRecord: async function(e) {
var id = e.currentTarget.dataset.id;
var dateStr = this.data.selectedDate;
wx.showModal({
title: '确认删除',
content: '确定删除这条爱爱记录吗?',
success: async (res) => {
if (res.confirm) {
wx.showLoading({ title: '删除中...', mask: true });
try {
await api.periodSexDelete({ id: id });
wx.hideLoading();
await this._refreshSexRecords(dateStr);
wx.showToast({ title: '已删除', icon: 'success' });
} catch(e) {
wx.hideLoading();
console.error('删除爱爱记录失败:', e);
wx.showToast({ title: '删除失败', icon: 'none' });
}
}
}
});
},
// 爱爱 - 长按日历格子删除
onDayLongPress: function(e) {
var dateStr = e.currentTarget.dataset.date;
var sexMap = this.data.sexRecordsMap;
var records = sexMap[dateStr] || [];
if (records.length === 0) {
wx.showToast({ title: '该日期没有爱爱记录', icon: 'none' });
return;
}
// 显示操作菜单选择要删除的记录
var itemList = records.map(function(r) {
var label = r.record_time ? r.record_time.substring(0, 5) : '未知时间';
label += ' - ' + (r.measure || '未知');
return label;
});
var self = this;
wx.showActionSheet({
itemList: itemList,
success: function(res) {
var idx = res.tapIndex;
var record = records[idx];
wx.showModal({
title: '确认删除',
content: '确定删除这条爱爱记录吗?',
success: async (modalRes) => {
if (modalRes.confirm) {
wx.showLoading({ title: '删除中...', mask: true });
try {
await api.periodSexDelete({ id: record.id });
wx.hideLoading();
await self._refreshSexRecords(dateStr);
wx.showToast({ title: '已删除', icon: 'success' });
} catch(e) {
wx.hideLoading();
console.error('删除爱爱记录失败:', e);
wx.showToast({ title: '删除失败', icon: 'none' });
}
}
}
});
}
});
},
// 爱爱 - 刷新某日期的爱爱记录
_refreshSexRecords: async function(dateStr) {
try {
var res = await api.periodSexList({ record_date: dateStr });
var records = Array.isArray(res) ? res : (res.records || []);
var sexMap = this.data.sexRecordsMap;
sexMap[dateStr] = records;
this.setData({
sexRecords: records,
sexRecordsMap: sexMap,
'sexForm.record_time': this._pad(new Date().getHours()) + ':' + this._pad(new Date().getMinutes()),
'sexForm.measure': '',
'sexForm.remark': ''
});
// 重新生成周期并写入缓存
var newCycles = this._buildDisplayCycles(this.data.dailyRecordsMap, this.data.savedCycles);
this.setData({ cycles: newCycles });
this.calculateStats(newCycles);
// 刷新日历和详情
this.buildCalendar();
this.refreshSelectedDayDetail();
} catch(e) {
console.error('刷新爱爱记录失败:', e);
}
},
// 症状
showSymptomPicker: function() {
if (!this.data.selectedDate) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var current = mapRecord.symptoms || '';
var selected = current ? current.split(',') : [];
var symptomMap = {};
for (var i = 0; i < selected.length; i++) {
symptomMap[selected[i]] = true;
}
this.setData({
showSymptomModal: true,
selectedSymptoms: selected,
symptomMap: symptomMap
});
},
toggleSymptom: function(e) {
var symptom = e.currentTarget.dataset.symptom;
var arr = [].concat(this.data.selectedSymptoms);
var idx = arr.indexOf(symptom);
if (symptom === '无不适') {
this.setData({ selectedSymptoms: ['无不适'], symptomMap: { '无不适': true } });
return;
}
var noIdx = arr.indexOf('无不适');
if (noIdx > -1) arr.splice(noIdx, 1);
if (idx > -1) {
arr.splice(idx, 1);
} else {
arr.push(symptom);
}
var symptomMap = {};
for (var i = 0; i < arr.length; i++) {
symptomMap[arr[i]] = true;
}
this.setData({ selectedSymptoms: arr, symptomMap: symptomMap });
},
confirmSymptoms: function() {
var symptomsStr = this.data.selectedSymptoms.join(',');
// 从完整 map 中获取记录,避免丢失已有字段
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var record = this._cloneRecord(mapRecord);
record.symptoms = symptomsStr;
this.setData({ showSymptomModal: false });
this._saveDailyRecord(record);
},
closeSymptomModal: function() {
this.setData({ showSymptomModal: false });
},
// 心情
showMoodPicker: function() {
this.setData({ showMoodModal: true });
},
selectMood: function(e) {
var mood = e.currentTarget.dataset.mood;
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var record = this._cloneRecord(mapRecord);
record.mood = record.mood === mood ? '' : mood;
this.setData({ showMoodModal: false });
this._saveDailyRecord(record);
},
closeMoodModal: function() {
this.setData({ showMoodModal: false });
},
// 白带
showDischargePicker: function() {
if (!this.data.selectedDate) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var record = this.data.dailyRecord || {};
var dischargeDetail = record.discharge_detail || {};
var abnormalArr = dischargeDetail.abnormal || [];
var abnormalMap = {};
for (var i = 0; i < abnormalArr.length; i++) {
abnormalMap[abnormalArr[i]] = true;
}
this.setData({
showDischargeModal: true,
dischargeForm: {
status: dischargeDetail.status || '',
flow: dischargeDetail.flow || '',
abnormal: abnormalArr
},
dischargeAbnormalMap: abnormalMap
});
},
// 点击选项只切换选中状态,点确定才保存
onDischargeStatus: function(e) {
var val = e.currentTarget.dataset.value;
var form = this.data.dischargeForm;
if (form.status === val) {
form.status = '';
} else {
form.status = val;
}
this.setData({ dischargeForm: form });
},
onDischargeFlow: function(e) {
var val = e.currentTarget.dataset.value;
var form = this.data.dischargeForm;
if (form.flow === val) {
form.flow = '';
} else {
form.flow = val;
}
this.setData({ dischargeForm: form });
},
onDischargeAbnormal: function(e) {
var val = e.currentTarget.dataset.value;
var form = this.data.dischargeForm;
var arr = [].concat(form.abnormal);
var idx = arr.indexOf(val);
if (idx > -1) {
arr.splice(idx, 1);
} else {
arr.push(val);
}
var abnormalMap = {};
for (var i = 0; i < arr.length; i++) {
abnormalMap[arr[i]] = true;
}
this.setData({
dischargeForm: {
status: form.status,
flow: form.flow,
abnormal: arr
},
dischargeAbnormalMap: abnormalMap
});
},
confirmDischargeDetail: function() {
var form = this.data.dischargeForm;
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var record = this._cloneRecord(mapRecord);
var parts = [];
if (form.status) parts.push(form.status);
if (form.flow) parts.push(form.flow);
if (form.abnormal.length > 0) parts.push(form.abnormal.join('、'));
record.discharge = parts.join(' · ') || '';
record.discharge_detail = {
status: form.status,
flow: form.flow,
abnormal: form.abnormal
};
this.setData({ showDischargeModal: false });
this._saveDailyRecord(record);
},
closeDischargeModal: function() {
this.setData({ showDischargeModal: false });
},
// 体温
showTempInput: function() {
if (!this.data.selectedDate) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var val = mapRecord.temperature || '';
this.setData({ showTempModal: true, tempValue: String(val) });
},
onTempInput: function(e) {
this.setData({ tempValue: e.detail.value });
},
confirmTemp: function() {
var val = this.data.tempValue;
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var record = this._cloneRecord(mapRecord);
// 已有体温记录且输入框为空或与原有值相同 → 清除
if (record.temperature && (val === '' || val === undefined || val === null || String(val) === String(record.temperature))) {
record.temperature = null;
this.setData({ showTempModal: false });
this._saveDailyRecord(record);
return;
}
if (val === '' || val === undefined || val === null) {
wx.showToast({ title: '请先输入体温', icon: 'none' });
return;
}
if (isNaN(parseFloat(val))) {
wx.showToast({ title: '请输入有效体温', icon: 'none' });
return;
}
record.temperature = val;
this.setData({ showTempModal: false });
this._saveDailyRecord(record);
},
closeTempModal: function() {
this.setData({ showTempModal: false });
},
// 体重
showWeightInput: function() {
if (!this.data.selectedDate) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var val = mapRecord.weight || '';
this.setData({ showWeightModal: true, weightValue: String(val) });
},
onWeightInput: function(e) {
this.setData({ weightValue: e.detail.value });
},
confirmWeight: function() {
var val = this.data.weightValue;
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var record = this._cloneRecord(mapRecord);
// 已有体重记录且输入框为空或与原有值相同 → 清除
if (record.weight && (val === '' || val === undefined || val === null || String(val) === String(record.weight))) {
record.weight = null;
this.setData({ showWeightModal: false });
this._saveDailyRecord(record);
return;
}
if (val === '' || val === undefined || val === null) {
wx.showToast({ title: '请先输入体重', icon: 'none' });
return;
}
if (isNaN(parseFloat(val))) {
wx.showToast({ title: '请输入有效体重', icon: 'none' });
return;
}
record.weight = val;
this.setData({ showWeightModal: false });
this._saveDailyRecord(record);
},
closeWeightModal: function() {
this.setData({ showWeightModal: false });
},
// 备注
showNoteInput: function() {
if (!this.data.selectedDate) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var val = mapRecord.note || '';
this.setData({ showNoteModal: true, noteValue: val });
},
onNoteInput: function(e) {
this.setData({ noteValue: e.detail.value });
},
confirmNote: function() {
var mapRecord = this.data.dailyRecordsMap[this.data.selectedDate] || {};
var record = this._cloneRecord(mapRecord);
record.note = this.data.noteValue;
this.setData({ showNoteModal: false });
this._saveDailyRecord(record);
},
closeNoteModal: function() {
this.setData({ showNoteModal: false });
},
// ========== 保存每日记录 ==========
_cloneRecord: function(record) {
return JSON.parse(JSON.stringify(record));
},
_saveDailyRecord: async function(record) {
var dateStr = this.data.selectedDate;
if (!dateStr) {
wx.showToast({ title: '请先选择日期', icon: 'none' });
return;
}
// 二次校验:禁止保存未来日期
var today = this._formatDate(new Date());
if (dateStr > today) {
wx.showToast({ title: '无法记录未来的日期', icon: 'none' });
return;
}
record.record_date = dateStr;
wx.showLoading({ title: '保存中...', mask: true });
try {
// 确保 period 字段明确处理:'0'=无, '1'=少量, '2'=中量, '3'=大量
var periodVal = record.period;
if (periodVal === undefined || periodVal === null || periodVal === '') {
periodVal = '0';
} else {
periodVal = String(periodVal);
}
await api.periodDailySave({
record_date: dateStr,
period: periodVal,
sex: '0',
symptoms: record.symptoms || '',
mood: record.mood || '',
discharge: record.discharge || '',
temperature: record.temperature !== undefined && record.temperature !== null && record.temperature !== '' ? String(record.temperature) : null,
weight: record.weight !== undefined && record.weight !== null && record.weight !== '' ? String(record.weight) : null,
note: record.note || '',
period_detail: record.period_detail ? JSON.stringify(record.period_detail) : ''
});
wx.hideLoading();
// 合并已有记录,避免覆盖丢失字段
var map = this.data.dailyRecordsMap;
var existing = map[dateStr] || {};
var merged = {};
// 先复制已有记录的所有字段
for (var k in existing) { merged[k] = existing[k]; }
// 再用新记录覆盖
for (var k in record) { merged[k] = record[k]; }
// 确保 merged.period 是字符串类型
if (merged.period !== undefined && merged.period !== null) {
merged.period = String(merged.period);
}
map[dateStr] = merged;
var sexMap = this.data.sexRecordsMap;
var sexRecords = sexMap[dateStr] || [];
this.setData({
dailyRecord: merged,
dailyRecordsMap: map,
hasDetailContent: !!(parseInt(merged.period) > 0 || sexRecords.length > 0 || merged.symptoms || merged.mood || merged.discharge || merged.temperature || merged.weight || merged.note)
});
// 重新生成周期并刷新统计
var newCycles = this._buildDisplayCycles(map, this.data.savedCycles);
this.setData({ cycles: newCycles });
this.calculateStats(newCycles);
// ★ 将更新后的数据写入本地缓存
this.buildCalendar();
wx.showToast({ title: '保存成功', icon: 'success' });
} catch(e) {
wx.hideLoading();
console.error('保存每日记录失败:', e);
wx.showToast({ title: '保存失败: ' + (e.error || e.errMsg || '未知错误'), icon: 'none' });
}
},
// ========== 姨姨设置弹窗 ==========
showPeriodSettings: async function() {
// 优先读取用户已保存的周期记录(period_records),还原上次保存的开始/结束日期与周期参数
// 修复:保存后再次打开弹窗被重置为默认值的问题
var saved = this.data.savedCycles || [];
var lastSaved = saved.length > 0 ? saved[0] : null;
var startStr;
var endStr;
var cycleLength;
var periodLength;
if (lastSaved && lastSaved.start_date && lastSaved.end_date) {
startStr = lastSaved.start_date;
endStr = lastSaved.end_date;
periodLength = parseInt(lastSaved.period_length) || 5;
cycleLength = parseInt(lastSaved.cycle_length) || this.data.avgCycle || 28;
} else {
// 无历史记录时使用默认值:结束日为今天,开始日 = 结束日 - 持续天数 + 1
periodLength = 5;
var endDate = new Date();
var startDate = new Date();
startDate.setDate(startDate.getDate() - periodLength + 1);
startStr = this._formatDate(startDate);
endStr = this._formatDate(endDate);
cycleLength = this.data.avgCycle || 28;
}
var form = {
start_date: startStr,
end_date: endStr,
cycle_length: cycleLength,
period_length: periodLength,
predicted_start: '',
predicted_end: '',
predicted_ovulation_day: '',
predicted_ovulation_start: '',
predicted_ovulation_end: ''
};
// 立即计算推算结果
form = this._computePrediction(form);
// 加载权限设置(先 await,确保保存时 periodVisible 已是最新值,避免误存 0)
try {
var pv = await api.periodVisibleGet();
if (pv && pv.period_visible !== undefined) {
this.setData({ periodVisible: !!pv.period_visible, hasCouple: !!pv.has_couple });
}
} catch (e) {
console.error('加载权限设置失败:', e);
}
this.setData({
showCycleModal: true,
showFormula: false,
cycleForm: form
});
},
onPeriodVisibleChange: function(e) {
if (this.data.visibilitySaving) return;
var visible = !!e.detail.value;
var oldValue = this.data.periodVisible;
this.setData({ periodVisible: visible, visibilitySaving: true });
api.periodVisibleSet({ period_visible: visible ? 1 : 0 }).then(() => {
this.setData({ visibilitySaving: false });
}).catch(() => {
this.setData({ periodVisible: oldValue, visibilitySaving: false });
wx.showToast({ title: '权限设置失败', icon: 'none' });
});
},
closeCycleModal: function() {
this.setData({ showCycleModal: false, showFormula: false });
},
toggleFormula: function() {
this.setData({ showFormula: !this.data.showFormula });
},
onCycleStartChange: function(e) {
var startDate = e.detail.value;
var form = this.data.cycleForm;
form.start_date = startDate;
// 根据开始日期和持续天数自动计算结束日期
var s = new Date(startDate);
s.setDate(s.getDate() + parseInt(form.period_length) - 1);
form.end_date = this._formatDate(s);
// 更新推算
form = this._computePrediction(form);
this.setData({ cycleForm: form });
},
onCycleEndChange: function(e) {
var endDate = e.detail.value;
var form = this.data.cycleForm;
form.end_date = endDate;
// 根据开始和结束日期自动计算持续天数
var s = new Date(form.start_date);
var e = new Date(endDate);
var diffDays = Math.round((e.getTime() - s.getTime()) / (1000 * 60 * 60 * 24)) + 1;
if (diffDays > 0) {
form.period_length = diffDays;
}
// 更新推算
form = this._computePrediction(form);
this.setData({ cycleForm: form });
},
adjustCycle: function(e) {
var dir = parseInt(e.currentTarget.dataset.dir);
var form = this.data.cycleForm;
form.cycle_length += dir;
if (form.cycle_length < 15) form.cycle_length = 15;
if (form.cycle_length > 60) form.cycle_length = 60;
form = this._computePrediction(form);
this.setData({ cycleForm: form });
},
adjustPeriod: function(e) {
var dir = parseInt(e.currentTarget.dataset.dir);
var form = this.data.cycleForm;
form.period_length += dir;
if (form.period_length < 1) form.period_length = 1;
if (form.period_length > 15) form.period_length = 15;
// 根据开始日期和新的持续天数计算结束日期
var s = new Date(form.start_date);
s.setDate(s.getDate() + parseInt(form.period_length) - 1);
form.end_date = this._formatDate(s);
form = this._computePrediction(form);
this.setData({ cycleForm: form });
},
// 推算下次姨妈信息
_computePrediction: function(form) {
if (!form.start_date) return form;
var start = new Date(form.start_date);
var cycleLen = parseInt(form.cycle_length) || 28;
var periodLen = parseInt(form.period_length) || 5;
// 下次姨妈开始 = 上次来时 + 周期长度
var nextStart = new Date(start);
nextStart.setDate(nextStart.getDate() + cycleLen);
// 下次姨妈结束 = 下次开始 + 持续天数 - 1
var nextEnd = new Date(nextStart);
nextEnd.setDate(nextEnd.getDate() + periodLen - 1);
// 排卵日 = 下次姨妈开始前14天
var ovDay = new Date(nextStart);
ovDay.setDate(ovDay.getDate() - 14);
// 排卵期 = 排卵日前3天 ~ 排卵日后3天
var ovStart = new Date(ovDay);
ovStart.setDate(ovStart.getDate() - 3);
var ovEnd = new Date(ovDay);
ovEnd.setDate(ovEnd.getDate() + 3);
form.predicted_start = this._formatDate(nextStart);
form.predicted_end = this._formatDate(nextEnd);
form.predicted_ovulation_day = this._formatDate(ovDay);
form.predicted_ovulation_start = this._formatDate(ovStart);
form.predicted_ovulation_end = this._formatDate(ovEnd);
return form;
},
saveCycle: async function() {
var fd = this.data.cycleForm;
if (!fd.start_date || !fd.end_date) {
wx.showToast({ title: '请选择日期', icon: 'none' });
return;
}
if (fd.start_date > fd.end_date) {
wx.showToast({ title: '结束日期不能早于开始日期', icon: 'none' });
return;
}
wx.showLoading({ title: '保存中...' });
try {
var saved = this.data.savedCycles || [];
var lastSaved = saved.length > 0 ? saved[0] : null;
var cycleData = {
start_date: fd.start_date,
end_date: fd.end_date,
cycle_length: fd.cycle_length,
period_length: fd.period_length
};
if (lastSaved && lastSaved.id) {
cycleData.id = lastSaved.id;
cycleData.symptoms = lastSaved.symptoms || '';
cycleData.note = lastSaved.note || '';
await api.periodUpdate(cycleData);
} else {
await api.periodAdd(cycleData);
}
wx.hideLoading();
wx.showToast({ title: '保存成功', icon: 'success' });
this.closeCycleModal();
// 等待重新拉取数据并重算周期/安全期后再结束
await this.loadData();
} catch(e) {
wx.hideLoading();
wx.showToast({ title: '保存失败', icon: 'none' });
}
},
onCycleLongPress: function(e) {
var dataset = e.currentTarget.dataset;
var id = dataset.id;
var startDate = dataset.start;
var endDate = dataset.end;
var self = this;
wx.showModal({
title: '删除周期',
content: '确定删除 ' + startDate + ' 至 ' + endDate + ' 的周期记录吗?',
confirmText: '删除',
confirmColor: '#e6437f',
success: async function(res) {
if (!res.confirm) return;
wx.showLoading({ title: '删除中...', mask: true });
try {
await api.periodDelete({
id: id || 0,
start_date: startDate,
end_date: endDate
});
wx.hideLoading();
wx.showToast({ title: '已删除', icon: 'success' });
self.loadData();
} catch (err) {
wx.hideLoading();
wx.showToast({ title: '删除失败', icon: 'none' });
}
}
});
},
closeSexModal: function() {
this.setData({ showSexModal: false });
},
preventMove: function() {},
// ========== 工具 ==========
_periodDetailText: function(detail) {
if (!detail) return '有记录';
var parts = [];
if (detail.amount) parts.push(detail.amount);
if (detail.color) parts.push(detail.color);
if (detail.pain) parts.push(detail.pain);
return parts.join(' · ') || '有记录';
},
_dischargeDetailText: function(detail) {
if (!detail) return '';
var parts = [];
if (detail.status) parts.push(detail.status);
if (detail.flow) parts.push(detail.flow);
if (detail.abnormal && detail.abnormal.length > 0) parts.push(detail.abnormal.join('、'));
return parts.join(' · ') || '';
},
_formatDate: function(date) {
return date.getFullYear() + '-' + this._pad(date.getMonth() + 1) + '-' + this._pad(date.getDate());
},
_pad: function(n) {
return n < 10 ? '0' + n : '' + n;
},
// 已保存的周期设置是推算依据;逐日标记仅补充没有周期记录的手动输入。
_buildDisplayCycles: function(dailyMap, savedCycles) {
var saved = (savedCycles || []).map(function(item) {
return Object.assign({}, item, {
cycle_length: parseInt(item.cycle_length) || 28,
period_length: parseInt(item.period_length) || 5
});
});
saved.sort(function(a, b) { return b.start_date.localeCompare(a.start_date); });
var dailyCycles = this._buildCyclesFromDaily(dailyMap).filter(function(item) {
return !saved.some(function(record) {
return item.start_date <= record.end_date && item.end_date >= record.start_date;
});
});
if (saved.length > 0) {
dailyCycles.forEach(function(item) {
if (item.start_date > saved[0].start_date) {
item.cycle_length = saved[0].cycle_length;
item.period_length = saved[0].period_length;
}
});
}
return saved.concat(dailyCycles).sort(function(a, b) {
return b.start_date.localeCompare(a.start_date);
});
},
choosePeriodSource: function() {
var self = this;
function label(name, preview) {
var count = preview.cycles || 0;
var detail = count ? count + '次周期' : (preview.items || 0) + '条记录';
return name + '(' + detail + (preview.latest ? ',最近' + preview.latest : '') + ')';
}
wx.showActionSheet({
itemList: [label('使用我的记录', this.data.myPreview), label('使用对方记录', this.data.partnerPreview)],
success: function(choice) {
var keepUserId = choice.tapIndex === 0 ? self.data.myUserId : self.data.partnerUserId;
wx.showModal({
title: '确认选择',
content: '选择后,另一方绑定前的姨姨记录会永久删除,无法恢复。确定继续吗?',
confirmColor: '#FF6B9D',
success: async function(result) {
if (!result.confirm) return;
wx.showLoading({ title: '处理中...', mask: true });
try {
await api.periodSourceSelect({ user_id: keepUserId });
await self.loadData();
} catch (err) {
wx.showToast({ title: '选择失败,请重试', icon: 'none' });
await self.loadData();
} finally {
wx.hideLoading();
}
}
});
}
});
},
// ========== 导航 ==========
goBack: function() { wx.navigateBack(); },
goHome: function() { wx.redirectTo({ url: '/pages/index/index' }); },
goProfile: function() { wx.redirectTo({ url: '/pages/profile/profile' }); }
});