临时保存2

This commit is contained in:
rjb
2025-09-09 08:00:07 +08:00
parent 0c7420d17c
commit 6f82485ccc
21 changed files with 2012 additions and 1 deletions

View File

@@ -94,6 +94,11 @@ def meal_planning_page():
"""饭菜规划页面"""
return render_template('meal_planning.html')
@meal_planning_bp.route('/meal-planning/mobile', methods=['GET'])
def meal_planning_mobile_page():
"""饭菜规划移动端页面"""
return render_template('meal_planning_mobile.html')
@meal_planning_bp.route('/meal-planning/history', methods=['GET'])
def meal_planning_history():
"""饭菜规划历史页面"""

View File

@@ -0,0 +1,55 @@
{
"name": "智能饭菜规划",
"short_name": "饭菜规划",
"description": "AI驱动的个性化饭菜清单规划师",
"start_url": "/meal-planning/mobile",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#667eea",
"orientation": "portrait",
"icons": [
{
"src": "/static/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/static/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/static/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/static/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/static/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/static/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/static/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"categories": ["food", "lifestyle", "productivity"],
"lang": "zh-CN",
"dir": "ltr"
}

View File

@@ -0,0 +1,125 @@
// Service Worker for 智能饭菜规划 PWA
const CACHE_NAME = 'meal-planning-v1';
const urlsToCache = [
'/meal-planning/mobile',
'/static/css/bootstrap.min.css',
'/static/js/bootstrap.bundle.min.js',
'/static/css/font-awesome.min.css',
'/api/meal-planning/generate',
'/api/meal-planning/save'
];
// 安装事件
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// 激活事件
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
// 拦截请求
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// 缓存命中 - 返回缓存的版本
if (response) {
return response;
}
// 克隆请求
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// 检查是否收到有效响应
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
// 后台同步
self.addEventListener('sync', function(event) {
if (event.tag === 'background-sync') {
event.waitUntil(doBackgroundSync());
}
});
function doBackgroundSync() {
// 处理离线时的数据同步
return Promise.resolve();
}
// 推送通知
self.addEventListener('push', function(event) {
const options = {
body: event.data ? event.data.text() : '您有新的饭菜规划建议!',
icon: '/static/icons/icon-192x192.png',
badge: '/static/icons/icon-72x72.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: '查看详情',
icon: '/static/icons/icon-72x72.png'
},
{
action: 'close',
title: '关闭',
icon: '/static/icons/icon-72x72.png'
}
]
};
event.waitUntil(
self.registration.showNotification('智能饭菜规划', options)
);
});
// 通知点击事件
self.addEventListener('notificationclick', function(event) {
event.notification.close();
if (event.action === 'explore') {
event.waitUntil(
clients.openWindow('/meal-planning/mobile')
);
}
});

View File

