chore: update code 周一-2025-11 17:11

This commit is contained in:
赵杰 Jie Zhao (雄狮汽车科技)
2025-11-03 17:11:28 +08:00
parent 37fb88b042
commit 20bbda37e9
4 changed files with 129 additions and 99 deletions

View File

@@ -1,8 +1,8 @@
// 分析功能脚本 // 营养分析功能脚本
let currentUserId = null; let currentUserId = null;
// DOM元素 // DOM元素
const loginSection = document.getElementById('loginSection'); const loginSection = document.getElementById('loginSection');
const requestSection = document.getElementById('requestSection'); const requestSection = document.getElementById('requestSection');
const analysisSection = document.getElementById('analysisSection'); const analysisSection = document.getElementById('analysisSection');
@@ -14,27 +14,28 @@ const analyzeBtn = document.getElementById('analyzeBtn');
const mealDataTextarea = document.getElementById('mealData'); const mealDataTextarea = document.getElementById('mealData');
const analysisResult = document.getElementById('analysisResult'); const analysisResult = document.getElementById('analysisResult');
// 显示消息 // 显示消息
function showMessage(message, type = 'info') { function showMessage(message, type = 'info') {
messageArea.textContent = message; messageArea.textContent = message;
messageArea.className = `message-area ${type}`; messageArea.className = `message-area ${type}`;
messageArea.style.display = 'block';
setTimeout(() => { setTimeout(() => {
messageArea.className = 'message-area'; messageArea.className = 'message-area';
messageArea.style.display = 'none'; messageArea.style.display = 'none';
}, 3000); }, 3000);
} }
// 用户登录 // 用户登录
loginBtn.addEventListener('click', async () => { loginBtn.addEventListener('click', async () => {
const userId = userIdInput.value.trim(); const userId = userIdInput.value.trim();
if (!userId) { if (!userId) {
showMessage('请输入用户ID', 'error'); showMessage('请输入用户ID', 'error');
return; return;
} }
loginBtn.disabled = true; loginBtn.disabled = true;
loginBtn.textContent = '登录中...'; loginBtn.textContent = '登录中...';
try { try {
const response = await fetch('/api/user/login', { const response = await fetch('/api/user/login', {
@@ -53,30 +54,30 @@ loginBtn.addEventListener('click', async () => {
currentUserId = userId; currentUserId = userId;
loginSection.style.display = 'none'; loginSection.style.display = 'none';
requestSection.style.display = 'block'; requestSection.style.display = 'block';
showMessage(`欢迎,${data.name || userId}`, 'success'); showMessage(`欢迎,${data.name || userId}`, 'success');
} else { } else {
showMessage(data.message || '登录失败', 'error'); showMessage(data.message || '登录失败', 'error');
} }
} catch (error) { } catch (error) {
console.error('登录失败:', error); console.error('登录失败:', error);
showMessage('登录失败,请检查网络连接', 'error'); showMessage('登录失败,请检查网络连接', 'error');
} finally { } finally {
loginBtn.disabled = false; loginBtn.disabled = false;
loginBtn.textContent = '登录'; loginBtn.textContent = '登录';
} }
}); });
// 开始分析 // 开始分析
analyzeBtn.addEventListener('click', async () => { analyzeBtn.addEventListener('click', async () => {
const mealDataText = mealDataTextarea.value.trim(); const mealDataText = mealDataTextarea.value.trim();
if (!mealDataText) { if (!mealDataText) {
showMessage('请输入餐食信息', 'error'); showMessage('请输入餐食信息', 'error');
return; return;
} }
analyzeBtn.disabled = true; analyzeBtn.disabled = true;
analyzeBtn.textContent = '分析中...'; analyzeBtn.textContent = '分析中...';
try { try {
const response = await fetch('/api/analysis/nutrition', { const response = await fetch('/api/analysis/nutrition', {
@@ -97,37 +98,47 @@ analyzeBtn.addEventListener('click', async () => {
if (data.success && data.analysis) { if (data.success && data.analysis) {
displayAnalysis(data.analysis); displayAnalysis(data.analysis);
analysisSection.style.display = 'block'; analysisSection.style.display = 'block';
showMessage('分析完成!', 'success'); showMessage('分析完成!', 'success');
} else { } else {
showMessage(data.message || '分析失败', 'error'); showMessage(data.message || '分析失败', 'error');
} }
} catch (error) { } catch (error) {
console.error('分析失败:', error); console.error('分析失败:', error);
showMessage('分析失败,请检查网络连接', 'error'); showMessage('分析失败,请检查网络连接', 'error');
} finally { } finally {
analyzeBtn.disabled = false; analyzeBtn.disabled = false;
analyzeBtn.textContent = '开始分析'; analyzeBtn.textContent = '开始分析';
} }
}); });
// 显示分析结果 // 显示分析结果
function displayAnalysis(analysis) { function displayAnalysis(analysis) {
analysisResult.innerHTML = ''; analysisResult.innerHTML = '';
if (typeof analysis === 'string') { if (typeof analysis === 'string') {
// 如果是字符串,直接显示 // 如果是字符串,直接显示
analysisResult.innerHTML = `<div class="analysis-section"><p>${analysis.replace(/\n/g, '<br>')}</p></div>`; analysisResult.innerHTML = `<div class="analysis-section"><p>${escapeHtml(analysis).replace(/\n/g, '<br>')}</p></div>`;
} else if (analysis.analysis) { } else if (analysis.analysis) {
// 如果包含analysis字段 // 如果analysis字段
analysisResult.innerHTML = `<div class="analysis-section"><h4>分析结果</h4><p>${analysis.analysis.replace(/\n/g, '<br>')}</p></div>`; analysisResult.innerHTML = `<div class="analysis-section"><h4>营养分析</h4><p>${escapeHtml(analysis.analysis).replace(/\n/g, '<br>')}</p></div>`;
} else { } else {
// 如果是对象,格式化显示 // 如果是对象,格式化显示
for (const [key, value] of Object.entries(analysis)) { for (const [key, value] of Object.entries(analysis)) {
const section = document.createElement('div'); const section = document.createElement('div');
section.className = 'analysis-section'; section.className = 'analysis-section';
section.innerHTML = `<h4>${key}</h4><p>${typeof value === 'string' ? value.replace(/\n/g, '<br>') : JSON.stringify(value, null, 2)}</p>`; const displayKey = escapeHtml(key);
const displayValue = typeof value === 'string'
? escapeHtml(value).replace(/\n/g, '<br>')
: escapeHtml(JSON.stringify(value, null, 2));
section.innerHTML = `<h4>${displayKey}</h4><p>${displayValue}</p>`;
analysisResult.appendChild(section); analysisResult.appendChild(section);
} }
} }
} }
// HTML转义函数防止XSS攻击
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

View File

@@ -1,8 +1,8 @@
// 数据采集功能脚本 // 数据采集功能脚本
let currentUserId = null; let currentUserId = null;
// DOM元素 // DOM元素
const loginSection = document.getElementById('loginSection'); const loginSection = document.getElementById('loginSection');
const basicQuestionnaire = document.getElementById('basicQuestionnaire'); const basicQuestionnaire = document.getElementById('basicQuestionnaire');
const mealRecord = document.getElementById('mealRecord'); const mealRecord = document.getElementById('mealRecord');
@@ -15,28 +15,29 @@ const userNameInput = document.getElementById('userName');
const submitBasicBtn = document.getElementById('submitBasicBtn'); const submitBasicBtn = document.getElementById('submitBasicBtn');
const submitMealBtn = document.getElementById('submitMealBtn'); const submitMealBtn = document.getElementById('submitMealBtn');
// 显示消息 // 显示消息
function showMessage(message, type = 'info') { function showMessage(message, type = 'info') {
messageArea.textContent = message; messageArea.textContent = message;
messageArea.className = `message-area ${type}`; messageArea.className = `message-area ${type}`;
messageArea.style.display = 'block';
setTimeout(() => { setTimeout(() => {
messageArea.className = 'message-area'; messageArea.className = 'message-area';
messageArea.style.display = 'none'; messageArea.style.display = 'none';
}, 3000); }, 3000);
} }
// 用户登录 // 用户登录/注册
loginBtn.addEventListener('click', async () => { loginBtn.addEventListener('click', async () => {
const userId = userIdInput.value.trim(); const userId = userIdInput.value.trim();
const userName = userNameInput.value.trim(); const userName = userNameInput.value.trim();
if (!userId || !userName) { if (!userId || !userName) {
showMessage('请输入用户ID和姓名', 'error'); showMessage('请输入用户ID和姓名', 'error');
return; return;
} }
loginBtn.disabled = true; loginBtn.disabled = true;
loginBtn.textContent = '登录中...'; loginBtn.textContent = '登录中...';
try { try {
const response = await fetch('/api/user/register', { const response = await fetch('/api/user/register', {
@@ -57,20 +58,20 @@ loginBtn.addEventListener('click', async () => {
loginSection.style.display = 'none'; loginSection.style.display = 'none';
basicQuestionnaire.style.display = 'block'; basicQuestionnaire.style.display = 'block';
mealRecord.style.display = 'block'; mealRecord.style.display = 'block';
showMessage('登录成功!', 'success'); showMessage('注册成功!', 'success');
} else { } else {
showMessage(data.message || '登录失败', 'error'); showMessage(data.message || '注册失败', 'error');
} }
} catch (error) { } catch (error) {
console.error('登录失败:', error); console.error('注册失败:', error);
showMessage('登录失败,请检查网络连接', 'error'); showMessage('注册失败,请检查网络连接', 'error');
} finally { } finally {
loginBtn.disabled = false; loginBtn.disabled = false;
loginBtn.textContent = '登录/注册'; loginBtn.textContent = '登录/注册';
} }
}); });
// 提交基础信息 // 提交基础信息
submitBasicBtn.addEventListener('click', async () => { submitBasicBtn.addEventListener('click', async () => {
const age = document.getElementById('age').value; const age = document.getElementById('age').value;
const gender = document.getElementById('gender').value; const gender = document.getElementById('gender').value;
@@ -79,12 +80,12 @@ submitBasicBtn.addEventListener('click', async () => {
const activityLevel = document.getElementById('activityLevel').value; const activityLevel = document.getElementById('activityLevel').value;
if (!age || !gender || !height || !weight || !activityLevel) { if (!age || !gender || !height || !weight || !activityLevel) {
showMessage('请填写完整的基础信息', 'error'); showMessage('请填写完整的基础信息', 'error');
return; return;
} }
submitBasicBtn.disabled = true; submitBasicBtn.disabled = true;
submitBasicBtn.textContent = '提交中...'; submitBasicBtn.textContent = '提交中...';
try { try {
const response = await fetch('/api/questionnaire/submit', { const response = await fetch('/api/questionnaire/submit', {
@@ -108,20 +109,20 @@ submitBasicBtn.addEventListener('click', async () => {
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
showMessage('基础信息提交成功!', 'success'); showMessage('基础信息提交成功!', 'success');
} else { } else {
showMessage(data.message || '提交失败', 'error'); showMessage(data.message || '提交失败', 'error');
} }
} catch (error) { } catch (error) {
console.error('提交失败:', error); console.error('提交失败:', error);
showMessage('提交失败,请检查网络连接', 'error'); showMessage('提交失败,请检查网络连接', 'error');
} finally { } finally {
submitBasicBtn.disabled = false; submitBasicBtn.disabled = false;
submitBasicBtn.textContent = '提交基础信息'; submitBasicBtn.textContent = '提交基础信息';
} }
}); });
// 记录餐食 // 记录餐食
submitMealBtn.addEventListener('click', async () => { submitMealBtn.addEventListener('click', async () => {
const mealDate = document.getElementById('mealDate').value || new Date().toISOString().split('T')[0]; const mealDate = document.getElementById('mealDate').value || new Date().toISOString().split('T')[0];
const mealType = document.getElementById('mealType').value; const mealType = document.getElementById('mealType').value;
@@ -130,20 +131,20 @@ submitMealBtn.addEventListener('click', async () => {
const satisfaction = document.getElementById('satisfaction').value; const satisfaction = document.getElementById('satisfaction').value;
if (!foodsText || !calories) { if (!foodsText || !calories) {
showMessage('请填写完整的餐食信息', 'error'); showMessage('请填写完整的餐食信息', 'error');
return; return;
} }
// 解析食物列表 // 解析食物列表
const foods = foodsText.split('\n').filter(line => line.trim()); const foods = foodsText.split('\n').filter(line => line.trim());
const quantities = foods.map(f => { const quantities = foods.map(f => {
const parts = f.trim().split(/\s+/); const parts = f.trim().split(/\s+/);
return parts.length > 1 ? parts.slice(1).join(' ') : '适量'; return parts.length > 1 ? parts.slice(1).join(' ') : '适量';
}); });
const foodNames = foods.map(f => f.trim().split(/\s+/)[0]); const foodNames = foods.map(f => f.trim().split(/\s+/)[0]);
submitMealBtn.disabled = true; submitMealBtn.disabled = true;
submitMealBtn.textContent = '记录中...'; submitMealBtn.textContent = '记录中...';
try { try {
const response = await fetch('/api/meal/record', { const response = await fetch('/api/meal/record', {
@@ -166,28 +167,27 @@ submitMealBtn.addEventListener('click', async () => {
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
showMessage('餐食记录成功!', 'success'); showMessage('餐食记录成功!', 'success');
// 清空表单 // 清空表单
document.getElementById('foods').value = ''; document.getElementById('foods').value = '';
document.getElementById('calories').value = ''; document.getElementById('calories').value = '';
document.getElementById('satisfaction').value = '3'; document.getElementById('satisfaction').value = '3';
} else { } else {
showMessage(data.message || '记录失败', 'error'); showMessage(data.message || '记录失败', 'error');
} }
} catch (error) { } catch (error) {
console.error('记录失败:', error); console.error('记录失败:', error);
showMessage('记录失败,请检查网络连接', 'error'); showMessage('记录失败,请检查网络连接', 'error');
} finally { } finally {
submitMealBtn.disabled = false; submitMealBtn.disabled = false;
submitMealBtn.textContent = '记录餐食'; submitMealBtn.textContent = '记录餐食';
} }
}); });
// 设置默认日期为今天 // 设置默认日期为今天
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const mealDateInput = document.getElementById('mealDate'); const mealDateInput = document.getElementById('mealDate');
if (mealDateInput) { if (mealDateInput) {
mealDateInput.value = new Date().toISOString().split('T')[0]; mealDateInput.value = new Date().toISOString().split('T')[0];
} }
}); });

View File

@@ -1,8 +1,8 @@
// 推荐功能脚本 // 推荐功能脚本
let currentUserId = null; let currentUserId = null;
// DOM元素 // DOM元素
const loginSection = document.getElementById('loginSection'); const loginSection = document.getElementById('loginSection');
const requestSection = document.getElementById('requestSection'); const requestSection = document.getElementById('requestSection');
const recommendationsSection = document.getElementById('recommendationsSection'); const recommendationsSection = document.getElementById('recommendationsSection');
@@ -14,27 +14,28 @@ const getRecommendationBtn = document.getElementById('getRecommendationBtn');
const mealTypeSelect = document.getElementById('mealType'); const mealTypeSelect = document.getElementById('mealType');
const recommendationsList = document.getElementById('recommendationsList'); const recommendationsList = document.getElementById('recommendationsList');
// 显示消息 // 显示消息
function showMessage(message, type = 'info') { function showMessage(message, type = 'info') {
messageArea.textContent = message; messageArea.textContent = message;
messageArea.className = `message-area ${type}`; messageArea.className = `message-area ${type}`;
messageArea.style.display = 'block';
setTimeout(() => { setTimeout(() => {
messageArea.className = 'message-area'; messageArea.className = 'message-area';
messageArea.style.display = 'none'; messageArea.style.display = 'none';
}, 3000); }, 3000);
} }
// 用户登录 // 用户登录
loginBtn.addEventListener('click', async () => { loginBtn.addEventListener('click', async () => {
const userId = userIdInput.value.trim(); const userId = userIdInput.value.trim();
if (!userId) { if (!userId) {
showMessage('请输入用户ID', 'error'); showMessage('请输入用户ID', 'error');
return; return;
} }
loginBtn.disabled = true; loginBtn.disabled = true;
loginBtn.textContent = '登录中...'; loginBtn.textContent = '登录中...';
try { try {
const response = await fetch('/api/user/login', { const response = await fetch('/api/user/login', {
@@ -53,25 +54,25 @@ loginBtn.addEventListener('click', async () => {
currentUserId = userId; currentUserId = userId;
loginSection.style.display = 'none'; loginSection.style.display = 'none';
requestSection.style.display = 'block'; requestSection.style.display = 'block';
showMessage(`欢迎,${data.name || userId}`, 'success'); showMessage(`欢迎,${data.name || userId}`, 'success');
} else { } else {
showMessage(data.message || '登录失败', 'error'); showMessage(data.message || '登录失败', 'error');
} }
} catch (error) { } catch (error) {
console.error('登录失败:', error); console.error('登录失败:', error);
showMessage('登录失败,请检查网络连接', 'error'); showMessage('登录失败,请检查网络连接', 'error');
} finally { } finally {
loginBtn.disabled = false; loginBtn.disabled = false;
loginBtn.textContent = '登录'; loginBtn.textContent = '登录';
} }
}); });
// 获取推荐 // 获取推荐
getRecommendationBtn.addEventListener('click', async () => { getRecommendationBtn.addEventListener('click', async () => {
const mealType = mealTypeSelect.value; const mealType = mealTypeSelect.value;
getRecommendationBtn.disabled = true; getRecommendationBtn.disabled = true;
getRecommendationBtn.textContent = '获取中...'; getRecommendationBtn.textContent = '获取中...';
try { try {
const response = await fetch('/api/recommendation/get', { const response = await fetch('/api/recommendation/get', {
@@ -92,20 +93,20 @@ getRecommendationBtn.addEventListener('click', async () => {
if (data.success && data.recommendations && data.recommendations.length > 0) { if (data.success && data.recommendations && data.recommendations.length > 0) {
displayRecommendations(data.recommendations); displayRecommendations(data.recommendations);
recommendationsSection.style.display = 'block'; recommendationsSection.style.display = 'block';
showMessage('推荐获取成功!', 'success'); showMessage('推荐获取成功!', 'success');
} else { } else {
showMessage(data.message || '暂无推荐,请先完善个人数据', 'info'); showMessage(data.message || '暂无推荐内容,请先完善个人数据', 'info');
} }
} catch (error) { } catch (error) {
console.error('获取推荐失败:', error); console.error('获取推荐失败:', error);
showMessage('获取推荐失败,请检查网络连接', 'error'); showMessage('获取推荐失败,请检查网络连接', 'error');
} finally { } finally {
getRecommendationBtn.disabled = false; getRecommendationBtn.disabled = false;
getRecommendationBtn.textContent = '获取推荐'; getRecommendationBtn.textContent = '获取推荐';
} }
}); });
// 显示推荐结果 // 显示推荐结果
function displayRecommendations(recommendations) { function displayRecommendations(recommendations) {
recommendationsList.innerHTML = ''; recommendationsList.innerHTML = '';
@@ -120,16 +121,34 @@ function displayRecommendations(recommendations) {
foods = [rec.food]; foods = [rec.food];
} }
const foodsHtml = foods.map(food =>
`<div class="food-item">${escapeHtml(food)}</div>`
).join('');
const confidenceHtml = rec.confidence
? `<div class="confidence">置信度: ${(rec.confidence * 100).toFixed(1)}%</div>`
: '';
const reasonHtml = rec.reason
? `<div style="margin-top: 10px; color: #666;">推荐理由: ${escapeHtml(rec.reason)}</div>`
: '';
card.innerHTML = ` card.innerHTML = `
<h3>推荐方案 ${index + 1}</h3> <h3>推荐方案 ${index + 1}</h3>
<div class="food-list"> <div class="food-list">
${foods.map(food => `<div class="food-item">${food}</div>`).join('')} ${foodsHtml}
</div> </div>
${rec.confidence ? `<div class="confidence">置信度: ${(rec.confidence * 100).toFixed(1)}%</div>` : ''} ${confidenceHtml}
${rec.reason ? `<div style="margin-top: 10px; color: #666;">推荐理由: ${rec.reason}</div>` : ''} ${reasonHtml}
`; `;
recommendationsList.appendChild(card); recommendationsList.appendChild(card);
}); });
} }
// HTML转义函数防止XSS攻击
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

View File

@@ -3,47 +3,47 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ӫ - ԻʳƼ</title> <title>营养分析 - 个性化饮食推荐助手</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/analysis.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/analysis.css') }}">
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<header class="header"> <header class="header">
<h1>? Ӫ</h1> <h1>🍎 营养分析</h1>
<p class="subtitle">AIӪ뽨</p> <p class="subtitle">AI智能营养分析</p>
</header> </header>
<nav class="nav"> <nav class="nav">
<a href="/" class="nav-item">ҳ</a> <a href="/" class="nav-item">首页</a>
<a href="/data-collection" class="nav-item">ݲɼ</a> <a href="/data-collection" class="nav-item">数据采集</a>
<a href="/recommendation" class="nav-item">Ƽ</a> <a href="/recommendation" class="nav-item">智能推荐</a>
<a href="/analysis" class="nav-item active">Ӫ</a> <a href="/analysis" class="nav-item active">营养分析</a>
<a href="/recitation" class="nav-item"></a> <a href="/recitation" class="nav-item">背诵排序</a>
</nav> </nav>
<main class="main"> <main class="main">
<div class="analysis-container"> <div class="analysis-container">
<div id="loginSection" class="section"> <div id="loginSection" class="section">
<h2>û¼</h2> <h2>用户登录</h2>
<div class="form-group"> <div class="form-group">
<label for="userId">ûID</label> <label for="userId">用户ID</label>
<input type="text" id="userId" class="form-input" placeholder="ûID"> <input type="text" id="userId" class="form-input" placeholder="请输入用户ID">
</div> </div>
<button id="loginBtn" class="btn btn-primary">¼</button> <button id="loginBtn" class="btn btn-primary">登录</button>
</div> </div>
<div id="requestSection" class="section" style="display: none;"> <div id="requestSection" class="section" style="display: none;">
<h2>Ӫ</h2> <h2>营养分析</h2>
<div class="form-group"> <div class="form-group">
<label>ʳϢ</label> <label>餐食信息:</label>
<textarea id="mealData" class="form-textarea" rows="5" placeholder="ʳϢ磺&#10;ͣࡢ㽶ţ&#10;350"></textarea> <textarea id="mealData" class="form-textarea" rows="5" placeholder="请输入餐食信息:&#10;例如:燕麦粥、香蕉、牛奶&#10;热量:350大卡"></textarea>
</div> </div>
<button id="analyzeBtn" class="btn btn-primary">ʼ</button> <button id="analyzeBtn" class="btn btn-primary">开始分析</button>
</div> </div>
<div id="analysisSection" class="section" style="display: none;"> <div id="analysisSection" class="section" style="display: none;">
<h2></h2> <h2>分析结果</h2>
<div id="analysisResult" class="analysis-result"></div> <div id="analysisResult" class="analysis-result"></div>
</div> </div>
@@ -52,7 +52,7 @@
</main> </main>
<footer class="footer"> <footer class="footer">
<p>&copy; 2024 ԻʳƼ</p> <p>&copy; 2024 个性化饮食推荐助手</p>
</footer> </footer>
</div> </div>