学校订餐菜单生成器:Django+DRF 后端与 React 前端,支持 A/B 双套营养周菜单生成、部分自选、拼音搜索、营养校准、Excel 导出

This commit is contained in:
root
2026-08-06 14:09:27 +08:00
commit 75f214dc40
48 changed files with 4684 additions and 0 deletions
@@ -0,0 +1,53 @@
from django.core.management.base import BaseCommand
from meals.models import Dish
TYPE_MAP = {'荤菜': 'meat', '素菜': 'veg', '汤': 'soup', '主食': 'staple'}
TYPE_MAP_REV = {v: k for k, v in TYPE_MAP.items()}
class Command(BaseCommand):
help = '从文本文件批量导入菜品。每行格式:类型|菜名|配料明细|蛋白质|脂肪|热量|菜系 (类型:荤菜/素菜/汤/主食,营养与菜系可省略)'
def add_arguments(self, parser):
parser.add_argument('file', help='菜品文本文件路径')
def handle(self, *args, **options):
path = options['file']
created, skipped = 0, 0
with open(path, encoding='utf-8-sig') as f:
for line_no, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith('#'):
continue
parts = [p.strip() for p in line.split('|')]
if len(parts) < 2:
self.stderr.write(f'第 {line_no} 行格式错误,已跳过: {line}')
skipped += 1
continue
label, name = parts[0], parts[1]
dish_type = TYPE_MAP.get(label)
if not dish_type or not name:
self.stderr.write(f'第 {line_no} 行类型或菜名无效,已跳过: {line}')
skipped += 1
continue
ingredient = parts[2] if len(parts) > 2 else ''
protein = float(parts[3]) if len(parts) > 3 and parts[3] else 0
fat = float(parts[4]) if len(parts) > 4 and parts[4] else 0
calorie = int(float(parts[5])) if len(parts) > 5 and parts[5] else 0
cuisine = parts[6] if len(parts) > 6 and parts[6] else '家常菜'
dish, is_created = Dish.objects.update_or_create(
name=name,
defaults={
'dish_type': dish_type,
'cuisine': cuisine,
'ingredient_detail': ingredient,
'protein': protein,
'fat': fat,
'calorie': calorie,
},
)
if is_created:
created += 1
self.stdout.write(f'新增: {label} - {name}({cuisine})')
self.stdout.write(self.style.SUCCESS(f'导入完成:新增 {created} 道,跳过 {skipped} 条'))