update. 优化模板结构
This commit is contained in:
+22
-2
@@ -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):
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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 %}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>长白山消防支队</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user