document.addEventListener('DOMContentLoaded', function() { // --- Global Variables & State --- let jwtToken = localStorage.getItem('jwtToken'); // Load token from storage let currentUserId = null; // Will be set after login or token validation let milkDataCache = []; // Cache loaded milk data // --- DOM Elements --- const addMilkBtn = document.getElementById('add-milk-btn'); const recordConsumptionBtn = document.getElementById('record-consumption-btn'); const settingsBtn = document.getElementById('settings-btn'); const actionsContainer = document.querySelector('.actions'); const backToDashboardBtn = document.getElementById('back-to-dashboard-btn'); const logoutBtn = document.getElementById('logout-btn'); const addMilkModal = document.getElementById('add-milk-modal'); const recordConsumptionModal = document.getElementById('record-consumption-modal'); const authModal = document.getElementById('auth-modal'); // Auth modal const closeAddModalBtn = document.getElementById('close-add-modal'); const closeRecordModalBtn = document.getElementById('close-record-modal'); const closeAuthModalBtn = document.getElementById('close-auth-modal'); // Auth close const dashboardPage = document.getElementById('dashboard'); const settingsPage = document.getElementById('settings-page'); const addMilkForm = document.getElementById('add-milk-form'); const recordConsumptionForm = document.getElementById('record-consumption-form'); const settingsForm = document.getElementById('settings-form'); const authForm = document.getElementById('auth-form'); // Auth form const loginBtn = document.getElementById('login-btn'); // Login button const registerBtn = document.getElementById('register-btn'); // Register button const milkListContainer = document.getElementById('milk-list'); const noMilkMessage = document.getElementById('no-milk-message'); const consumeDateInput = document.getElementById('consume-date'); const authStatusDiv = document.getElementById('auth-status'); // Auth status display const authErrorP = document.getElementById('auth-error'); // Auth error display // --- API Helper --- async function fetchApi(endpoint, options = {}) { const headers = { 'Content-Type': 'application/json', ...options.headers, // Allow overriding headers }; // Add JWT token if available if (jwtToken) { headers['Authorization'] = `Bearer ${jwtToken}`; } const config = { ...options, headers: headers, }; // Construct full API URL (assuming API is served from the same origin) const url = `/api${endpoint}`; // Prepend /api try { const response = await fetch(url, config); // Handle unauthorized errors (e.g., expired token) if (response.status === 401 || response.status === 422) { // 422 Unprocessable Entity (often used by flask-jwt for missing/bad token) console.error('Authentication error:', response.status); handleLogout(true); // Force logout and show login modal throw new Error('Authentication Required'); // Stop further processing } // Get response body (if any) const responseData = response.headers.get('Content-Type')?.includes('application/json') ? await response.json() // Parse JSON if applicable : null; // Handle non-JSON responses if necessary if (!response.ok) { // Try to get error message from response body const errorMessage = responseData?.error || responseData?.message || `HTTP error ${response.status}`; console.error(`API Error (${response.status}) on ${endpoint}:`, errorMessage, responseData); throw new Error(errorMessage); } console.log(`API Success on ${endpoint}: Status ${response.status}`); return responseData; // Return JSON data or null } catch (error) { console.error(`Workspace failed for ${endpoint}:`, error); // Display error to user? (e.g., a general banner) displayGlobalError(`请求失败: ${error.message}`); throw error; // Re-throw error so calling function knows it failed } } // --- Authentication Functions --- function showLoginModal() { clearAuthForm(); authErrorP.style.display = 'none'; openModal(authModal); } function clearAuthForm() { const usernameInput = document.getElementById('auth-username'); const passwordInput = document.getElementById('auth-password'); if (usernameInput) usernameInput.value = ''; if (passwordInput) passwordInput.value = ''; } async function handleLogin(event) { event?.preventDefault(); // Prevent default if called from button click const username = document.getElementById('auth-username').value; const password = document.getElementById('auth-password').value; authErrorP.style.display = 'none'; if (!username || !password) { displayAuthError('请输入用户名和密码'); return; } try { const data = await fetchApi('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }); if (data && data.access_token) { jwtToken = data.access_token; localStorage.setItem('jwtToken', jwtToken); console.log('Login successful'); closeModal(authModal); initializeApp(); // Reload data and update UI for logged-in user } else { displayAuthError('登录失败,请检查凭证'); } } catch (error) { // Error display handled by fetchApi or display specific message displayAuthError(`登录失败: ${error.message}`); } } async function handleRegister(event) { event?.preventDefault(); const username = document.getElementById('auth-username').value; const password = document.getElementById('auth-password').value; authErrorP.style.display = 'none'; if (!username || !password) { displayAuthError('请输入用户名和密码'); return; } if (password.length < 4) { // Example basic validation displayAuthError('密码长度至少需要4位'); return; } try { await fetchApi('/auth/register', { method: 'POST', body: JSON.stringify({ username, password }), }); // Registration successful, now attempt login alert('注册成功!请现在登录。'); // Maybe prefill username? document.getElementById('auth-password').value = ''; // Clear password // Or automatically log in: await handleLogin(); } catch (error) { displayAuthError(`注册失败: ${error.message}`); } } function handleLogout(showLogin = false) { jwtToken = null; currentUserId = null; localStorage.removeItem('jwtToken'); console.log('Logged out'); // Reset UI milkListContainer.innerHTML = ''; noMilkMessage.textContent = '请先登录以查看或添加牛奶记录。'; noMilkMessage.style.display = 'block'; updateOverview('-- L / -- 盒', '--', '--'); updateAuthStatus(); document.getElementById('settings-form').reset(); // Clear settings form hideMainContent(); // Hide main buttons/content if logged out if (showLogin) { showLoginModal(); } } function updateAuthStatus() { if (jwtToken) { // Try to get username if needed (decode token client-side - simple way) // Or make a quick /api/user/profile call, but might fail if token JUST expired let username = '已登录'; // Placeholder try { // Basic decoding (assumes standard JWT structure) - NOT FOR VERIFICATION const payload = JSON.parse(atob(jwtToken.split('.')[1])); currentUserId = payload.sub; // 'sub' usually holds the user ID/identity // Fetch username from profile API if needed for display username = `用户 ${currentUserId}`; // Placeholder until profile loaded fetchApi('/user/profile').then(profile => { if(profile && profile.username){ authStatusDiv.textContent = `已登录: ${profile.username}`; } }).catch(() => {}); // Ignore errors here, keep placeholder } catch (e) { console.error("Error decoding token:", e); } authStatusDiv.textContent = username; logoutBtn.style.display = 'inline-block'; showMainContent(); } else { authStatusDiv.textContent = '未登录'; logoutBtn.style.display = 'none'; hideMainContent(); // Optionally show login modal immediately if no token on load // showLoginModal(); } } function hideMainContent(){ // Hide elements that require login if(actionsContainer) { actionsContainer.style.display = 'none'; // <-- 修改这里,隐藏容器 } // (下面几行也可以删掉) // if(addMilkBtn) addMilkBtn.style.display = 'none'; // if(recordConsumptionBtn) recordConsumptionBtn.style.display = 'none'; // if(settingsBtn) settingsBtn.style.display = 'none'; // Could also hide the summary container etc. } function showMainContent(){ // Show elements after login if(actionsContainer) { actionsContainer.style.display = 'flex'; // <-- 修改这里,显示容器 (用 flex 因为 CSS 里是 flex 布局) } // if(addMilkBtn) addMilkBtn.style.display = 'inline-block'; // if(recordConsumptionBtn) recordConsumptionBtn.style.display = 'inline-block'; // if(settingsBtn) settingsBtn.style.display = 'inline-block'; } function displayAuthError(message) { authErrorP.textContent = message; authErrorP.style.display = 'block'; } function displayGlobalError(message){ // Implement a more robust global error display if needed console.error("Global Error:", message); alert(`操作失败: ${message}`); // Simple alert for now } // --- Core Data Loading & Rendering --- async function loadMilkData() { console.log("Fetching milk data and overview..."); if (!jwtToken) { console.log("No token found, skipping data load."); noMilkMessage.textContent = '请先登录以查看或添加牛奶记录。'; noMilkMessage.style.display = 'block'; milkListContainer.innerHTML = ''; updateOverview('-- L / -- 盒', '--', '--'); return; } try { // Fetch overview and milk list in parallel const [overviewData, milkBatches] = await Promise.all([ fetchApi('/overview'), fetchApi('/milk?onlyActive=false') // Get all non-deleted batches initially ]); milkDataCache = milkBatches || []; // Store fetched data // Update Overview Section if (overviewData) { const nearest = overviewData.nearestExpiry; const nearestStr = nearest ? `${nearest.note || '牛奶'} - ${nearest.expiryDate} (${nearest.daysRemaining}天)` : '无临期牛奶'; updateOverview( `${overviewData.totalQuantityLiters} L / ${overviewData.totalQuantityItemsApprox} 盒`, nearestStr, overviewData.estimatedFinishDate || '--' ); } else { updateOverview('-- L / -- 盒', '--', '--'); } // Render Milk List milkListContainer.innerHTML = ''; // Clear existing list if (milkBatches && milkBatches.length > 0) { noMilkMessage.style.display = 'none'; milkBatches.sort((a, b) => new Date(a.expiryDate) - new Date(b.expiryDate)); // Ensure sorted milkBatches.forEach(renderMilkCard); populateConsumeDropdown(milkBatches.filter(b => b.remainingVolume > 0.001 && b.status !== 'expired')); // Populate with active, non-expired } else { noMilkMessage.textContent = '还没有添加牛奶记录哦。'; noMilkMessage.style.display = 'block'; populateConsumeDropdown([]); // Clear dropdown } } catch (error) { console.error("Failed to load initial data:", error); noMilkMessage.textContent = '加载牛奶数据失败,请稍后重试。'; noMilkMessage.style.display = 'block'; milkListContainer.innerHTML = ''; updateOverview('错误', '错误', '错误'); // Error should have been handled by fetchApi, maybe logout occurred } } function renderMilkCard(milk) { const card = document.createElement('article'); card.className = `milk-card ${milk.status || 'normal'}`; // 'normal', 'warning', 'expired' card.dataset.id = milk.id; const progressValue = milk.initialTotalVolume > 0 ? (milk.remainingVolume / milk.initialTotalVolume) * 100 : 0; let expiryText; if (milk.daysRemaining === undefined || milk.daysRemaining === null){ expiryText = `过期: ${milk.expiryDate}`; } else if (milk.daysRemaining < 0){ expiryText = `已过期 ${Math.abs(milk.daysRemaining)} 天 (${milk.expiryDate})`; } else if (milk.daysRemaining === 0){ expiryText = `今天过期 (${milk.expiryDate})`; } else { expiryText = `剩 ${milk.daysRemaining} 天 (${milk.expiryDate})`; } card.innerHTML = `