feat: 现代化翻页组件 + 完整项目更新

- 新增 Pagination 通用组件:首页/上一页/页码/下一页/末页导航
- PickModal 和 SwapModal 集成分页功能,每页20项
- 搜索/筛选切换自动回到第1页
- 现代化CSS样式:hover浮起动效、主色高亮、移动端适配
- 新增 dashboard、admin 管理模板
- 菜系(cuisine)模型和迁移
- 构建脚本 build.py 支持 PyInstaller 打包
- 前端资源重新构建
This commit is contained in:
Chunyu
2026-08-28 13:44:21 +08:00
parent 65b52b052f
commit d7553520ce
51 changed files with 4318 additions and 441 deletions
@@ -0,0 +1,29 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
"""Step 1: Create the Cuisine table (no changes to Dish yet)."""
dependencies = [
('meals', '0005_dish_pinyin'),
]
operations = [
migrations.CreateModel(
name='Cuisine',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, unique=True, verbose_name='菜系名称')),
('description', models.CharField(blank=True, default='', max_length=200, verbose_name='描述')),
('sort_order', models.IntegerField(default=0, help_text='数值越小越靠前', verbose_name='排序')),
('is_active', models.BooleanField(default=True, verbose_name='启用')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
],
options={
'verbose_name': '菜系',
'verbose_name_plural': '菜系',
'ordering': ['sort_order', 'id'],
},
),
]
@@ -0,0 +1,93 @@
import django.db.models.deletion
from django.db import migrations, models
CUISINE_SEEDS = [
('家常菜', 0),
('本帮菜', 10),
('江浙菜', 20),
('川菜', 30),
('粤菜', 40),
('鲁菜', 50),
('湘菜', 60),
('西北菜', 70),
('面点小吃', 80),
]
def seed_cuisines(apps, schema_editor):
"""Create Cuisine rows and save dish->cuisine mapping to a temp table."""
Cuisine = apps.get_model('meals', 'Cuisine')
name_to_id = {}
for name, order in CUISINE_SEEDS:
obj, _ = Cuisine.objects.get_or_create(name=name, defaults={'sort_order': order})
name_to_id[name] = obj.pk
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT id, cuisine FROM meals_dish WHERE cuisine IS NOT NULL AND cuisine != ''")
rows = cursor.fetchall()
for _, cuisine_name in rows:
if cuisine_name not in name_to_id:
obj, _ = Cuisine.objects.get_or_create(name=cuisine_name, defaults={'sort_order': 90})
name_to_id[cuisine_name] = obj.pk
with connection.cursor() as cursor:
cursor.execute("CREATE TABLE _temp_dish_cuisine (dish_id INTEGER, cuisine_id INTEGER)")
for dish_id, cuisine_name in rows:
cuisine_id = name_to_id.get(cuisine_name)
if cuisine_id:
cursor.execute(
"INSERT INTO _temp_dish_cuisine (dish_id, cuisine_id) VALUES (%s, %s)",
[dish_id, cuisine_id],
)
def update_cuisine_fk(apps, schema_editor):
"""Populate Dish.cuisine_id from the temp table, then clean up."""
from django.db import connection
with connection.cursor() as cursor:
cursor.execute(
"UPDATE meals_dish SET cuisine_id = "
"(SELECT cuisine_id FROM _temp_dish_cuisine "
"WHERE _temp_dish_cuisine.dish_id = meals_dish.id) "
"WHERE EXISTS (SELECT 1 FROM _temp_dish_cuisine "
"WHERE _temp_dish_cuisine.dish_id = meals_dish.id)"
)
cursor.execute("DROP TABLE _temp_dish_cuisine")
def reverse_seed(apps, schema_editor):
"""Reverse: clean up temp table and delete Cuisine rows."""
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("DROP TABLE IF EXISTS _temp_dish_cuisine")
Cuisine = apps.get_model('meals', 'Cuisine')
Cuisine.objects.all().delete()
class Migration(migrations.Migration):
"""Step 2: Seed cuisines from existing data, then alter Dish.cuisine to FK."""
dependencies = [
('meals', '0006_cuisine_dish_cuisine_fk'),
]
operations = [
migrations.RunPython(seed_cuisines, reverse_seed),
migrations.AlterField(
model_name='dish',
name='cuisine',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='dishes',
to='meals.cuisine',
verbose_name='菜系',
),
),
migrations.RunPython(update_cuisine_fk, migrations.RunPython.noop),
]
@@ -0,0 +1,18 @@
# Generated by Django 6.0.8 on 2026-08-27 06:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('meals', '0007_seed_cuisines_and_update_dish'),
]
operations = [
migrations.AlterField(
model_name='cuisine',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]