diff --git a/base/admin.py b/base/admin.py index c4171e8..e49c831 100644 --- a/base/admin.py +++ b/base/admin.py @@ -1,7 +1,8 @@ -from django.contrib import admin +from django.contrib import admin, messages +from django.core.cache import cache from import_export.admin import ImportExportModelAdmin -from base.models import MainMenu, BaseInfo, LinkCategory, FriendLink +from base.models import MainMenu, BaseInfo, LinkCategory, FriendLink, PictureLink # —————— 主菜单 —————— @@ -60,6 +61,25 @@ class FriendLinkAdmin(ImportExportModelAdmin): return super().formfield_for_foreignkey(db_field, request, **kwargs) +@admin.register(PictureLink) +class PictureLinkAdmin(admin.ModelAdmin): + list_display = ['name', 'order', 'is_active', 'created_at'] + + def save_model(self, request, obj, form, change): + # 1. 先执行 Model 的 save 方法(这里会触发逻辑判断和消息暂存) + super().save_model(request, obj, form, change) + + # 2. 【关键】检查有没有暂存的消息 + # 获取 key(如果是新建对象,obj.pk 此时已经有了,如果是内存中的临时对象则用 id) + msg_key = obj.pk if obj.pk else id(obj) + key = f"picture_count_limit_{msg_key}" + msg = cache.get(key) # 缓存 30 秒 + if msg: + # 取出消息并弹窗 + cache.delete(key) + self.message_user(request, msg, level=messages.WARNING) + + # —————— 页脚链接 —————— @admin.register(BaseInfo) class BaseInfoAdmin(ImportExportModelAdmin): diff --git a/base/context_processors.py b/base/context_processors.py index 7ed907d..2717d97 100644 --- a/base/context_processors.py +++ b/base/context_processors.py @@ -1,4 +1,4 @@ -from base.models import BaseInfo, LinkCategory, MainMenu +from base.models import BaseInfo, LinkCategory, MainMenu, PictureLink def site_info(request): @@ -8,9 +8,10 @@ def site_info(request): site_info_obj = BaseInfo.objects.filter(is_active=True).first() link_categories = LinkCategory.objects.filter(is_active=True).prefetch_related('links') menus = MainMenu.objects.filter(visible=True, parent__isnull=True).order_by('order') - + picture_links = PictureLink.objects.filter(is_active=True).order_by('order') return { 'site_info': site_info_obj, 'link_categories': link_categories, 'menus': menus, + 'picture_links': picture_links, } diff --git a/base/migrations/0004_picturelink.py b/base/migrations/0004_picturelink.py new file mode 100644 index 0000000..fc63a6c --- /dev/null +++ b/base/migrations/0004_picturelink.py @@ -0,0 +1,31 @@ +# Generated by Django 6.0.3 on 2026-04-02 01:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0003_baseinfo_delete_footerlink'), + ] + + operations = [ + migrations.CreateModel( + name='PictureLink', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ('name', models.CharField(max_length=100, verbose_name='网站名称')), + ('picture', models.ImageField(help_text='建议尺寸:280x120', upload_to='picture/%Y/%m/%d/', verbose_name='图片链接')), + ('url', models.URLField(help_text='点击图片要访问的站点地址', verbose_name='链接地址')), + ('order', models.IntegerField(default=0, verbose_name='排序')), + ('is_active', models.BooleanField(default=True, verbose_name='是否启用')), + ], + options={ + 'verbose_name': '图片链接', + 'verbose_name_plural': '图链管理', + 'ordering': ['order', '-created_at'], + }, + ), + ] diff --git a/base/models.py b/base/models.py index fbc2299..797eaa5 100644 --- a/base/models.py +++ b/base/models.py @@ -1,3 +1,4 @@ +from django.core.cache import cache from django.core.exceptions import ValidationError from django.db import models @@ -49,6 +50,42 @@ class FriendLink(BaseEntity): return f"[{self.category.name}] {self.name}" +class PictureLink(BaseEntity): + name = models.CharField("网站名称", max_length=100) + picture = models.ImageField("图片链接", upload_to='picture/%Y/%m/%d/', help_text="建议尺寸:280x120") + url = models.URLField("链接地址", help_text="点击图片要访问的站点地址") + + # 2. 排序和状态 + order = models.IntegerField("排序", default=0) + is_active = models.BooleanField("是否启用", default=True) + + class Meta: + verbose_name = "图片链接" + verbose_name_plural = "图链管理" + ordering = ['order', '-created_at'] + + def __str__(self): + return f"{self.name}" + + def save(self, *args, **kwargs): + # 1. 逻辑判断:如果当前想要启用 + if self.is_active: + # 统计现有的启用数量(排除自己) + active_count = PictureLink.objects.filter(is_active=True).exclude(pk=self.pk).count() + + # 2. 如果已满 4 个 + if active_count >= 4: + self.is_active = False # 强制设为不生效 + + # 3. 【关键】把消息暂存起来,key 用 pk(如果是新建还没 pk,用 id(self) 临时顶替) + # 注意:这里不能直接弹窗,因为没有 request + msg_key = self.pk if self.pk else id(self) + msg = "限制生效:最多只能有 4 个启用的链接,当前对象已被自动设置为【不启用】。" + cache.set(f"picture_count_limit_{msg_key}", msg) # 缓存 30 秒 + + super().save(*args, **kwargs) + + class MainMenu(BaseEntity): # --- 基础信息 --- title = models.CharField("标题", max_length=100) diff --git a/server/settings.py b/server/settings.py index 9f2f076..de3421f 100644 --- a/server/settings.py +++ b/server/settings.py @@ -156,6 +156,25 @@ else: }, } +CACHES = { + 'default': { + # 指定使用本地内存缓存后端 + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + + # 缓存实例的名称,用于区分不同的内存存储 + # 如果只有一个 locmem 缓存,可以忽略此项,但建议设置 + 'LOCATION': 'unique-snowflake', + + # (可选) 默认超时时间,单位:秒 + 'TIMEOUT': 300, + + # (可选) 最大缓存条目数,超过后会根据 LRU 策略淘汰旧数据 + 'OPTIONS': { + 'MAX_ENTRIES': 1000, + 'CULL_FREQUENCY': 3, # 到达 MAX_ENTRIES 时,剔除 1/CULL_FREQUENCY 的数据 + } + } +} # Password validation # https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators diff --git a/static/css/style.css b/static/css/style.css index db31e69..bf61c73 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -241,7 +241,7 @@ body { } .nav-tabs .nav-item { - margin-bottom: -3px; /* 负边距,让 Tab 沉下去盖住强调线 */ + /*margin-bottom: -3px; !* 负边距,让 Tab 沉下去盖住强调线 *!*/ margin-right: 5px; } @@ -251,7 +251,7 @@ body { border-top-right-radius: 4px; /* 4. 字体大小修正 */ - font-size: 15px; + /*font-size: 15px;*/ font-weight: 600; /* 加粗 */ color: #555; /* 未选中时的文字颜色 */ @@ -364,16 +364,16 @@ body { } /* --- 自定义样式结束 --- */ - -.ad-banner { - display: flex; - justify-content: space-between; - padding: 30px 0 15px 0; +/* 可选:给图片加点样式,让它更好看 */ +.link-image { + border-radius: 8px; /* 圆角 */ + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 阴影 */ + transition: transform 0.2s; /* 悬停动画 */ } -.ad-item { - width: 280px; - height: 120px; +/* 鼠标悬停时图片微微放大 */ +.link-image:hover { + transform: scale(1.02); } .grid { diff --git a/templates/base.html b/templates/base.html index 93da50f..9015a8c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -16,7 +16,7 @@
{{ site_info.e_title }}
-CHANGBAI MOUNTAIN FIRE AND RESCUE BRIGADE
+CHANGBAI MOUNTAIN FIRE AND RESCUE BRIGADE