update. 完成分类显示与详情页显示效果

This commit is contained in:
2026-04-01 18:54:42 +08:00
parent 777ca50328
commit 414dc0ee95
23 changed files with 359 additions and 707 deletions
+14 -3
View File
@@ -83,18 +83,18 @@ class ArticleForm(forms.ModelForm):
super().__init__(*args, **kwargs)
# 核心代码:过滤 section 字段,只保留有父级的分类(即二级分类)
# 这里的 queryset 会直接作用于 Admin 的下拉选择框
self.fields['section'].queryset = MainMenu.objects.filter(parent__isnull=False, enabled=True)
self.fields['section'].queryset = MainMenu.objects.filter(parent__isnull=False, visible=True)
# 可选:优化显示格式,让用户知道是哪个主板块下的子板块
# 例如显示:"[新闻中心] 国内新闻"
self.fields['section'].label_from_instance = lambda obj: f"[{obj.parent.name}] {obj.name}"
self.fields['section'].label_from_instance = lambda obj: f"[{obj.parent.title}] {obj.title}"
# —————— 文章 ——————
@admin.register(Article)
class ArticleAdmin(ImportExportModelAdmin):
form = ArticleForm # <--- 关联自定义表单
list_display = ('title', 'section', 'author', 'is_published', 'created_at')
list_display = ('title', 'section', 'author', 'is_published', 'created_at', 'attachment_count')
list_filter = ('section', 'is_published', 'created_at')
search_fields = ('title', 'content')
date_hierarchy = 'created_at'
@@ -108,6 +108,17 @@ class ArticleAdmin(ImportExportModelAdmin):
('发布设置', {'fields': ('is_published',)}),
)
# 2. 定义显示附件数量的方法
def attachment_count(self, obj):
# 使用 count() 方法统计数量
count = obj.attachments.count()
if count == 0:
return "-" # 没有附件显示横杠
return f"📎 {count}" # 有附件显示图标和数量
# 3. 设置该列在后台的标题
attachment_count.short_description = "附件"
# —————— 通知(带签收) ——————
@admin.register(Notice)
+43 -3
View File
@@ -39,8 +39,48 @@ def seed_articles(count=20):
section = random.choice(subsections)
author = random.choice(users) if users else None
paragraphs = fake.paragraphs(nb=random.randint(3, 8))
content_html = "\n".join([f"<p>{p}</p>" for p in paragraphs])
# --- 2. 构建丰富的 HTML 内容 ---
html_parts = []
# A. 开头:一段引言或摘要
if random.random() > 0.5:
html_parts.append(f"<p><strong>【摘要】</strong> {fake.sentence(nb_words=10)}</p>")
# B. 正文第一段
html_parts.append(f"<p>{fake.paragraph(nb_sentences=4)}</p>")
# C. 随机插入图片占位符 (模拟上传的封面或插图)
# 使用 placehold.co 生成带文字的灰色占位图
img_width = random.choice([600, 800])
img_height = random.choice([300, 400])
img_text = fake.words(nb=3, unique=True)
html_parts.append(
f'<div style="text-align: center; margin: 20px 0;"><img src="https://placehold.co/{img_width}x{img_height}/23418A/FFF?text={"+".join(img_text)}" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"></div>')
# D. 正文中间段落
html_parts.append(f"<p>{fake.paragraph(nb_sentences=5)}</p>")
# E. 随机插入引用块 (增加排版层次感)
if random.random() > 0.6:
quote = fake.sentence(nb_words=8)
html_parts.append(
f'<blockquote style="border-left: 5px solid #23418A; padding: 10px 20px; margin: 20px 0; background: #f8f9fa; color: #555;"><i>"{quote}"</i></blockquote>')
# F. 随机插入无序列表 (模拟要点陈述)
if random.random() > 0.7:
list_items = "".join([f"<li>{fake.sentence()}</li>" for _ in range(3)])
html_parts.append(f"<ul style='margin: 20px 0; padding-left: 20px;'>{list_items}</ul>")
# G. 加粗和斜体混合段落
p1 = fake.paragraph(nb_sentences=2)
p2 = fake.paragraph(nb_sentences=2)
html_parts.append(f"<p>{p1} <strong style='color: #23418A;'>{fake.words(nb=4, unique=True)}</strong> {p2}</p>")
# H. 结尾
html_parts.append(f"<p>{fake.paragraph(nb_sentences=3)}</p>")
# 拼接最终 HTML
content_html = "\n".join(html_parts)
article = Article.objects.create(
title=fake.sentence(nb_words=random.randint(5, 10))[:-1],
@@ -81,4 +121,4 @@ def seed_articles(count=20):
if __name__ == "__main__":
seed_articles(30)
seed_articles(120)
+1 -1
View File
@@ -23,7 +23,7 @@ class MainMenuAdmin(ImportExportModelAdmin):
display_name.admin_order_field = 'name'
def is_enabled(self, obj):
return obj.enabled
return obj.visible
is_enabled.boolean = True
is_enabled.short_description = '启用'
+4 -2
View File
@@ -23,8 +23,9 @@ def show_footer():
}
@register.inclusion_tag('components/main_menu.html')
def show_main_menu():
@register.inclusion_tag('components/main_menu.html', takes_context=True)
def show_main_menu(context):
request = context['request']
# 1. 获取当前生效的那一条页脚数据
# 根据你之前的模型,is_active=True 的只有一条
menus = MainMenu.objects.filter(visible=True, parent__isnull=True).order_by('order')
@@ -33,4 +34,5 @@ def show_main_menu():
# 3. 将数据传递给模板
return {
'menus': menus,
'request': request,
}
+2 -1
View File
@@ -12,9 +12,10 @@ urlpatterns = [
path('about/', views.about, name='about'),
path('news/', views.news, name='news'),
path('info/', views.info, name='info'),
path('service/', views.service, name='service'),
path('news/<str:sub>.html', views.sub_news_list, name='news_list'),
path('party/', views.party, name='party'),
path('interaction/', views.interaction, name='interaction'),
path('science/', views.science, name='science'),
path('article/<int:pk>.html/', views.article_detail, name='article_detail'),
path('news/<int:pk>.html/', views.news_detail, name='news_detail'),
]
+59 -80
View File
@@ -9,7 +9,7 @@ from loguru import logger
from article.models import Article
from base.models import MainMenu
from news.models import NewsSection
from news.models import NewsSection, News
from utils.format import get_now_strftime_format
@@ -32,91 +32,35 @@ def article_detail(request, pk: int):
logger.error(traceback.format_exc())
return render(request, 'front/error/500.html')
def news_detail(request, pk: int):
"""
根据主键 (pk) 获取文章详情
"""
try:
# 使用 get_object_or_404 简化代码:如果文章不存在,自动返回 404 页面
article = get_object_or_404(News, pk=pk)
# 如果是渲染 HTML 模板
return render(request, 'front/details/news.html', {'article': article})
except (News.DoesNotExist, Http404):
return render(request, 'front/error/404.html')
except Exception as e:
logger.error(e)
logger.error(traceback.format_exc())
return render(request, 'front/error/500.html')
def index(request):
# 时间字符串
time_str = get_now_strftime_format()
# 新闻内容
news_sections = NewsSection.objects.filter(visible=True).order_by('order')
news_data = {
"key": "important",
"key": "news",
"data": {
'消防要闻': [
{'title': '习近平会见全国应急管理系统先进模范和消防忠诚卫士表彰...', 'date': '2021-11-06'},
{'title': '北京市消防救援局挂牌', 'date': '2024-06-19'},
{'title': '市消防救援局即日起开展专项行动,推出一揽子“便民利企”措施', 'date': '2025-07-01'},
{'title': '北京开展石油化工生产装置灭火救援实战演练', 'date': '2025-09-30'},
{'title': '北京市全面启动 2025 年度消防宣传月活动', 'date': '2025-11-11'},
{'title': '逛庙会学消防!厂甸大观园庙会打造消防“打卡点”', 'date': '2026-02-25'},
{'title': '“火焰蓝”守护年味!消防员坚守景区庙会护平安', 'date': '2026-02-21'},
],
'基层动态': [
{'title': '某支队开展冬季练兵比武活动', 'date': '2023-12-01'},
{'title': '社区微型消防站进行应急演练', 'date': '2023-12-05'},
{'title': '朝阳区举办消防安全知识讲座', 'date': '2024-01-15'},
],
'国务院新闻': [
{'title': '国务院安委会部署全国安全生产大检查', 'date': '2023-11-20'},
]
section.title: section.news_set.filter(is_published=True).order_by("-created_at")
for section in news_sections
}
}
# section_data = [
# {
# "key": "info",
# "data": {
# '信息公开': [
# {'title': '习近平会见全国应急管理系统先进模范和消防忠诚卫士表彰...', 'date': '2021-11-06'},
# {'title': '北京市消防救援局挂牌', 'date': '2024-06-19'},
# {'title': '市消防救援局即日起开展专项行动,推出一揽子“便民利企”措施', 'date': '2025-07-01'},
# {'title': '北京开展石油化工生产装置灭火救援实战演练', 'date': '2025-09-30'},
# {'title': '北京市全面启动 2025 年度消防宣传月活动', 'date': '2025-11-11'},
# {'title': '逛庙会学消防!厂甸大观园庙会打造消防“打卡点”', 'date': '2026-02-25'},
# {'title': '“火焰蓝”守护年味!消防员坚守景区庙会护平安', 'date': '2026-02-21'},
# ],
# },
# },
# {
# "key": "party",
# "data": {
# '党建引领': [
# {'title': '习近平会见全国应急管理系统先进模范和消防忠诚卫士表彰...', 'date': '2021-11-06'},
# {'title': '北京市消防救援局挂牌', 'date': '2024-06-19'},
# {'title': '市消防救援局即日起开展专项行动,推出一揽子“便民利企”措施', 'date': '2025-07-01'},
# {'title': '北京开展石油化工生产装置灭火救援实战演练', 'date': '2025-09-30'},
# {'title': '北京市全面启动 2025 年度消防宣传月活动', 'date': '2025-11-11'},
# {'title': '逛庙会学消防!厂甸大观园庙会打造消防“打卡点”', 'date': '2026-02-25'},
# {'title': '“火焰蓝”守护年味!消防员坚守景区庙会护平安', 'date': '2026-02-21'},
# ],
# },
# },
# {
# "key": "interaction",
# "data": {
# '互动交流': [
# {'title': '习近平会见全国应急管理系统先进模范和消防忠诚卫士表彰...', 'date': '2021-11-06'},
# {'title': '北京市消防救援局挂牌', 'date': '2024-06-19'},
# {'title': '市消防救援局即日起开展专项行动,推出一揽子“便民利企”措施', 'date': '2025-07-01'},
# {'title': '北京开展石油化工生产装置灭火救援实战演练', 'date': '2025-09-30'},
# {'title': '北京市全面启动 2025 年度消防宣传月活动', 'date': '2025-11-11'},
# {'title': '逛庙会学消防!厂甸大观园庙会打造消防“打卡点”', 'date': '2026-02-25'},
# {'title': '“火焰蓝”守护年味!消防员坚守景区庙会护平安', 'date': '2026-02-21'},
# ],
# },
# },
# {
# "key": "science",
# "data": {
# '消防科普': [
# {'title': '习近平会见全国应急管理系统先进模范和消防忠诚卫士表彰...', 'date': '2021-11-06'},
# {'title': '北京市消防救援局挂牌', 'date': '2024-06-19'},
# {'title': '市消防救援局即日起开展专项行动,推出一揽子“便民利企”措施', 'date': '2025-07-01'},
# {'title': '北京开展石油化工生产装置灭火救援实战演练', 'date': '2025-09-30'},
# {'title': '北京市全面启动 2025 年度消防宣传月活动', 'date': '2025-11-11'},
# {'title': '逛庙会学消防!厂甸大观园庙会打造消防“打卡点”', 'date': '2026-02-25'},
# {'title': '“火焰蓝”守护年味!消防员坚守景区庙会护平安', 'date': '2026-02-21'},
# ],
# },
# }
# ]
articles_qs = Article.objects.filter(is_published=True).order_by('-created_at')
main_sections = MainMenu.objects.filter(parent__isnull=True, visible=True).order_by('order').prefetch_related(
# 预取可见的子板块
@@ -184,12 +128,12 @@ def about(request):
def news(request):
sections = NewsSection.objects.filter(enabled=True).all().order_by('order')
sections = NewsSection.objects.filter(visible=True).all().order_by('order')
section_data = [
{
"key": section.code,
"data": {
section.name: section.news_set.filter(is_published=True)[:7],
section.title: section.news_set.filter(is_published=True)[:7],
}
}
for section in sections
@@ -260,6 +204,41 @@ def main_article_list(request, main):
return render(request, 'front/list.html', context=context)
def sub_news_list(request, sub: str):
logger.info(f"正在访问 {sub}")
sections = NewsSection.objects.all().order_by('order')
logger.info(f"新闻板块: {sections}")
main_section = {
'title': '新闻中心',
'code': 'news'
}
sub_section = sections.filter(code=sub).first()
logger.info(f"主板块 {main_section} 子板块 {sub_section}")
if not sub_section or not main_section:
raise Http404("不存在的板块")
context = {
'main_section': main_section,
'sub_section': sub_section,
'sections': sections,
'articles': sub_section.news_set.filter(is_published=True).order_by('-created_at')[:21]
}
return render(request, 'front/list.html', context=context)
def main_news_list(request):
logger.info("正在访问新闻中心")
news_list = News.objects.all()
news_sections = NewsSection.objects.all()
return render(request, 'front/list.html', {
'main_section': {
'title': '新闻中心',
'code': 'news'
},
'articles': news_list,
'sections': news_sections,
})
def sub_article_list(request, main: str, sub: str):
"""信息公开"""
logger.info(f"正在访问 {main}")
+5 -5
View File
@@ -11,11 +11,11 @@ from .models import NewsSection, News
# --- 1. 板块管理配置 ---
@admin.register(NewsSection)
class NewsSectionAdmin(ImportExportModelAdmin):
list_display = ('name', 'code', 'order', 'enabled') # 列表显示的字段
list_editable = ('order', 'enabled') # 允许直接在列表页修改排序和状态
search_fields = ('name', 'code') # 搜索框
prepopulated_fields = {'code': ('name',)} # 输入名称时自动生成 Slug
ordering = ('order', 'name') # 默认排序
list_display = ('title', 'code', 'order', 'visible') # 列表显示的字段
list_editable = ('order', 'visible') # 允许直接在列表页修改排序和状态
search_fields = ('title', 'code') # 搜索框
prepopulated_fields = {'code': ('title',)} # 输入名称时自动生成 Slug
ordering = ('order', 'title') # 默认排序
# --- 2. 文章管理配置 ---
@@ -0,0 +1,27 @@
# Generated by Django 6.0.3 on 2026-04-01 09:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0002_initial'),
]
operations = [
migrations.AlterModelOptions(
name='newssection',
options={'ordering': ('order', 'title'), 'verbose_name': '新闻模块', 'verbose_name_plural': '新闻模块'},
),
migrations.RenameField(
model_name='newssection',
old_name='name',
new_name='title',
),
migrations.RenameField(
model_name='newssection',
old_name='enabled',
new_name='visible',
),
]
+4 -4
View File
@@ -7,18 +7,18 @@ from users.models import User
# Create your models here.
class NewsSection(BaseEntity):
name = models.CharField('模块名称', max_length=100, unique=True)
title = models.CharField('模块名称', max_length=100, unique=True)
code = models.SlugField('唯一标识', max_length=50, unique=True, help_text='用于模板或URL识别,建议英文')
order = models.PositiveSmallIntegerField('排序权重', default=0)
enabled = models.BooleanField('启用状态', default=True)
visible = models.BooleanField('启用状态', default=True)
class Meta:
verbose_name = '新闻模块'
verbose_name_plural = '新闻模块'
ordering = ('order', 'name')
ordering = ('order', 'title')
def __str__(self):
return self.name
return self.title
class News(BaseEntity):
+53 -21
View File
@@ -19,49 +19,81 @@ fake = Faker('zh_CN')
def seed_news(count=20):
print(f"🚀 开始生成 {count} 条新闻数据...")
print(f"🚀 开始生成 {count}富文本新闻数据...")
# --- 1. 获取关联数据 ---
# 获取所有板块
sections = list(NewsSection.objects.all())
# 获取所有用户
users = list(User.objects.all())
# 检查基础数据是否存在
if not sections:
print("❌ 错误:数据库中没有找到任何板块 (NewsSection)请先添加板块。")
print("❌ 错误:数据库中没有找到任何板块 (NewsSection)")
return
if not users:
print("⚠️ 警告:数据库中没有用户 (User),作者字段将设为空。")
# --- 2. 生成数据 ---
created_count = 0
for i in range(count):
# 随机选择一个板块
section = random.choice(sections)
# 随机选择一个作者 (如果有用户)
author = random.choice(users) if users else None
# 生成富文本内容 (模拟 CKEditor 输出的 HTML)
# paragrahs 生成多段文本,join 拼接
paragraphs = fake.paragraphs(nb=random.randint(3, 6))
content_html = "\n".join([f"<p>{p}</p>" for p in paragraphs])
# --- 2. 构建丰富的 HTML 内容 ---
html_parts = []
# 创建新闻对象
# A. 开头:一段引言或摘要
if random.random() > 0.5:
html_parts.append(f"<p><strong>【摘要】</strong> {fake.sentence(nb_words=10)}</p>")
# B. 正文第一段
html_parts.append(f"<p>{fake.paragraph(nb_sentences=4)}</p>")
# C. 随机插入图片占位符 (模拟上传的封面或插图)
# 使用 placehold.co 生成带文字的灰色占位图
img_width = random.choice([600, 800])
img_height = random.choice([300, 400])
img_text = fake.words(nb=3, unique=True)
html_parts.append(
f'<div style="text-align: center; margin: 20px 0;"><img src="https://placehold.co/{img_width}x{img_height}/23418A/FFF?text={"+".join(img_text)}" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"></div>')
# D. 正文中间段落
html_parts.append(f"<p>{fake.paragraph(nb_sentences=5)}</p>")
# E. 随机插入引用块 (增加排版层次感)
if random.random() > 0.6:
quote = fake.sentence(nb_words=8)
html_parts.append(
f'<blockquote style="border-left: 5px solid #23418A; padding: 10px 20px; margin: 20px 0; background: #f8f9fa; color: #555;"><i>"{quote}"</i></blockquote>')
# F. 随机插入无序列表 (模拟要点陈述)
if random.random() > 0.7:
list_items = "".join([f"<li>{fake.sentence()}</li>" for _ in range(3)])
html_parts.append(f"<ul style='margin: 20px 0; padding-left: 20px;'>{list_items}</ul>")
# G. 加粗和斜体混合段落
p1 = fake.paragraph(nb_sentences=2)
p2 = fake.paragraph(nb_sentences=2)
html_parts.append(f"<p>{p1} <strong style='color: #23418A;'>{fake.words(nb=4, unique=True)}</strong> {p2}</p>")
# H. 结尾
html_parts.append(f"<p>{fake.paragraph(nb_sentences=3)}</p>")
# 拼接最终 HTML
content_html = "\n".join(html_parts)
# --- 3. 创建对象 ---
news = News.objects.create(
title=fake.sentence(nb_words=random.randint(4, 8))[:-1], # 生成标题并去掉末尾句号
title=fake.sentence(nb_words=random.randint(4, 8))[:-1],
section=section,
author=author,
content=content_html,
is_published=True, # 默认已发布
# 封面图留空,因为自动生成图片文件比较复杂,建议手动上传测试图
is_published=True,
# 可以在这里加上摘要字段,如果有的话
# summary=fake.paragraph(nb_sentences=1)
)
created_count += 1
print(f" - [{section.name}] {news.title}")
print(f" - [{section.title}] {news.title}")
print(f"✅ 成功生成 {created_count} 条新闻数据!")
print(f"✅ 成功生成 {created_count}富文本新闻数据!")
if __name__ == "__main__":
seed_news(30)
seed_news(120)
+1 -1
View File
@@ -47,7 +47,7 @@
{% if items %}
{% for item in items %}
<li class="news-item">
<a href="{% url "front:article_detail" pk=item.id %}"
<a href="{% url route|default:"front:article_detail" pk=item.id %}"
style="text-decoration: none;color: black;">
<div class="news-title-wrapper">
+2 -1
View File
@@ -2,7 +2,8 @@
{% for menu in menus %}
<!-- 新闻中心 -->
<li class="{% if request.resolver_match.kwargs.url == menu.url %}active{% endif %}">
<li class="{% if menu.code in request.path %}active{% endif %}">
<a href="{% url 'front:menu' main=menu.code %}">{{ menu.title }}</a>
</li>
{% endfor %}
+11 -8
View File
@@ -24,7 +24,8 @@
<h1 class="fw-bold mb-3">{{ article.title }}</h1>
<div class="text-muted small">
<span class="me-3"><i class="bi bi-clock"></i> {{ article.created_at|date:"Y-m-d H:i" }}</span>
<span class="me-3"><i class="bi bi-person"></i> {{ article.author.username|default:"管理员" }}</span>
<span class="me-3"><i
class="bi bi-person"></i> {{ article.author.username|default:"管理员" }}</span>
<span><i class="bi bi-eye"></i> 阅读数:{{ article.views|default:0 }}</span>
</div>
</div>
@@ -32,7 +33,8 @@
<!-- 封面图 (如果有) -->
{% if article.cover %}
<div class="mb-4 text-center">
<img src="{{ article.cover.url }}" alt="{{ article.title }}" class="img-fluid rounded shadow-sm" style="max-height: 400px; width: 100%; object-fit: cover;">
<img src="{{ article.cover.url }}" alt="{{ article.title }}" class="img-fluid rounded shadow-sm"
style="max-height: 400px; width: 100%; object-fit: cover;">
</div>
{% endif %}
@@ -40,17 +42,18 @@
<div class="article-content mb-5" style="line-height: 1.8; font-size: 1.1rem; color: #333;">
{{ article.content|safe }}
</div>
{% with attachments=article.attachments.all %}
<!-- 3. 附件列表 (如果有附件) -->
{% if article.attachments_list %}
{% if attachments %}
<div class="card bg-light mb-5">
<div class="card-header">
<i class="bi bi-paperclip"></i> 相关附件
</div>
<ul class="list-group list-group-flush">
{% for attachment in article.attachments_list %}
{% for attachment in attachments %}
<li class="list-group-item bg-transparent">
<a href="{{ attachment.file.url }}" target="_blank" class="text-decoration-none">
<a href="{{ attachment.file.url }}" target="_blank"
class="text-decoration-none">
<i class="bi bi-file-earmark-text"></i>
<!-- 截取文件名显示,防止太长 -->
{{ attachment.file.name|slice:"30:" }}
@@ -63,9 +66,9 @@
</ul>
</div>
{% endif %}
{% endwith %}
<!-- 4. 底部操作栏 -->
<div class="d-grid gap-4 d-sm-flex justify-content-sm-center border-top pt-4 mt-5">
<div class="d-grid gap-4 d-sm-flex justify-content-sm-center border-top pt-4 mt-5" style="gap: 30px;">
<a href="javascript:history.back()" class="btn btn-outline-secondary btn px-3">
<i class="bi bi-arrow-left"></i> 返回上一页
</a>
+58 -9
View File
@@ -1,10 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% extends 'base.html' %}
</body>
</html>
{% block title %}{{ article.section.title }} - 长白山消防救援支队{% endblock %}
{% block content %}
<div class="container mt-4">
<!-- 1. 面包屑导航 -->
<nav aria-label="breadcrumb" style="margin-left: -10px;">
<ol class="breadcrumb" style="background-color: transparent !important; padding-bottom: 0;">
<li class="breadcrumb-item"><a href="/">网站首页</a></li>
<!-- 动态显示所属板块 -->
<li class="breadcrumb-item"><a href="#">{{ article.section.parent.title }}</a></li>
<li class="breadcrumb-item"><a href="#">{{ article.section.title }}</a></li>
<li class="breadcrumb-item active" aria-current="page">文章详情</li>
</ol>
</nav>
<!-- 2. 文章主体内容 -->
<div class="row justify-content-center mt-4">
<div class="col-lg-10">
<!-- 文章头部信息 -->
<div class="text-center mb-5">
<h1 class="fw-bold mb-3">{{ article.title }}</h1>
<div class="text-muted small">
<span class="me-3"><i class="bi bi-clock"></i> {{ article.created_at|date:"Y-m-d H:i" }}</span>
<span class="me-3"><i
class="bi bi-person"></i> {{ article.author.username|default:"管理员" }}</span>
<span><i class="bi bi-eye"></i> 阅读数:{{ article.views|default:0 }}</span>
</div>
</div>
<!-- 封面图 (如果有) -->
{% if article.cover %}
<div class="mb-4 text-center">
<img src="{{ article.cover.url }}" alt="{{ article.title }}" class="img-fluid rounded shadow-sm"
style="max-height: 400px; width: 100%; object-fit: cover;">
</div>
{% endif %}
<!-- 正文内容 -->
<div class="article-content mb-5" style="line-height: 1.8; font-size: 1.1rem; color: #333;">
{{ article.content|safe }}
</div>
<!-- 4. 底部操作栏 -->
<div class="d-grid gap-4 d-sm-flex justify-content-sm-center border-top pt-4 mt-5" style="gap: 30px;">
<a href="javascript:history.back()" class="btn btn-outline-secondary btn px-3">
<i class="bi bi-arrow-left"></i> 返回上一页
</a>
<a href="/" class="btn btn-primary btn px-3">
<i class="bi bi-house-door-fill"></i> 返回首页
</a>
</div>
</div>
</div>
</div>
{% endblock %}
+2 -1
View File
@@ -42,7 +42,8 @@
{% include 'components/_carousel.html' with carousel_id=carousel.key slides=carousel.data height="300px" %}
</div>
<div class="col-md-6 col-sm-12">
{% include 'components/_news_card.html' with data=news_data.data unique_id=news_data.key %}
{% url "front:news" as more_url %}
{% include 'components/_news_card.html' with data=news_data.data unique_id=news_data.key more_url=more_url route="front:news_detail" %}
</div>
</div>
+2 -1
View File
@@ -10,7 +10,8 @@
</nav>
<div class="row" style="margin: 15px auto ">
{% for section in sections %}
<div class="col-6" style="margin: auto auto 15px">
<div class="col-6 {% if forloop.last and not sections|length|divisibleby:2 %}col-md-12{% endif %}"
style="margin: auto auto 15px">
{% url "front:sub_list" main=main_section.code sub=section.key as card_more_url %}
{% include 'components/_news_card.html' with data=section.data unique_id=section.key more_url=card_more_url %}
-168
View File
@@ -1,168 +0,0 @@
{% extends 'base.html' %}
{% block title %}互动交流 - 长白山消防救援支队{% endblock %}
{% block content %}
<div class="container">
<nav aria-label="breadcrumb" style="margin-left: -10px;">
<ol class="breadcrumb" style="background-color: transparent !important;padding-bottom: 0;">
<li class="breadcrumb-item"><a href="#">网站首页</a></li>
<li class="breadcrumb-item active" aria-current="page">互动交流</li>
</ol>
</nav>
<div class="row" style="height: 100%;margin: 15px auto;margin-right: 0 !important;">
<div class="col-3">
<ul class="list-group">
<li class="list-group-item" style="background: #23418A; color: #f5f5f5">
<h5>互动交流</h5>
</li>
{% for sec in sections %}
<li class="list-group-item">
<a href="?q={{ sec }}" style="color: #1d2124;text-decoration: none;"> {{ sec }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="col-9" style="width: 100%;padding-right: 0 !important;">
<!-- 组件开始 -->
<div class="section-header-card">
<!-- 左侧文字内容 -->
<div class="header-content">
<div class="header-title">
<span class="icon-circle"></span>
{{ q }}
</div>
<hr class="divider-line">
</div>
{# <!-- 右侧装饰色块 -->#}
{# <div class="right-decoration">#}
{# <div class="deco-top"></div>#}
{# <div class="deco-bottom"></div>#}
{# </div>#}
<ul class="list-unstyled mb-0" style="width: 100%;padding-right: 38px; margin-left: 0;">
{# items 就是当前 key 对应的列表数据,直接使用 #}
{% if data_list %}
{% for item in data_list %}
<li class="news-item">
<div class="news-title-wrapper">
<span class="bullet-point"></span>
<span class="news-title-text"
title="{{ item.title }}">{{ item.title|truncatechars:30 }}</span>
</div>
<span class="news-date">{{ item.date }}</span>
</li>
{% endfor %}
{% else %}
<li class="text-muted text-center py-2">暂无数据</li>
{% endif %}
</ul>
</div>
<!-- 组件结束 -->
<div style="display: flex; justify-content: center; margin-top: 10px;margin-bottom: -5px;">
<ul class="pagination" style="margin: auto;">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<style>
/* --- 组件核心样式 --- */
.section-header-card {
position: relative;
width: 100%;
{#max-width: 800px; /* 模拟图片中的长宽比 */#} min-height: 720px; /* 设置一个保底高度 */
height: auto; /* 高度随内容增加而增加 */
background-color: #ffffff;
border-radius: 12px; /* 大圆角 */
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); /* 柔和阴影 */
overflow: hidden; /* 确保右侧装饰块不溢出圆角 */
display: flex;
flex-direction: column;
align-items: center;
padding-left: 30px;
{#justify-content: space-between;#}
}
/* 左侧内容区域 */
.header-content {
z-index: 2; /* 确保内容在装饰块之上 */
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
}
/* 标题部分 */
.header-title {
font-size: 20px;
font-weight: 700;
color: #23408A; /* 深蓝色文字 */
margin-bottom: 12px;
display: flex;
align-items: center;
}
/* 空心圆圈图标 */
.icon-circle {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #23408A; /* 边框颜色同文字 */
border-radius: 50%;
margin-right: 10px;
box-sizing: border-box; /* 确保边框包含在宽高内 */
}
/* 分割线 */
.divider-line {
width: 95%; /* 线条长度,不完全贯穿 */
height: 2px;
background-color: #23408A; /* 线条颜色比文字稍浅一点或保持一致 */
border: none;
margin: 0;
}
/* --- 右侧装饰块 --- */
.right-decoration {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 60px; /* 装饰块宽度 */
display: flex;
flex-direction: column;
}
/* 上方深色块 */
.deco-top {
flex: 1; /* 占据上半部分 */
background-color: #5a6da3; /* 深蓝灰色 */
min-height: 50%;
}
/* 下方浅色块 */
.deco-bottom {
flex: 1; /* 占据下半部分 */
background-color: #eef4ff; /* 极浅的蓝色 */
min-height: 50%;
}
</style>
{% endblock %}
+33 -1
View File
@@ -4,14 +4,38 @@
<div class="container">
<nav aria-label="breadcrumb" style="margin-left: -10px;">
<ol class="breadcrumb" style="background-color: transparent !important;padding-bottom: 0;">
<li class="breadcrumb-item"><a href="#">网站首页</a></li>
<li class="breadcrumb-item"><a href="/">网站首页</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ main_section.title }}</li>
<li class="breadcrumb-item active" aria-current="page">{{ sub_section.title }}</li>
</ol>
</nav>
<div class="row" style="height: 100%;margin: 15px auto;margin-right: 0 !important;">
<div class="col-3">
<ul class="list-group">
{% if request.path|slice:":6" == '/news/' %}
<li class="list-group-item" style="background: #23418A; color: #f5f5f5">
<a href="{% url "front:news" %}"
style="color: #F5F5F5;text-decoration: none;">
<h5 style="margin: 0; padding: 0; line-height: 1.2;">{{ main_section.title }}</h5>
</a>
</li>
{% for sec in sections %}
<!-- 核心修改:判断当前路径是否包含该子栏目的 code -->
{% if request.path|slice:":6" == '/news/' and sec.code in request.path %}
<li class="list-group-item" style="background: #e9ecef;"> <!-- 选中样式:灰色背景 -->
<a href="{% url "front:news_list" sub=sec.code %}"
style="color: #23418A; text-decoration: none; font-weight: bold;"> {{ sec.title }}</a>
</li>
{% else %}
<li class="list-group-item">
<a href="{% url "front:news_list" sub=sec.code %}"
style="color: #1d2124; text-decoration: none;"> {{ sec.title }}</a>
</li>
{% endif %}
{% endfor %}
{% else %}
<li class="list-group-item" style="background: #23418A; color: #f5f5f5">
<a href="{% url "front:main_list" main=main_section.code %}"
style="color: #F5F5F5;text-decoration: none;">
@@ -19,11 +43,19 @@
</a>
</li>
{% for sec in sections %}
{% if sec.code in request.path %}
<li class="list-group-item" style="background: #e9ecef;">
<a href="{% url "front:sub_list" main=main_section.code sub=sec.code %}"
style="color: #23418A; text-decoration: none; font-weight: bold;"> {{ sec.title }}</a>
</li>
{% else %}
<li class="list-group-item">
<a href="{% url "front:sub_list" main=main_section.code sub=sec.code %}"
style="color: #1d2124; text-decoration: none;"> {{ sec.title }}</a>
</li>
{% endif %}
{% endfor %}
{% endif %}
</ul>
</div>
+3 -2
View File
@@ -7,14 +7,15 @@
<nav aria-label="breadcrumb" style="margin-left: -10px;">
<ol class="breadcrumb" style="background-color: transparent !important;padding-bottom: 0;">
<li class="breadcrumb-item"><a href="#">网站首页</a></li>
<li class="breadcrumb-item"><a href="/">网站首页</a></li>
<li class="breadcrumb-item active" aria-current="page">新闻中心</li>
</ol>
</nav>
<div style="margin: 0 5px;">
{% for section in sections %}
<div class="row" style="margin: auto auto 15px;">
{% include 'components/_news_card.html' with data=section.data unique_id=section.key %}
{% url "front:news_list" sub=section.key as more_url %}
{% include 'components/_news_card.html' with data=section.data unique_id=section.key more_url=more_url route="front:news_detail" %}
</div>
{% endfor %}
</div>
-23
View File
@@ -1,23 +0,0 @@
{% extends 'base.html' %}
{% block title %}党建引领 - 长白山消防救援支队{% endblock %}
{% block content %}
<div class="container">
<nav aria-label="breadcrumb" style="margin-left: -10px;">
<ol class="breadcrumb" style="background-color: transparent !important;padding-bottom: 0;">
<li class="breadcrumb-item"><a href="#">网站首页</a></li>
<li class="breadcrumb-item active" aria-current="page">党建引领</li>
</ol>
</nav>
<div class="row" style="margin: auto;">
{% for section in sections %}
<div class="col-6" style="margin: auto auto 15px;">
{% if forloop.counter == 2 %}
{% include 'components/_carousel.html' with carousel_id=section.key slides=section.data height="300px" %}
{% else %}
{% include 'components/_news_card.html' with data=section.data unique_id=section.key %}
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endblock %}
-168
View File
@@ -1,168 +0,0 @@
{% extends 'base.html' %}
{% block title %}消防科普 - 长白山消防救援支队{% endblock %}
{% block content %}
<div class="container">
<nav aria-label="breadcrumb" style="margin-left: -10px;">
<ol class="breadcrumb" style="background-color: transparent !important;padding-bottom: 0;">
<li class="breadcrumb-item"><a href="#">网站首页</a></li>
<li class="breadcrumb-item active" aria-current="page">消防科普</li>
</ol>
</nav>
<div class="row" style="height: 100%;margin: 15px auto;margin-right: 0 !important;">
<div class="col-3">
<ul class="list-group">
<li class="list-group-item" style="background: #23418A; color: #f5f5f5">
<h5>消防科普</h5>
</li>
{% for sec in sections %}
<li class="list-group-item">
<a href="?q={{ sec }}" style="color: #1d2124;text-decoration: none;"> {{ sec }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="col-9" style="width: 100%;padding-right: 0 !important;">
<!-- 组件开始 -->
<div class="section-header-card">
<!-- 左侧文字内容 -->
<div class="header-content">
<div class="header-title">
<span class="icon-circle"></span>
{{ q }}
</div>
<hr class="divider-line">
</div>
{# <!-- 右侧装饰色块 -->#}
{# <div class="right-decoration">#}
{# <div class="deco-top"></div>#}
{# <div class="deco-bottom"></div>#}
{# </div>#}
<ul class="list-unstyled mb-0" style="width: 100%;padding-right: 38px; margin-left: 0;">
{# items 就是当前 key 对应的列表数据,直接使用 #}
{% if data_list %}
{% for item in data_list %}
<li class="news-item">
<div class="news-title-wrapper">
<span class="bullet-point"></span>
<span class="news-title-text"
title="{{ item.title }}">{{ item.title|truncatechars:30 }}</span>
</div>
<span class="news-date">{{ item.date }}</span>
</li>
{% endfor %}
{% else %}
<li class="text-muted text-center py-2">暂无数据</li>
{% endif %}
</ul>
</div>
<!-- 组件结束 -->
<div style="display: flex; justify-content: center; margin-top: 10px;margin-bottom: -5px;">
<ul class="pagination" style="margin: auto;">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<style>
/* --- 组件核心样式 --- */
.section-header-card {
position: relative;
width: 100%;
{#max-width: 800px; /* 模拟图片中的长宽比 */#} min-height: 720px; /* 设置一个保底高度 */
height: auto; /* 高度随内容增加而增加 */
background-color: #ffffff;
border-radius: 12px; /* 大圆角 */
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); /* 柔和阴影 */
overflow: hidden; /* 确保右侧装饰块不溢出圆角 */
display: flex;
flex-direction: column;
align-items: center;
padding-left: 30px;
{#justify-content: space-between;#}
}
/* 左侧内容区域 */
.header-content {
z-index: 2; /* 确保内容在装饰块之上 */
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
}
/* 标题部分 */
.header-title {
font-size: 20px;
font-weight: 700;
color: #23408A; /* 深蓝色文字 */
margin-bottom: 12px;
display: flex;
align-items: center;
}
/* 空心圆圈图标 */
.icon-circle {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #23408A; /* 边框颜色同文字 */
border-radius: 50%;
margin-right: 10px;
box-sizing: border-box; /* 确保边框包含在宽高内 */
}
/* 分割线 */
.divider-line {
width: 95%; /* 线条长度,不完全贯穿 */
height: 2px;
background-color: #23408A; /* 线条颜色比文字稍浅一点或保持一致 */
border: none;
margin: 0;
}
/* --- 右侧装饰块 --- */
.right-decoration {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 60px; /* 装饰块宽度 */
display: flex;
flex-direction: column;
}
/* 上方深色块 */
.deco-top {
flex: 1; /* 占据上半部分 */
background-color: #5a6da3; /* 深蓝灰色 */
min-height: 50%;
}
/* 下方浅色块 */
.deco-bottom {
flex: 1; /* 占据下半部分 */
background-color: #eef4ff; /* 极浅的蓝色 */
min-height: 50%;
}
</style>
{% endblock %}
-169
View File
@@ -1,169 +0,0 @@
{% extends 'base.html' %}
{% block title %}办事服务 - 长白山消防救援支队{% endblock %}
{% block content %}
<div class="container">
<nav aria-label="breadcrumb" style="margin-left: -10px;">
<ol class="breadcrumb" style="background-color: transparent !important;padding-bottom: 0;">
<li class="breadcrumb-item"><a href="#">网站首页</a></li>
<li class="breadcrumb-item active" aria-current="page">办事服务</li>
</ol>
</nav>
<div class="row" style="height: 100%;margin: auto;margin-right: 0 !important;">
<div class="col-3">
<ul class="list-group">
<li class="list-group-item" style="background: #23418A; color: #f5f5f5">
<h5>办事服务</h5>
</li>
{% for sec in sections %}
<li class="list-group-item">
<a href="?q={{ sec }}" style="color: #1d2124;text-decoration: none;"> {{ sec }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="col-9" style="width: 100%;padding-right: 0 !important;">
<!-- 组件开始 -->
<div class="section-header-card">
<!-- 左侧文字内容 -->
<div class="header-content">
<div class="header-title">
<span class="icon-circle"></span>
{{ q }}
</div>
<hr class="divider-line">
</div>
{# <!-- 右侧装饰色块 -->#}
{# <div class="right-decoration">#}
{# <div class="deco-top"></div>#}
{# <div class="deco-bottom"></div>#}
{# </div>#}
<ul class="list-unstyled mb-0" style="width: 100%;padding-right: 38px; margin-left: 0;">
{# items 就是当前 key 对应的列表数据,直接使用 #}
{% if data_list %}
{% for item in data_list %}
<li class="news-item">
<div class="news-title-wrapper">
<span class="bullet-point"></span>
<span class="news-title-text"
title="{{ item.title }}">{{ item.title|truncatechars:30 }}</span>
</div>
<span class="news-date">{{ item.date }}</span>
</li>
{% endfor %}
{% else %}
<li class="text-muted text-center py-2">暂无数据</li>
{% endif %}
</ul>
</div>
<!-- 组件结束 -->
<div style="display: flex; justify-content: center; margin-top: 10px;margin-bottom: -5px;">
<ul class="pagination" style="margin: 15px auto;">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<style>
/* --- 组件核心样式 --- */
.section-header-card {
position: relative;
width: 100%;
{#max-width: 800px; /* 模拟图片中的长宽比 */#} min-height: 720px; /* 设置一个保底高度 */
height: auto; /* 高度随内容增加而增加 */
background-color: #ffffff;
border-radius: 12px; /* 大圆角 */
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); /* 柔和阴影 */
overflow: hidden; /* 确保右侧装饰块不溢出圆角 */
display: flex;
flex-direction: column;
align-items: center;
padding-left: 30px;
{#justify-content: space-between;#}
}
/* 左侧内容区域 */
.header-content {
z-index: 2; /* 确保内容在装饰块之上 */
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
}
/* 标题部分 */
.header-title {
font-size: 20px;
font-weight: 700;
color: #23408A; /* 深蓝色文字 */
margin-bottom: 12px;
display: flex;
align-items: center;
}
/* 空心圆圈图标 */
.icon-circle {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid #23408A; /* 边框颜色同文字 */
border-radius: 50%;
margin-right: 10px;
box-sizing: border-box; /* 确保边框包含在宽高内 */
}
/* 分割线 */
.divider-line {
width: 95%; /* 线条长度,不完全贯穿 */
height: 2px;
background-color: #23408A; /* 线条颜色比文字稍浅一点或保持一致 */
border: none;
margin: 0;
}
/* --- 右侧装饰块 --- */
.right-decoration {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 60px; /* 装饰块宽度 */
display: flex;
flex-direction: column;
}
/* 上方深色块 */
.deco-top {
flex: 1; /* 占据上半部分 */
background-color: #5a6da3; /* 深蓝灰色 */
min-height: 50%;
}
/* 下方浅色块 */
.deco-bottom {
flex: 1; /* 占据下半部分 */
background-color: #eef4ff; /* 极浅的蓝色 */
min-height: 50%;
}
</style>
{% endblock %}
+4 -4
View File
@@ -50,8 +50,8 @@ def upload_image_and_rename(instance, filename):
file_obj = None
# 遍历 instance 的所有属性,找到第一个 FileField 类型的值
for field in instance._meta.get_fields():
if hasattr(field, 'upload_to') and hasattr(instance, field.name):
val = getattr(instance, field.name)
if hasattr(field, 'upload_to') and hasattr(instance, field.title):
val = getattr(instance, field.title)
if val and hasattr(val, 'chunks'): # 确认是文件对象
file_obj = val
break
@@ -79,8 +79,8 @@ def upload_file_and_rename(instance, filename):
file_obj = None
# 同样的通用查找逻辑
for field in instance._meta.get_fields():
if hasattr(field, 'upload_to') and hasattr(instance, field.name):
val = getattr(instance, field.name)
if hasattr(field, 'upload_to') and hasattr(instance, field.title):
val = getattr(instance, field.title)
if val and hasattr(val, 'chunks'):
file_obj = val
break