feat: 饮食建议页日期选择改为周日历卡片+月历弹窗,优化交互体验

This commit is contained in:
liuyunqin
2026-07-31 16:43:18 +08:00
parent a1be3ce138
commit e8fd8fea5b
+276 -134
View File
@@ -15,32 +15,46 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; }
.nav-back { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #1a1a1a; }
.nav-title { flex: 1; text-align: center; font-size: 17px; font-weight: 600; color: #1a1a1a; }
.nav-date { display: flex; align-items: center; gap: 3px; cursor: pointer; user-select: none; padding: 4px 6px; border-radius: 6px; transition: background .2s; }
.nav-date:hover { background: #f5f5f5; }
.nav-date__text { font-size: 13px; color: #666; font-weight: 500; }
.nav-date__arrow { transition: transform .25s; color: #999; }
.nav-date__arrow.open { transform: rotate(180deg); }
/* Calendar dropdown */
.calendar-dropdown { background: #fff; border-bottom: 1px solid #f0f0f0; overflow: hidden; max-height: 0; transition: max-height .3s ease, padding .3s ease; }
.calendar-dropdown.show { max-height: 370px; padding: 8px 16px 12px; }
.calendar-dropdown__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.calendar-dropdown__nav { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #999; border-radius: 50%; font-size: 14px; user-select: none; }
.calendar-dropdown__nav:hover { background: #f0f0f0; }
.calendar-dropdown__month { font-size: 14px; font-weight: 700; color: #1a1a1a; }
.calendar-dropdown__weekdays { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; font-size: 11px; color: #bbb; margin-bottom: 4px; }
.calendar-dropdown__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; text-align: center; }
.calendar-dropdown__day { padding: 7px 0; font-size: 13px; color: #333; border-radius: 8px; cursor: pointer; font-weight: 500; position: relative; transition: background .15s; }
.calendar-dropdown__day:hover { background: #f5f5f5; }
.calendar-dropdown__day--other { color: #ddd; pointer-events: none; }
.calendar-dropdown__day--today { border: 1.5px solid #36cfc9; color: #36cfc9; font-weight: 700; }
.calendar-dropdown__day--selected { background: #36cfc9; color: #fff; font-weight: 700; }
.calendar-dropdown__day--has-data::after { content: ''; position: absolute; bottom: 2px; left: 50%; transform: translateX(-50%); width: 4px; height: 4px; border-radius: 50%; background: #36cfc9; }
.calendar-dropdown__day--selected.calendar-dropdown__day--has-data::after { background: #fff; }
.calendar-dropdown__legend { display: flex; align-items: center; gap: 12px; margin-top: 8px; font-size: 11px; color: #bbb; }
.calendar-dropdown__legend-dot { width: 5px; height: 5px; border-radius: 50%; background: #36cfc9; }
.calendar-dropdown__legend-dot--none { background: #e0e0e0; }
.nav-title { flex: 1; text-align: center; font-size: 17px; font-weight: 600; color: #1a1a1a; }
.nav-placeholder { width: 28px; height: 28px; }
/* ===== 周日历(页面顶部) ===== */
.calendar-card { background: #fff; border-radius: 16px; margin: 10px 12px 0; padding: 12px 12px 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); position: relative; }
.calendar-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.calendar-card__arrow { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #999; border-radius: 50%; transition: background .2s; user-select: none; }
.calendar-card__arrow:hover { background: #f0f0f0; color: #333; }
.calendar-card__range { font-size: 13px; font-weight: 600; color: #333; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; user-select: none; }
.calendar-card__range svg { flex-shrink: 0; }
.calendar-card__date-arrow { transition: transform .2s; }
.calendar-card__date-arrow.open { transform: rotate(180deg); }
/* 月历弹窗 */
.month-picker { position: absolute; top: 38px; left: 12px; right: 12px; z-index: 20; background: #fff; border-radius: 14px; padding: 12px 14px; box-shadow: 0 8px 30px rgba(0,0,0,0.15); display: none; }
.month-picker.show { display: block; }
.month-picker__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.month-picker__title { font-size: 14px; font-weight: 700; color: #1a1a1a; }
.month-picker__nav { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #999; border-radius: 50%; font-size: 13px; }
.month-picker__nav:hover { background: #f0f0f0; }
.month-picker__weekdays { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; font-size: 11px; color: #bbb; margin-bottom: 6px; }
.month-picker__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; text-align: center; }
.month-picker__day { padding: 6px 0; font-size: 13px; color: #333; border-radius: 8px; cursor: pointer; font-weight: 500; }
.month-picker__day:hover { background: #f5f5f5; }
.month-picker__day--other { color: #ddd; }
.month-picker__day--today { background: #e8fbfa; color: #36cfc9; font-weight: 700; }
.month-picker__day--selected { background: #36cfc9; color: #fff; font-weight: 700; }
.cal-week { display: flex; gap: 4px; }
.cal-day { flex: 1; text-align: center; padding: 6px 0; border-radius: 10px; cursor: pointer; background: #f7f8fa; transition: background .2s; }
.cal-day.active { background: #e8fbfa; }
.cal-day.today .cal-day__num { color: #1a1a1a; font-weight: 800; }
.cal-day.active .cal-day__num { color: #36cfc9; font-weight: 800; }
.cal-day.active .cal-day__wd { color: #36cfc9; font-weight: 600; }
.cal-day__wd { font-size: 10px; color: #999; margin-bottom: 3px; }
.cal-day__date-wrap { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0 auto; }
.cal-day__num { font-size: 14px; font-weight: 700; color: #333; position: relative; z-index: 2; }
.cal-day__ring { position: absolute; inset: 0; border-radius: 50%; border: 2.5px solid #36cfc9; z-index: 1; opacity: 0.35; }
.cal-day.active .cal-day__ring { opacity: 1; }
.cal-day.no-data { opacity: 0.4; }
/* Tabs */
.tab-bar { display: flex; background: #fff; margin: 10px 12px 0; border-radius: 12px; padding: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.03); }
@@ -260,35 +274,46 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<div class="nav-bar">
<div class="nav-back" onclick="goBack()">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
</div>
<div class="nav-title">今日饮食建议</div>
<div class="nav-date" onclick="toggleCalendar()">
<span class="nav-date__text" id="navDateText">2026-07-23</span>
<svg class="nav-date__arrow" id="navDateArrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<!-- Calendar dropdown -->
<div class="calendar-dropdown" id="calendarDropdown">
<div class="calendar-dropdown__header">
<div class="calendar-dropdown__nav" onclick="event.stopPropagation();prevCalMonth()"></div>
<span class="calendar-dropdown__month" id="calMonthTitle">2026年7月</span>
<div class="calendar-dropdown__nav" onclick="event.stopPropagation();nextCalMonth()"></div>
</div>
<div class="calendar-dropdown__weekdays">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="calendar-dropdown__grid" id="calMonthGrid"></div>
<div class="calendar-dropdown__legend">
<span class="calendar-dropdown__legend-dot"></span><span>已记录</span>
<span class="calendar-dropdown__legend-dot calendar-dropdown__legend-dot--none"></span><span>未记录</span>
</div>
</div>
<!-- Tabs -->
<div class="nav-bar">
<div class="nav-back" onclick="goBack()">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
</div>
<div class="nav-title">今日饮食建议</div>
<div class="nav-placeholder"></div>
</div>
<!-- ===== 周日历(页面顶部) ===== -->
<div class="calendar-card">
<div class="calendar-card__header">
<div class="calendar-card__arrow" onclick="prevWeek()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
</div>
<span class="calendar-card__range" onclick="toggleMonthPicker()">
<span id="calRange">2026年7月23日</span>
<svg class="calendar-card__date-arrow" id="dateArrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</span>
<div class="calendar-card__arrow" id="calNext" onclick="nextWeek()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
</div>
</div>
<!-- 月历弹窗 -->
<div class="month-picker" id="monthPicker">
<div class="month-picker__header">
<div class="month-picker__nav" onclick="event.stopPropagation();prevMonth()"></div>
<span class="month-picker__title" id="monthPickerTitle">2026年7月</span>
<div class="month-picker__nav" onclick="event.stopPropagation();nextMonth()"></div>
</div>
<div class="month-picker__weekdays">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="month-picker__grid" id="monthGrid"></div>
</div>
<div class="cal-week" id="calWeek"></div>
</div>
<!-- Tabs -->
<div class="tab-bar">
<div class="tab-bar__item active" data-tab="recipe" onclick="switchTab('recipe')">食谱建议</div>
<div class="tab-bar__item" data-tab="nutrition" onclick="switchTab('nutrition')">营养分析</div>
@@ -398,9 +423,201 @@ function goBack() {
/* ===== Core data ===== */
var todayStr = '2026-07-23';
var selectedDate = todayStr;
var todayDate = new Date(2026, 6, 23); // 2026-07-23
/* ===== 周导航(页面顶部周日历) ===== */
var WEEK_MS = 7 * 24 * 60 * 60 * 1000;
var weekDayLabels = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
var currentWeekStart = getMonday(todayDate);
function getMonday(d) {
var m = new Date(d);
var day = m.getDay();
var diff = day === 0 ? -6 : 1 - day;
m.setDate(m.getDate() + diff);
m.setHours(0, 0, 0, 0);
return m;
}
function fmtDate(d) {
var y = d.getFullYear();
var m = ('0' + (d.getMonth() + 1)).slice(-2);
var day = ('0' + d.getDate()).slice(-2);
return y + '-' + m + '-' + day;
}
function isSameDay(a, b) {
return a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate();
}
/* ===== 渲染周日历 ===== */
function renderWeek() {
var container = document.getElementById('calWeek');
container.innerHTML = '';
var weekDayChars = ['日', '一', '二', '三', '四', '五', '六'];
for (var i = 0; i < 7; i++) {
var d = new Date(currentWeekStart);
d.setDate(d.getDate() + i);
var dateStr = fmtDate(d);
var wd = weekDayChars[d.getDay()];
var dayNum = d.getDate();
var isToday = isSameDay(d, todayDate);
var hasData = dayData[dateStr] && dayData[dateStr].intake > 0;
var dayEl = document.createElement('div');
dayEl.className = 'cal-day' + (isToday ? ' today' : '') + (!hasData ? ' no-data' : '');
dayEl.setAttribute('data-date', dateStr);
dayEl.onclick = function() { selectDay(this); };
dayEl.innerHTML =
'<div class="cal-day__wd">' + wd + '</div>' +
'<div class="cal-day__date-wrap">' +
(hasData ? '<div class="cal-day__ring"></div>' : '') +
'<span class="cal-day__num">' + dayNum + '</span>' +
'</div>';
container.appendChild(dayEl);
}
// 箭头状态:未来周禁止前进
var mondayOfThisWeek = getMonday(todayDate);
var nextArrow = document.getElementById('calNext');
if (currentWeekStart.getTime() >= mondayOfThisWeek.getTime()) {
nextArrow.style.opacity = '0.3';
nextArrow.style.pointerEvents = 'none';
} else {
nextArrow.style.opacity = '1';
nextArrow.style.pointerEvents = '';
}
// 选中当前日期(如果在本周内),否则 fallback
var selParts = selectedDate.split('-');
var selD = new Date(parseInt(selParts[0]), parseInt(selParts[1]) - 1, parseInt(selParts[2]));
var selMonday = getMonday(selD);
var target = null;
if (selMonday.getTime() === currentWeekStart.getTime()) {
target = container.querySelector('.cal-day[data-date="' + selectedDate + '"]');
}
if (!target) {
target = container.querySelector('.cal-day.today');
if (!target) target = container.querySelector('.cal-day:not(.no-data)');
if (!target) target = container.querySelector('.cal-day');
}
if (target) selectDay(target, true);
}
function prevWeek() {
currentWeekStart = new Date(currentWeekStart.getTime() - WEEK_MS);
renderWeek();
}
function nextWeek() {
var mondayOfThisWeek = getMonday(todayDate);
if (currentWeekStart.getTime() >= mondayOfThisWeek.getTime()) return;
currentWeekStart = new Date(currentWeekStart.getTime() + WEEK_MS);
renderWeek();
}
function selectDay(el, skipRender) {
document.querySelectorAll('.cal-day').forEach(function(d) { d.classList.remove('active'); });
el.classList.add('active');
var newDate = el.getAttribute('data-date');
if (newDate === selectedDate && !skipRender) return; // 同一日期不重复渲染
selectedDate = newDate;
var parts = selectedDate.split('-');
document.getElementById('calRange').textContent = parts[0] + '年' + parseInt(parts[1]) + '月' + parseInt(parts[2]) + '日';
renderAll();
}
/* ===== 月历弹窗 ===== */
var pickerYear = 2026;
var pickerMonth = 6;
function toggleMonthPicker() {
var picker = document.getElementById('monthPicker');
var arrow = document.getElementById('dateArrow');
var show = !picker.classList.contains('show');
picker.classList.toggle('show', show);
arrow.classList.toggle('open', show);
if (show) {
pickerYear = todayDate.getFullYear();
pickerMonth = todayDate.getMonth();
renderMonthGrid();
}
}
function prevMonth() {
if (pickerMonth === 0) { pickerMonth = 11; pickerYear--; }
else { pickerMonth--; }
renderMonthGrid();
}
function nextMonth() {
if (pickerMonth === 11) { pickerMonth = 0; pickerYear++; }
else { pickerMonth++; }
renderMonthGrid();
}
function renderMonthGrid() {
document.getElementById('monthPickerTitle').textContent = pickerYear + '年' + (pickerMonth + 1) + '月';
var grid = document.getElementById('monthGrid');
grid.innerHTML = '';
var firstDay = new Date(pickerYear, pickerMonth, 1).getDay();
var daysInMonth = new Date(pickerYear, pickerMonth + 1, 0).getDate();
var prevDays = new Date(pickerYear, pickerMonth, 0).getDate();
for (var i = firstDay - 1; i >= 0; i--) {
var el = document.createElement('div');
el.className = 'month-picker__day month-picker__day--other';
el.textContent = prevDays - i;
grid.appendChild(el);
}
var todayDateStr = fmtDate(todayDate);
for (var d = 1; d <= daysInMonth; d++) {
var dateStr = pickerYear + '-' + ('0' + (pickerMonth + 1)).slice(-2) + '-' + ('0' + d).slice(-2);
var el = document.createElement('div');
var cls = 'month-picker__day';
if (dateStr === todayDateStr) cls += ' month-picker__day--today';
if (dateStr === selectedDate) cls += ' month-picker__day--selected';
el.className = cls;
el.textContent = d;
el.onclick = (function(ds) { return function(e) { e.stopPropagation(); pickDay(ds); }; })(dateStr);
grid.appendChild(el);
}
}
function pickDay(dateStr) {
document.getElementById('monthPicker').classList.remove('show');
document.getElementById('dateArrow').classList.remove('open');
var parts = dateStr.split('-');
var d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
currentWeekStart = getMonday(d);
selectedDate = dateStr;
renderWeek();
}
document.addEventListener('click', function(e) {
var picker = document.getElementById('monthPicker');
if (!picker.classList.contains('show')) return;
if (!e.target.closest('.calendar-card__range') && !e.target.closest('#monthPicker')) {
picker.classList.remove('show');
document.getElementById('dateArrow').classList.remove('open');
}
});
var dayData = {
'2026-07-20': {
'2026-07-20': {
intake: 1050, summary: '今日热量摄入合理,营养素搭配均衡。早餐提供了充足蛋白质,午餐以碳水为主,晚餐清淡适中。',
meals: {
breakfast: { kcal: 280, time: '07:30', items: [
@@ -513,83 +730,6 @@ var dayData = {
'2026-07-26': null
};
/* ===== Calendar logic ===== */
var calYear = 2026;
var calMonth = 6;
function toggleCalendar() {
var dd = document.getElementById('calendarDropdown');
var arrow = document.getElementById('navDateArrow');
var show = !dd.classList.contains('show');
dd.classList.toggle('show', show);
arrow.classList.toggle('open', show);
if (show) {
var parts = selectedDate.split('-');
calYear = parseInt(parts[0]);
calMonth = parseInt(parts[1]) - 1;
renderCalGrid();
}
}
function prevCalMonth() {
if (calMonth === 0) { calMonth = 11; calYear--; }
else calMonth--;
renderCalGrid();
}
function nextCalMonth() {
if (calMonth === 11) { calMonth = 0; calYear++; }
else calMonth++;
renderCalGrid();
}
function renderCalGrid() {
document.getElementById('calMonthTitle').textContent = calYear + '年' + (calMonth + 1) + '月';
var grid = document.getElementById('calMonthGrid');
grid.innerHTML = '';
var firstDay = new Date(calYear, calMonth, 1).getDay();
var daysInMonth = new Date(calYear, calMonth + 1, 0).getDate();
var prevDays = new Date(calYear, calMonth, 0).getDate();
for (var i = firstDay - 1; i >= 0; i--) {
var el = document.createElement('div');
el.className = 'calendar-dropdown__day calendar-dropdown__day--other';
el.textContent = prevDays - i;
grid.appendChild(el);
}
for (var d = 1; d <= daysInMonth; d++) {
var ds = calYear + '-' + ('0' + (calMonth + 1)).slice(-2) + '-' + ('0' + d).slice(-2);
var el = document.createElement('div');
var cls = 'calendar-dropdown__day';
if (ds === todayStr) cls += ' calendar-dropdown__day--today';
if (ds === selectedDate) cls += ' calendar-dropdown__day--selected';
if (dayData[ds] && dayData[ds].intake > 0) cls += ' calendar-dropdown__day--has-data';
el.className = cls;
el.textContent = d;
el.onclick = (function(ds) { return function(e) { e.stopPropagation(); pickCalDay(ds); }; })(ds);
grid.appendChild(el);
}
}
function pickCalDay(ds) {
document.getElementById('calendarDropdown').classList.remove('show');
document.getElementById('navDateArrow').classList.remove('open');
selectedDate = ds;
document.getElementById('navDateText').textContent = ds;
renderAll();
}
document.addEventListener('click', function(e) {
var dd = document.getElementById('calendarDropdown');
if (!dd.classList.contains('show')) return;
if (!e.target.closest('.nav-date') && !e.target.closest('#calendarDropdown')) {
dd.classList.remove('show');
document.getElementById('navDateArrow').classList.remove('open');
}
});
/* ===== Toggle summary collapse ===== */
function toggleSummary() {
var body = document.getElementById('summaryBody');
@@ -1130,7 +1270,9 @@ function renderNutrientIntake(data) {
}
/* ===== Init ===== */
document.getElementById('navDateText').textContent = selectedDate;
var initParts = selectedDate.split('-');
document.getElementById('calRange').textContent = initParts[0] + '年' + parseInt(initParts[1]) + '月' + parseInt(initParts[2]) + '日';
renderWeek();
renderAll();
</script>
</body>