feat: 现代化翻页组件 + 完整项目更新
- 新增 Pagination 通用组件:首页/上一页/页码/下一页/末页导航 - PickModal 和 SwapModal 集成分页功能,每页20项 - 搜索/筛选切换自动回到第1页 - 现代化CSS样式:hover浮起动效、主色高亮、移动端适配 - 新增 dashboard、admin 管理模板 - 菜系(cuisine)模型和迁移 - 构建脚本 build.py 支持 PyInstaller 打包 - 前端资源重新构建
This commit is contained in:
+36
-17
@@ -8,13 +8,14 @@ from openpyxl.utils import get_column_letter
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .models import Dish
|
||||
from .serializers import DishSerializer
|
||||
from .models import Cuisine, Dish
|
||||
from .serializers import DishSerializer, CuisineSerializer
|
||||
|
||||
WEEKDAYS = ['周一', '周二', '周三', '周四', '周五']
|
||||
SCHOOL_NAME = '上海外国语大学附属宝山双语学校'
|
||||
SCHOOL_NAME = '学校菜单'
|
||||
|
||||
COLOR_HEADER = PatternFill('solid', fgColor='2F5597')
|
||||
COLOR_DAY = PatternFill('solid', fgColor='D9E2F3')
|
||||
@@ -27,9 +28,16 @@ BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
|
||||
CENTER = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
|
||||
|
||||
class CuisineViewSet(viewsets.ModelViewSet):
|
||||
queryset = Cuisine.objects.all()
|
||||
serializer_class = CuisineSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
|
||||
class DishViewSet(viewsets.ModelViewSet):
|
||||
queryset = Dish.objects.all()
|
||||
serializer_class = DishSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Dish.objects.all()
|
||||
@@ -59,9 +67,14 @@ def bulk_create_dishes(request):
|
||||
if not name or dish_type not in ('meat', 'veg', 'soup', 'staple'):
|
||||
skipped.append(name or '未命名')
|
||||
continue
|
||||
# Resolve cuisine FK by name
|
||||
cuisine_name = str(raw.get('cuisine', '') or '').strip()
|
||||
cuisine_obj = None
|
||||
if cuisine_name:
|
||||
cuisine_obj, _ = Cuisine.objects.get_or_create(name=cuisine_name)
|
||||
defaults = {
|
||||
'dish_type': dish_type,
|
||||
'cuisine': str(raw.get('cuisine', '家常菜') or '家常菜'),
|
||||
'cuisine': cuisine_obj,
|
||||
'ingredient_detail': str(raw.get('ingredient_detail', '') or raw.get('ingredient', '')),
|
||||
'protein': float(raw.get('protein', 0) or 0),
|
||||
'fat': float(raw.get('fat', 0) or 0),
|
||||
@@ -94,7 +107,7 @@ def _pop_next(pool, shuffle_src):
|
||||
def _serialize_dish(d):
|
||||
return {
|
||||
'id': d.id, 'name': d.name, 'ingredient': d.ingredient_detail, 'dish_type': d.dish_type,
|
||||
'cuisine': d.cuisine, 'protein': float(d.protein), 'fat': float(d.fat), 'calorie': d.calorie,
|
||||
'cuisine': d.cuisine_name, 'protein': float(d.protein), 'fat': float(d.fat), 'calorie': d.calorie,
|
||||
}
|
||||
|
||||
|
||||
@@ -211,19 +224,19 @@ def generate_week_menu(payload, menu_key='a'):
|
||||
|
||||
week.append({
|
||||
'day': day,
|
||||
'meats': [{'id': d.id, 'name': d.name, 'ingredient': d.ingredient_detail, 'dish_type': d.dish_type, 'cuisine': d.cuisine,
|
||||
'meats': [{'id': d.id, 'name': d.name, 'ingredient': d.ingredient_detail, 'dish_type': d.dish_type, 'cuisine': d.cuisine_name,
|
||||
'protein': float(d.protein), 'fat': float(d.fat), 'calorie': d.calorie} for d in day_meat_list],
|
||||
'vegs': [{'id': d.id, 'name': d.name, 'ingredient': d.ingredient_detail, 'dish_type': d.dish_type, 'cuisine': d.cuisine,
|
||||
'vegs': [{'id': d.id, 'name': d.name, 'ingredient': d.ingredient_detail, 'dish_type': d.dish_type, 'cuisine': d.cuisine_name,
|
||||
'protein': float(d.protein), 'fat': float(d.fat), 'calorie': d.calorie} for d in day_veg],
|
||||
'soup': {'id': soup.id, 'name': soup.name, 'ingredient': soup.ingredient_detail, 'dish_type': soup.dish_type, 'cuisine': soup.cuisine,
|
||||
'soup': {'id': soup.id, 'name': soup.name, 'ingredient': soup.ingredient_detail, 'dish_type': soup.dish_type, 'cuisine': soup.cuisine_name,
|
||||
'protein': float(soup.protein), 'fat': float(soup.fat), 'calorie': soup.calorie} if soup else None,
|
||||
'staple': {'id': staple.id, 'name': staple.name, 'ingredient': staple.ingredient_detail, 'dish_type': staple.dish_type, 'cuisine': staple.cuisine,
|
||||
'staple': {'id': staple.id, 'name': staple.name, 'ingredient': staple.ingredient_detail, 'dish_type': staple.dish_type, 'cuisine': staple.cuisine_name,
|
||||
'protein': float(staple.protein), 'fat': float(staple.fat), 'calorie': staple.calorie} if staple else None,
|
||||
})
|
||||
return week
|
||||
|
||||
|
||||
def build_menu_title(label, school_name=None, week_no=None):
|
||||
def build_menu_title(label, school_name=None, week_no=None, date_start=None, date_end=None):
|
||||
school_name = school_name or SCHOOL_NAME
|
||||
iso = date.today().isocalendar()
|
||||
try:
|
||||
@@ -232,12 +245,16 @@ def build_menu_title(label, school_name=None, week_no=None):
|
||||
week_no = iso.week
|
||||
if not 1 <= week_no <= 53:
|
||||
week_no = iso.week
|
||||
try:
|
||||
monday = date.fromisocalendar(iso.year, week_no, 1)
|
||||
except ValueError:
|
||||
monday = date.today() - timedelta(days=date.today().weekday())
|
||||
friday = monday + timedelta(days=4)
|
||||
date_range = f'{monday.month}.{monday.day}--{friday.month}.{friday.day}'
|
||||
# Use custom dates if provided, otherwise compute from week number
|
||||
if date_start and date_end:
|
||||
date_range = f'{date_start}--{date_end}'
|
||||
else:
|
||||
try:
|
||||
monday = date.fromisocalendar(iso.year, week_no, 1)
|
||||
except ValueError:
|
||||
monday = date.today() - timedelta(days=date.today().weekday())
|
||||
friday = monday + timedelta(days=4)
|
||||
date_range = f'{monday.month}.{monday.day}--{friday.month}.{friday.day}'
|
||||
return f'{school_name}[第{week_no}周]{date_range}营养{label}菜单'
|
||||
|
||||
|
||||
@@ -246,11 +263,13 @@ def generate_menu(request):
|
||||
data = request.data
|
||||
school = data.get('school_name') or SCHOOL_NAME
|
||||
week_no = data.get('week_no')
|
||||
date_start = data.get('date_start')
|
||||
date_end = data.get('date_end')
|
||||
menus = {}
|
||||
for label in ('A', 'B'):
|
||||
key = label.lower()
|
||||
menus[key] = {
|
||||
'title': build_menu_title(label, school, week_no),
|
||||
'title': build_menu_title(label, school, week_no, date_start, date_end),
|
||||
'week': generate_week_menu(data, menu_key=key),
|
||||
}
|
||||
return Response(menus)
|
||||
|
||||
Reference in New Issue
Block a user