@@ -0,0 +1,498 @@
{% extends "base.html" %}
{% block title %}智能饭菜规划 - 提示词大师{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- 移动端优化的页面标题 -->
<div class="page-header-mobile">
<h1 class="page-title-mobile">
<i class="fas fa-utensils"></i>
智能饭菜规划
</h1>
<p class="page-subtitle-mobile">AI驱动的个性化饭菜清单规划师</p>
</div>
<!-- 移动端优化的表单区域 -->
<div class="mobile-form-container">
<div class="card mobile-card">
<div class="card-header mobile-card-header">
<h5 class="card-title-mobile">
<i class="fas fa-cog"></i>
规划参数
</h5>
</div>
<div class="card-body mobile-card-body">
<form id="mealPlanningFormMobile">
<!-- 地区类型 -->
<div class="form-group-mobile mb-3">
<label for="regionType" class="form-label-mobile">地区类型</label>
<select class="form-select-mobile" id="regionType" name="region_type">
<option value="全国">全国</option>
<option value="北方">北方</option>
<option value="南方">南方</option>
<option value="川菜">川菜</option>
<option value="粤菜">粤菜</option>
<option value="鲁菜">鲁菜</option>
<option value="苏菜">苏菜</option>
<option value="浙菜">浙菜</option>
<option value="闽菜">闽菜</option>
<option value="湘菜">湘菜</option>
<option value="徽菜">徽菜</option>
</select>
</div>
<!-- 就餐人数 -->
<div class="form-group-mobile mb-3">
<label for="dinerCount" class="form-label-mobile">就餐人数</label>
<select class="form-select-mobile" id="dinerCount" name="diner_count">
<option value="1">1人</option>
<option value="2" selected>2人</option>
<option value="3">3人</option>
<option value="4">4人</option>
<option value="5">5人</option>
<option value="6">6人</option>
<option value="8">8人</option>
<option value="10">10人</option>
</select>
</div>
<!-- 用餐类型 -->
<div class="form-group-mobile mb-3">
<label for="mealType" class="form-label-mobile">用餐类型</label>
<select class="form-select-mobile" id="mealType" name="meal_type">
<option value="早餐">早餐</option>
<option value="午餐" selected>午餐</option>
<option value="晚餐">晚餐</option>
<option value="全天">全天</option>
</select>
</div>
<!-- 用餐者家乡 -->
<div class="form-group-mobile mb-3">
<label for="hometown" class="form-label-mobile">用餐者家乡 <span class="text-danger">*</span></label>
<input type="text" class="form-control-mobile" id="hometown" name="hometown"
placeholder="如:四川成都" required>
</div>
<!-- 个人喜好 -->
<div class="form-group-mobile mb-3">
<label for="preferences" class="form-label-mobile">个人喜好</label>
<textarea class="form-control-mobile" id="preferences" name="preferences" rows="3"
placeholder="如:喜欢辣味、偏爱素食、喜欢海鲜等"></textarea>
</div>
<!-- 饮食禁忌 -->
<div class="form-group-mobile mb-3">
<label for="dietaryRestrictions" class="form-label-mobile">饮食禁忌</label>
<textarea class="form-control-mobile" id="dietaryRestrictions" name="dietary_restrictions" rows="3"
placeholder="如:不吃猪肉、对花生过敏、素食主义等"></textarea>
</div>
<!-- 预算范围 -->
<div class="form-group-mobile mb-3">
<label for="budget" class="form-label-mobile">预算范围(元)</label>
<select class="form-select-mobile" id="budget" name="budget">
<option value="50">50元以下</option>
<option value="100" selected>50-100元</option>
<option value="150">100-150元</option>
<option value="200">150-200元</option>
<option value="300">200-300元</option>
<option value="500">300-500元</option>
<option value="1000">500元以上</option>
</select>
</div>
<!-- 生成按钮 -->
<div class="d-grid">
<button type="submit" class="btn btn-primary-mobile btn-lg" id="generateBtnMobile">
<i class="fas fa-magic"></i>
生成饭菜规划
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 移动端优化的结果展示区域 -->
<div class="mobile-result-container" id="resultContainerMobile" style="display: none;">
<div class="card mobile-card">
<div class="card-header mobile-card-header">
<h5 class="card-title-mobile">
<i class="fas fa-clipboard-list"></i>
饭菜规划结果
</h5>
<div class="card-actions-mobile">
<button class="btn btn-sm btn-outline-primary-mobile" id="copyBtnMobile">
<i class="fas fa-copy"></i>
复制
</button>
<button class="btn btn-sm btn-outline-success-mobile" id="saveBtnMobile">
<i class="fas fa-save"></i>
保存
</button>
</div>
</div>
<div class="card-body mobile-card-body">
<div class="meal-plan-content-mobile" id="mealPlanContentMobile">
<!-- 结果内容将在这里显示 -->
</div>
</div>
</div>
</div>
</div>
<!-- 移动端优化的加载动画 -->
<div class="modal fade" id="loadingModalMobile" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content mobile-modal-content">
<div class="modal-body text-center py-4">
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">生成中...</span>
</div>
<h5>正在生成饭菜规划...</h5>
<p class="text-muted">AI正在为您制定个性化的饭菜清单请稍候</p>
</div>
</div>
</div>
</div>
<style>
/* 移动端专用样式 */
@media (max-width: 768px) {
.page-header-mobile {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.5rem 1rem;
margin: -1rem -1rem 1.5rem -1rem;
border-radius: 0 0 20px 20px;
}
.page-title-mobile {
font-size: 1.8rem;
font-weight: 700;
margin-bottom: 0.5rem;
text-align: center;
}
.page-subtitle-mobile {
font-size: 1rem;
opacity: 0.9;
margin-bottom: 0;
text-align: center;
}
.mobile-form-container {
margin-bottom: 1.5rem;
}
.mobile-card {
border: none;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
border-radius: 15px;
margin-bottom: 1rem;
}
.mobile-card-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
border-radius: 15px 15px 0 0 !important;
padding: 1rem;
}
.card-title-mobile {
margin-bottom: 0;
font-weight: 600;
color: #495057;
font-size: 1.1rem;
}
.mobile-card-body {
padding: 1rem;
}
.form-group-mobile {
margin-bottom: 1rem;
}
.form-label-mobile {
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
font-size: 0.95rem;
}
.form-control-mobile, .form-select-mobile {
border-radius: 10px;
border: 1px solid #ced4da;
padding: 0.75rem;
font-size: 1rem;
width: 100%;
}
.form-control-mobile:focus, .form-select-mobile:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.btn-primary-mobile {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 10px;
padding: 1rem 1.5rem;
font-weight: 600;
font-size: 1.1rem;
width: 100%;
}
.btn-primary-mobile:hover {
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
}
.mobile-result-container {
margin-bottom: 2rem;
}
.card-actions-mobile {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.btn-outline-primary-mobile, .btn-outline-success-mobile {
border-radius: 8px;
padding: 0.5rem 1rem;
font-size: 0.9rem;
flex: 1;
}
.meal-plan-content-mobile {
background: #f8f9fa;
border-radius: 10px;
padding: 1rem;
border-left: 4px solid #667eea;
font-size: 0.95rem;
line-height: 1.6;
}
.meal-plan-content-mobile h1,
.meal-plan-content-mobile h2,
.meal-plan-content-mobile h3 {
color: #495057;
margin-top: 1rem;
margin-bottom: 0.5rem;
font-size: 1.1rem;
}
.meal-plan-content-mobile h1:first-child,
.meal-plan-content-mobile h2:first-child,
.meal-plan-content-mobile h3:first-child {
margin-top: 0;
}
.meal-plan-content-mobile ul,
.meal-plan-content-mobile ol {
padding-left: 1.2rem;
}
.meal-plan-content-mobile li {
margin-bottom: 0.3rem;
}
.meal-plan-content-mobile strong {
color: #667eea;
}
.mobile-modal-content {
border-radius: 15px;
border: none;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
/* 触摸优化 */
.btn, .form-control, .form-select {
min-height: 44px; /* iOS推荐的最小触摸目标 */
}
/* 防止双击缩放 */
input, select, textarea, button {
touch-action: manipulation;
}
}
/* 超小屏幕优化 */
@media (max-width: 480px) {
.page-title-mobile {
font-size: 1.5rem;
}
.mobile-card-body {
padding: 0.75rem;
}
.form-control-mobile, .form-select-mobile {
padding: 0.6rem;
font-size: 0.95rem;
}
.btn-primary-mobile {
padding: 0.8rem 1rem;
font-size: 1rem;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('mealPlanningFormMobile');
const generateBtn = document.getElementById('generateBtnMobile');
const resultContainer = document.getElementById('resultContainerMobile');
const copyBtn = document.getElementById('copyBtnMobile');
const saveBtn = document.getElementById('saveBtnMobile');
const loadingModal = new bootstrap.Modal(document.getElementById('loadingModalMobile'));
// 表单提交处理
form.addEventListener('submit', function(e) {
e.preventDefault();
// 验证必填字段
const hometown = document.getElementById('hometown').value.trim();
if (!hometown) {
showAlert('请输入用餐者家乡', 'danger');
return;
}
// 收集表单数据
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// 显示加载动画
loadingModal.show();
generateBtn.disabled = true;
// 发送请求
fetch('/api/meal-planning/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
loadingModal.hide();
generateBtn.disabled = false;
if (result.success) {
displayResult(result.data.meal_plan);
resultContainer.style.display = 'block';
showAlert('饭菜规划生成成功!', 'success');
// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth' });
} else {
showAlert(result.message || '生成失败,请重试', 'danger');
}
})
.catch(error => {
loadingModal.hide();
generateBtn.disabled = false;
console.error('Error:', error);
showAlert('网络错误,请检查网络连接后重试', 'danger');
});
});
// 显示结果
function displayResult(mealPlan) {
// 简单的Markdown转HTML函数
function simpleMarkdownToHtml(text) {
return text
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
.replace(/\*(.*)\*/gim, '<em>$1</em>')
.replace(/^\* (.*$)/gim, '<li>$1</li>')
.replace(/^\d+\. (.*$)/gim, '<li>$1</li>')
.replace(/\n\n/gim, '</p><p>')
.replace(/\n/gim, '<br>')
.replace(/^(.*)$/gim, '<p>$1</p>');
}
document.getElementById('mealPlanContentMobile').innerHTML = simpleMarkdownToHtml(mealPlan);
}
// 复制功能
copyBtn.addEventListener('click', function() {
const content = document.getElementById('mealPlanContentMobile').textContent;
navigator.clipboard.writeText(content).then(() => {
showAlert('饭菜规划已复制到剪贴板', 'success');
}).catch(() => {
showAlert('复制失败,请手动复制', 'danger');
});
});
// 保存功能
saveBtn.addEventListener('click', function() {
const content = document.getElementById('mealPlanContentMobile').textContent;
// 获取当前表单参数
const formData = {
meal_plan_content: content,
region_type: document.getElementById('regionType').value,
diner_count: document.getElementById('dinerCount').value,
meal_type: document.getElementById('mealType').value,
hometown: document.getElementById('hometown').value,
preferences: document.getElementById('preferences').value,
dietary_restrictions: document.getElementById('dietaryRestrictions').value,
budget: document.getElementById('budget').value
};
fetch('/api/meal-planning/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(result => {
if (result.success) {
showAlert('饭菜规划保存成功!', 'success');
console.log('保存成功规划ID:', result.data.meal_plan_id);
} else {
showAlert(result.message || '保存失败', 'danger');
}
})
.catch(error => {
console.error('Error:', error);
showAlert('保存失败,请重试', 'danger');
});
});
// 显示提示信息
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// 插入到页面顶部
const container = document.querySelector('.container-fluid');
container.insertBefore(alertDiv, container.firstChild);
// 自动消失
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
});
</script>
{% endblock %}