feat: complete web app features and fix encoding

This commit is contained in:
赵杰 Jie Zhao (雄狮汽车科技)
2025-11-02 22:23:10 +08:00
parent 8b5063a092
commit f65abdef0f
15 changed files with 1542 additions and 5 deletions

View File

@@ -3,11 +3,20 @@
网页端应用 - 个性化饮食推荐助手 + 背诵排序功能
"""
from flask import Flask, render_template, request, jsonify
from flask import Flask, render_template, request, jsonify, session, Response
import re
import random
import logging
import json
from pathlib import Path
from datetime import datetime
# 导入业务模块
from core.base import BaseConfig, AppCore, ModuleManager, ModuleType, initialize_app
from modules.data_collection import DataCollectionModule
from modules.ai_analysis import AIAnalysisModule
from modules.recommendation_engine import RecommendationEngine
from modules.ocr_calorie_recognition import OCRCalorieRecognitionModule
# 配置日志
logging.basicConfig(
@@ -27,6 +36,43 @@ app.config['SECRET_KEY'] = 'your-secret-key-here'
app.jinja_env.auto_reload = True
app.config['TEMPLATES_AUTO_RELOAD'] = True
# 确保所有响应使用UTF-8编码
@app.after_request
def after_request(response):
"""确保所有响应使用UTF-8编码"""
response.headers['Content-Type'] = response.headers.get('Content-Type', 'text/html; charset=utf-8')
if 'charset=' not in response.headers.get('Content-Type', ''):
if response.headers.get('Content-Type', '').startswith('text/html'):
response.headers['Content-Type'] = 'text/html; charset=utf-8'
elif response.headers.get('Content-Type', '').startswith('application/json'):
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response
# 初始化应用核心(延迟加载,避免在导入时就初始化)
app_core = None
config = None
def get_app_core():
"""获取应用核心实例(延迟初始化)"""
global app_core, config
if app_core is None:
config = BaseConfig()
if initialize_app(config):
app_core = AppCore(config)
# 注册所有模块
module_manager = ModuleManager(config)
module_manager.register_module(DataCollectionModule(config))
module_manager.register_module(AIAnalysisModule(config))
module_manager.register_module(RecommendationEngine(config))
module_manager.register_module(OCRCalorieRecognitionModule(config))
module_manager.initialize_all()
app_core.module_manager = module_manager
app_core.start()
logger.info("应用核心初始化完成")
else:
logger.error("应用核心初始化失败")
return app_core
class RecitationSorter:
"""背诵排序器"""
@@ -110,13 +156,31 @@ sorter = RecitationSorter()
@app.route('/')
def index():
"""首页"""
return render_template('index.html')
return render_template('index.html', encoding='utf-8')
@app.route('/recitation')
def recitation():
"""背诵排序页面"""
return render_template('recitation.html')
return render_template('recitation.html', encoding='utf-8')
@app.route('/data-collection')
def data_collection():
"""数据采集页面"""
return render_template('data_collection.html', encoding='utf-8')
@app.route('/recommendation')
def recommendation():
"""推荐页面"""
return render_template('recommendation.html', encoding='utf-8')
@app.route('/analysis')
def analysis():
"""分析页面"""
return render_template('analysis.html', encoding='utf-8')
@app.route('/api/extract', methods=['POST'])
@@ -189,6 +253,322 @@ def sort_items():
}), 500
@app.route('/api/export/sorted', methods=['POST'])
def export_sorted():
"""导出排序结果"""
try:
data = request.get_json()
items = data.get('items', [])
export_format = data.get('format', 'txt') # txt, json, csv
if not items:
return jsonify({
'success': False,
'message': '没有可导出的数据'
}), 400
if export_format == 'json':
content = json.dumps({
'items': items,
'count': len(items),
'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}, ensure_ascii=False, indent=2)
filename = f'背诵排序结果_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'
mimetype = 'application/json; charset=utf-8'
elif export_format == 'csv':
import csv
import io
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(['序号', '知识点'])
for i, item in enumerate(items, 1):
writer.writerow([i, item])
content = output.getvalue()
filename = f'背诵排序结果_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'
mimetype = 'text/csv; charset=utf-8'
else: # txt
content_lines = ['背诵排序结果', '=' * 50, '']
for i, item in enumerate(items, 1):
content_lines.append(f'{i}. {item}')
content_lines.extend(['', '=' * 50, f'导出时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'])
content = '\n'.join(content_lines)
filename = f'背诵排序结果_{datetime.now().strftime("%Y%m%d_%H%M%S")}.txt'
mimetype = 'text/plain; charset=utf-8'
response = Response(
content.encode('utf-8'),
mimetype=mimetype,
headers={
'Content-Disposition': f'attachment; filename="{filename}"'
}
)
return response
except Exception as e:
logger.error(f"导出失败: {e}")
return jsonify({
'success': False,
'message': f'导出失败: {str(e)}'
}), 500
# ==================== 业务功能API ====================
@app.route('/api/user/login', methods=['POST'])
def user_login():
"""用户登录"""
try:
data = request.get_json()
user_id = data.get('user_id', '').strip()
if not user_id:
return jsonify({
'success': False,
'message': '请输入用户ID'
}), 400
# 获取用户数据(如果不存在会自动创建)
core = get_app_core()
user_data = core.get_user_data(user_id)
if user_data:
session['user_id'] = user_id
return jsonify({
'success': True,
'user_id': user_id,
'name': user_data.profile.get('name', '未设置')
})
else:
return jsonify({
'success': False,
'message': '用户数据获取失败'
}), 500
except Exception as e:
logger.error(f"用户登录失败: {e}")
return jsonify({
'success': False,
'message': f'登录失败: {str(e)}'
}), 500
@app.route('/api/user/register', methods=['POST'])
def user_register():
"""用户注册"""
try:
data = request.get_json()
user_id = data.get('user_id', '').strip()
name = data.get('name', '').strip()
if not user_id or not name:
return jsonify({
'success': False,
'message': '请输入用户ID和姓名'
}), 400
core = get_app_core()
user_data = core.get_user_data(user_id)
# 更新用户基本信息
user_data.profile['name'] = name
core.data_manager.save_user_data(user_data)
session['user_id'] = user_id
return jsonify({
'success': True,
'user_id': user_id,
'name': name
})
except Exception as e:
logger.error(f"用户注册失败: {e}")
return jsonify({
'success': False,
'message': f'注册失败: {str(e)}'
}), 500
@app.route('/api/questionnaire/submit', methods=['POST'])
def submit_questionnaire():
"""提交问卷"""
try:
user_id = session.get('user_id') or request.json.get('user_id')
if not user_id:
return jsonify({
'success': False,
'message': '请先登录'
}), 401
data = request.get_json()
questionnaire_type = data.get('type', 'basic') # basic, taste, physiological
answers = data.get('answers', {})
core = get_app_core()
input_data = {
'type': 'questionnaire',
'questionnaire_type': questionnaire_type,
'answers': answers
}
result = core.process_user_request(ModuleType.DATA_COLLECTION, input_data, user_id)
if result and result.result.get('success', False):
return jsonify({
'success': True,
'message': '问卷提交成功'
})
else:
return jsonify({
'success': False,
'message': result.result.get('message', '问卷提交失败') if result else '处理失败'
}), 500
except Exception as e:
logger.error(f"提交问卷失败: {e}")
return jsonify({
'success': False,
'message': f'提交失败: {str(e)}'
}), 500
@app.route('/api/meal/record', methods=['POST'])
def record_meal():
"""记录餐食"""
try:
user_id = session.get('user_id') or request.json.get('user_id')
if not user_id:
return jsonify({
'success': False,
'message': '请先登录'
}), 401
data = request.get_json()
meal_data = {
'date': data.get('date', datetime.now().strftime('%Y-%m-%d')),
'meal_type': data.get('meal_type', 'lunch'), # breakfast, lunch, dinner
'foods': data.get('foods', []),
'quantities': data.get('quantities', []),
'calories': data.get('calories', 0),
'satisfaction_score': data.get('satisfaction_score', 3),
'notes': data.get('notes', '')
}
core = get_app_core()
input_data = {
'type': 'meal_record',
**meal_data
}
result = core.process_user_request(ModuleType.DATA_COLLECTION, input_data, user_id)
if result and result.result.get('success', False):
return jsonify({
'success': True,
'message': '餐食记录成功'
})
else:
return jsonify({
'success': False,
'message': result.result.get('message', '记录失败') if result else '处理失败'
}), 500
except Exception as e:
logger.error(f"记录餐食失败: {e}")
return jsonify({
'success': False,
'message': f'记录失败: {str(e)}'
}), 500
@app.route('/api/recommendation/get', methods=['POST'])
def get_recommendations():
"""获取餐食推荐"""
try:
user_id = session.get('user_id') or request.json.get('user_id')
if not user_id:
return jsonify({
'success': False,
'message': '请先登录'
}), 401
data = request.get_json()
meal_type = data.get('meal_type', 'lunch')
preferences = data.get('preferences', {})
context = data.get('context', {})
core = get_app_core()
input_data = {
'type': 'meal_recommendation',
'meal_type': meal_type,
'preferences': preferences,
'context': context
}
result = core.process_user_request(ModuleType.RECOMMENDATION, input_data, user_id)
if result and result.result.get('success', False):
return jsonify({
'success': True,
'recommendations': result.result.get('recommendations', [])
})
else:
return jsonify({
'success': False,
'message': result.result.get('message', '推荐失败') if result else '处理失败',
'recommendations': []
}), 500
except Exception as e:
logger.error(f"获取推荐失败: {e}")
return jsonify({
'success': False,
'message': f'获取失败: {str(e)}',
'recommendations': []
}), 500
@app.route('/api/analysis/nutrition', methods=['POST'])
def analyze_nutrition():
"""营养分析"""
try:
user_id = session.get('user_id') or request.json.get('user_id')
if not user_id:
return jsonify({
'success': False,
'message': '请先登录'
}), 401
data = request.get_json()
meal_data = data.get('meal_data', {})
core = get_app_core()
input_data = {
'type': 'nutrition_analysis',
'meal_data': meal_data
}
result = core.process_user_request(ModuleType.USER_ANALYSIS, input_data, user_id)
if result and result.result.get('success', False):
return jsonify({
'success': True,
'analysis': result.result.get('analysis', {})
})
else:
return jsonify({
'success': False,
'message': result.result.get('message', '分析失败') if result else '处理失败'
}), 500
except Exception as e:
logger.error(f"营养分析失败: {e}")
return jsonify({
'success': False,
'message': f'分析失败: {str(e)}'
}), 500
@app.route('/health')
def health():
"""健康检查"""