update. 优化模板结构

This commit is contained in:
2026-04-02 11:01:25 +08:00
parent 66dd4916d1
commit 784c8bbb6e
10 changed files with 241 additions and 128 deletions
+22 -2
View File
@@ -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 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) 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) @admin.register(BaseInfo)
class BaseInfoAdmin(ImportExportModelAdmin): class BaseInfoAdmin(ImportExportModelAdmin):
+3 -2
View File
@@ -1,4 +1,4 @@
from base.models import BaseInfo, LinkCategory, MainMenu from base.models import BaseInfo, LinkCategory, MainMenu, PictureLink
def site_info(request): def site_info(request):
@@ -8,9 +8,10 @@ def site_info(request):
site_info_obj = BaseInfo.objects.filter(is_active=True).first() site_info_obj = BaseInfo.objects.filter(is_active=True).first()
link_categories = LinkCategory.objects.filter(is_active=True).prefetch_related('links') link_categories = LinkCategory.objects.filter(is_active=True).prefetch_related('links')
menus = MainMenu.objects.filter(visible=True, parent__isnull=True).order_by('order') menus = MainMenu.objects.filter(visible=True, parent__isnull=True).order_by('order')
picture_links = PictureLink.objects.filter(is_active=True).order_by('order')
return { return {
'site_info': site_info_obj, 'site_info': site_info_obj,
'link_categories': link_categories, 'link_categories': link_categories,
'menus': menus, 'menus': menus,
'picture_links': picture_links,
} }
+31
View File
@@ -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'],
},
),
]
+37
View File
@@ -1,3 +1,4 @@
from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
@@ -49,6 +50,42 @@ class FriendLink(BaseEntity):
return f"[{self.category.name}] {self.name}" 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): class MainMenu(BaseEntity):
# --- 基础信息 --- # --- 基础信息 ---
title = models.CharField("标题", max_length=100) title = models.CharField("标题", max_length=100)
+19
View File
@@ -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 # Password validation
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
+10 -10
View File
@@ -241,7 +241,7 @@ body {
} }
.nav-tabs .nav-item { .nav-tabs .nav-item {
margin-bottom: -3px; /* 负边距,让 Tab 沉下去盖住强调线 */ /*margin-bottom: -3px; !* 负边距,让 Tab 沉下去盖住强调线 *!*/
margin-right: 5px; margin-right: 5px;
} }
@@ -251,7 +251,7 @@ body {
border-top-right-radius: 4px; border-top-right-radius: 4px;
/* 4. 字体大小修正 */ /* 4. 字体大小修正 */
font-size: 15px; /*font-size: 15px;*/
font-weight: 600; /* 加粗 */ font-weight: 600; /* 加粗 */
color: #555; /* 未选中时的文字颜色 */ color: #555; /* 未选中时的文字颜色 */
@@ -364,16 +364,16 @@ body {
} }
/* --- 自定义样式结束 --- */ /* --- 自定义样式结束 --- */
/* 可选:给图片加点样式,让它更好看 */
.ad-banner { .link-image {
display: flex; border-radius: 8px; /* 圆角 */
justify-content: space-between; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 阴影 */
padding: 30px 0 15px 0; transition: transform 0.2s; /* 悬停动画 */
} }
.ad-item { /* 鼠标悬停时图片微微放大 */
width: 280px; .link-image:hover {
height: 120px; transform: scale(1.02);
} }
.grid { .grid {
+49
View File
@@ -159,10 +159,59 @@
</div> </div>
</div> </div>
</footer> </footer>
<button id="backToTop"
class="btn btn-primary rounded-circle shadow d-none"
style="position: fixed; bottom: 30px; right: 30px; width: 50px; height: 50px; z-index: 1000; font-size: 20px;"
title="回到顶部">
</button>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script> <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script> <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<!-- 2. JavaScript 逻辑 -->
<script>
// 获取按钮元素
const backToTopBtn = document.getElementById("backToTop");
// 监听页面滚动事件
window.addEventListener("scroll", function () {
// 当页面向下滚动超过 300px 时显示按钮,否则隐藏
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
backToTopBtn.classList.remove("d-none");
// 可选:添加淡入动画类
backToTopBtn.classList.add("fade-in");
} else {
backToTopBtn.classList.add("d-none");
backToTopBtn.classList.remove("fade-in");
}
});
// 监听按钮点击事件
backToTopBtn.addEventListener("click", function () {
// 平滑滚动到顶部
window.scrollTo({
top: 0,
behavior: "smooth"
});
});
</script>
<!-- 3. 可选:添加简单的淡入淡出 CSS 动画 -->
<style>
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
<!-- 页面底部脚本 --> <!-- 页面底部脚本 -->
{% block extra_js %}{% endblock %} {% block extra_js %}{% endblock %}
</body> </body>
+15 -13
View File
@@ -5,6 +5,7 @@
{% block title %}首页 - {% endblock %} {% block title %}首页 - {% endblock %}
{% block content %} {% block content %}
<div class="container">
<!-- 顶部横幅 --> <!-- 顶部横幅 -->
<div class="row d-none d-md-block"> <div class="row d-none d-md-block">
<div class="col-12"> <div class="col-12">
@@ -48,20 +49,20 @@
</div> </div>
<!-- 广告横幅 --> <div class="row g-4 justify-content-start mt-3">
<div class="ad-banner row"> {% for link in picture_links %}
<div class="col-md-3 col-sm-6 col-xs-12"> <!--
<img class="ad-item" src="/media/upload/images/2026/03/25/img.png" alt="我为十五五规划献策"> col-6: 手机端一行2个
</div> col-md-4: 平板一行3个
<div class="col-md-3 col-sm-6 col-xs-12"> col-lg-3: 电脑端一行4个 (25%)
<img class="ad-item" src="/media/upload/images/2026/03/25/img_1.png" alt="精彩火焰蓝"> -->
</div> <div class="col-6 col-md-4 col-lg-3">
<div class="col-md-3 col-sm-6 col-xs-12"> <a href="{{ link.url }}" class="d-block">
<img class="ad-item" src="/media/upload/images/2026/03/25/img_2.png" alt="电动自行车安全"> <!-- img-fluid 让图片宽度 100% 自适应父容器 -->
</div> <img class="img-fluid" src="{{ link.picture.url }}" alt="{{ link.name }}">
<div class="col-md-3 col-sm-6 col-xs-12"> </a>
<img class="ad-item" src="/media/upload/images/2026/03/25/img_3.png" alt="消防安全培训">
</div> </div>
{% endfor %}
</div> </div>
@@ -81,4 +82,5 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div>
{% endblock %} {% endblock %}
-35
View File
@@ -1,35 +0,0 @@
{% load static %}
<header class="site-header">
<div class="header-top">
<div class="logo-container">
<img src="{% static 'images/logo.png' %}" alt="长白山消防救援支队" class="logo">
<h1>长白山消防救援支队</h1>
<p>CHANGBAI MOUNTAIN FIRE AND RESCUE BRIGADE</p>
</div>
<div class="top-links">
<a href="#" aria-label="搜索"><i class="icon-search"></i></a>
<a href="#" aria-label="用户中心"><i class="icon-user"></i></a>
<a href="#" aria-label="消息"><i class="icon-message"></i></a>
<a href="#" aria-label="登录"><i class="icon-login"></i></a>
</div>
</div>
<!-- 导航栏 -->
<nav class="main-navigation">
<ul class="nav-list">
<li><a href="{% url 'index' %}" class="{% if request.resolver_match.url_name == 'index' %}active{% endif %}">网站首页</a></li>
<li><a href="{% url 'about' %}" class="{% if request.resolver_match.url_name == 'about' %}active{% endif %}">单位简介</a></li>
<li><a href="{% url 'news' %}" class="{% if request.resolver_match.url_name == 'news' %}active{% endif %}">新闻中心</a></li>
<li><a href="{% url 'info' %}" class="{% if request.resolver_match.url_name == 'info' %}active{% endif %}">信息公开</a></li>
<li><a href="{% url 'service' %}" class="{% if request.resolver_match.url_name == 'service' %}active{% endif %}">办事服务</a></li>
<li><a href="{% url 'party' %}" class="{% if request.resolver_match.url_name == 'party' %}active{% endif %}">党建引领</a></li>
<li><a href="{% url 'interaction' %}" class="{% if request.resolver_match.url_name == 'interaction' %}active{% endif %}">互动交流</a></li>
<li><a href="{% url 'science' %}" class="{% if request.resolver_match.url_name == 'science' %}active{% endif %}">消防科普</a></li>
</ul>
</nav>
<!-- 轮播图/大背景图 -->
<div class="hero-banner">
<img src="{% static 'images/banner.jpg' %}" alt="雪地消防员" class="banner-img">
</div>
</header>
-11
View File
@@ -1,11 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>长白山消防支队</title>
</head>
<body>
</body>
</html>