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 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):
+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):
@@ -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,
}
+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.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)
+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
# 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 {
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 {
+49
View File
@@ -159,10 +159,59 @@
</div>
</div>
</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/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 %}
</body>
+15 -13
View File
@@ -5,6 +5,7 @@
{% block title %}首页 - {% endblock %}
{% block content %}
<div class="container">
<!-- 顶部横幅 -->
<div class="row d-none d-md-block">
<div class="col-12">
@@ -48,20 +49,20 @@
</div>
<!-- 广告横幅 -->
<div class="ad-banner row">
<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="我为十五五规划献策">
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
<img class="ad-item" src="/media/upload/images/2026/03/25/img_1.png" alt="精彩火焰蓝">
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
<img class="ad-item" src="/media/upload/images/2026/03/25/img_2.png" alt="电动自行车安全">
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
<img class="ad-item" src="/media/upload/images/2026/03/25/img_3.png" alt="消防安全培训">
<div class="row g-4 justify-content-start mt-3">
{% for link in picture_links %}
<!--
col-6: 手机端一行2个
col-md-4: 平板一行3个
col-lg-3: 电脑端一行4个 (25%)
-->
<div class="col-6 col-md-4 col-lg-3">
<a href="{{ link.url }}" class="d-block">
<!-- img-fluid 让图片宽度 100% 自适应父容器 -->
<img class="img-fluid" src="{{ link.picture.url }}" alt="{{ link.name }}">
</a>
</div>
{% endfor %}
</div>
@@ -81,4 +82,5 @@
{% endif %}
{% endfor %}
</div>
</div>
{% 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>