init. 初始化项目,并初步完成前端模板样式
@@ -0,0 +1,19 @@
|
||||
### Django template
|
||||
*.log
|
||||
*.pot
|
||||
*.pyc
|
||||
__pycache__/
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
media
|
||||
/venv/
|
||||
/db/*
|
||||
/logs/*
|
||||
/deploy/
|
||||
/CONTAINER_ALREADY_STARTED_PLACEHOLDER
|
||||
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
|
||||
# in your Git repository. Update and uncomment the following line accordingly.
|
||||
# <django-project-name>/staticfiles/
|
||||
*/tests.py
|
||||
Dockerfile
|
||||
@@ -0,0 +1,18 @@
|
||||
/db/*
|
||||
/logs/*
|
||||
/deploy/
|
||||
celerybeat-schedule
|
||||
config/supervisord.pid
|
||||
config/supervisord.sock
|
||||
.idea/
|
||||
/.idea/
|
||||
/.idea/*
|
||||
/templates/
|
||||
/templates/*
|
||||
**/__pycache__
|
||||
**/.DS_Store
|
||||
/venv/
|
||||
/sites/
|
||||
/internal/
|
||||
/sites/*
|
||||
/internal/*
|
||||
@@ -0,0 +1,138 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
|
||||
from article.models import NoticeReceipt, Notice, Article, Carousel, Section
|
||||
from users.models import User
|
||||
|
||||
|
||||
# —————— 板块 ——————
|
||||
@admin.register(Section)
|
||||
class SectionAdmin(admin.ModelAdmin):
|
||||
list_display = ('display_name', 'code', 'parent', 'is_enabled', 'order')
|
||||
list_filter = ('parent', 'enabled')
|
||||
search_fields = ('name', 'code')
|
||||
ordering = ('order', 'name')
|
||||
list_editable = ('order',)
|
||||
fieldsets = (
|
||||
('基本信息', {
|
||||
'fields': ('name', 'code', 'parent', 'enabled', 'order')
|
||||
}),
|
||||
)
|
||||
|
||||
def display_name(self, obj):
|
||||
"""在列表中显示层级缩进"""
|
||||
if obj.is_sub:
|
||||
return f" └─ {obj.name}" # 使用全角空格+符号模拟缩进
|
||||
return obj.name
|
||||
display_name.short_description = '板块名称'
|
||||
display_name.admin_order_field = 'name'
|
||||
|
||||
def is_enabled(self, obj):
|
||||
return obj.enabled
|
||||
is_enabled.boolean = True
|
||||
is_enabled.short_description = '启用'
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
"""限制 parent 只能选择主板块(无父级的板块)"""
|
||||
if db_field.name == "parent":
|
||||
kwargs["queryset"] = Section.objects.filter(parent__isnull=True)
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""优化查询,避免 N+1"""
|
||||
return super().get_queryset(request).select_related('parent')
|
||||
|
||||
|
||||
# —————— 轮播图 ——————
|
||||
@admin.register(Carousel)
|
||||
class CarouselAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'image_thumb', 'link_url', 'order', 'active')
|
||||
list_editable = ('order', 'active')
|
||||
ordering = ('order',)
|
||||
|
||||
def image_thumb(self, obj):
|
||||
if obj.image:
|
||||
return format_html('<image src="{}" width="60" height="auto" />', obj.image.url)
|
||||
return "无图"
|
||||
image_thumb.short_description = "预览"
|
||||
|
||||
|
||||
# —————— 文章 ——————
|
||||
@admin.register(Article)
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'section', 'author', 'is_published', 'created_at')
|
||||
list_filter = ('section', 'is_published', 'created_at')
|
||||
search_fields = ('title', 'content')
|
||||
date_hierarchy = 'created_at'
|
||||
autocomplete_fields = ('author',) # 需要 UserAdmin 支持搜索
|
||||
|
||||
fieldsets = (
|
||||
('基本信息', {'fields': ('title', 'section', 'cover')}),
|
||||
('内容', {'fields': ('content',)}),
|
||||
('发布设置', {'fields': ('is_published',)}),
|
||||
)
|
||||
|
||||
|
||||
# —————— 通知(带签收) ——————
|
||||
@admin.register(Notice)
|
||||
class NoticeAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'title',
|
||||
'publisher',
|
||||
'require_sign',
|
||||
'is_published',
|
||||
'signed_count',
|
||||
'total_target_users',
|
||||
'created_at'
|
||||
)
|
||||
list_filter = ('require_sign', 'is_published', 'created_at', 'target_departments')
|
||||
search_fields = ('title', 'content')
|
||||
filter_horizontal = ('target_departments',)
|
||||
readonly_fields = ('created_at', 'updated_at', 'publisher')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
fieldsets = (
|
||||
('基本信息', {
|
||||
'fields': ('title', 'content', 'is_published')
|
||||
}),
|
||||
('签收设置', {
|
||||
'fields': ('require_sign', 'target_departments'),
|
||||
'description': '勾选“是否需要签收”后,可指定接收部门。未指定则默认全员可见但不可签收。'
|
||||
}),
|
||||
('时间信息', {
|
||||
'fields': ('created_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not change:
|
||||
obj.publisher = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
def signed_count(self, obj):
|
||||
return obj.receipts.count()
|
||||
signed_count.short_description = "已签收人数"
|
||||
|
||||
def total_target_users(self, obj):
|
||||
if obj.target_departments.exists():
|
||||
count = User.objects.filter(
|
||||
dept__in=obj.target_departments.all(),
|
||||
is_active=True
|
||||
).count()
|
||||
else:
|
||||
count = User.objects.filter(is_active=True).count()
|
||||
return count
|
||||
total_target_users.short_description = "应签收人数"
|
||||
|
||||
|
||||
# —————— 签收记录 ——————
|
||||
@admin.register(NoticeReceipt)
|
||||
class NoticeReceiptAdmin(admin.ModelAdmin):
|
||||
list_display = ('notice', 'user', 'signed_at')
|
||||
list_filter = ('signed_at', 'notice__title')
|
||||
search_fields = ('user__cname', 'user__username', 'notice__title')
|
||||
readonly_fields = ('notice', 'user', 'signed_at')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False # 禁止手动添加
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ArticleConfig(AppConfig):
|
||||
name = 'article'
|
||||
verbose_name = '文章管理'
|
||||
@@ -0,0 +1,88 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-24 10:31
|
||||
|
||||
import django_ckeditor_5.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Article',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200, verbose_name='标题')),
|
||||
('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='正文')),
|
||||
('cover', models.ImageField(blank=True, upload_to='articles/', verbose_name='封面图')),
|
||||
('is_published', models.BooleanField(default=True, verbose_name='已发布')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '文章',
|
||||
'verbose_name_plural': '文章管理',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Carousel',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100, verbose_name='轮播标题')),
|
||||
('image', models.ImageField(upload_to='carousel/', verbose_name='图片')),
|
||||
('link_url', models.URLField(blank=True, verbose_name='跳转链接')),
|
||||
('order', models.IntegerField(default=0, verbose_name='排序')),
|
||||
('active', models.BooleanField(default=True, verbose_name='启用')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '轮播图',
|
||||
'verbose_name_plural': '轮播管理',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Notice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200, verbose_name='通知标题')),
|
||||
('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='通知内容')),
|
||||
('require_sign', models.BooleanField(default=False, verbose_name='是否需要签收')),
|
||||
('is_published', models.BooleanField(default=True, verbose_name='已发布')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='发布时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '通知',
|
||||
'verbose_name_plural': '通知管理',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NoticeReceipt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('signed_at', models.DateTimeField(auto_now_add=True, verbose_name='签收时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '签收记录',
|
||||
'verbose_name_plural': '签收管理',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Section',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, unique=True, verbose_name='板块名称')),
|
||||
('code', models.SlugField(help_text='用于模板识别,如 news, notice', unique=True, verbose_name='板块代码')),
|
||||
('description', models.TextField(blank=True, verbose_name='描述')),
|
||||
('order', models.IntegerField(default=0, verbose_name='排序')),
|
||||
('enabled', models.BooleanField(default=True, verbose_name='启用')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '板块',
|
||||
'verbose_name_plural': '板块管理',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-24 10:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('article', '0001_initial'),
|
||||
('users', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='author',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='作者'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='notice',
|
||||
name='publisher',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='发布人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='notice',
|
||||
name='target_departments',
|
||||
field=models.ManyToManyField(blank=True, to='users.department', verbose_name='接收部门'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='noticereceipt',
|
||||
name='notice',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='receipts', to='article.notice'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='noticereceipt',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='签收人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='section',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='article.section', verbose_name='所属板块'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='noticereceipt',
|
||||
unique_together={('notice', 'user')},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-24 13:24
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('article', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='section',
|
||||
options={'ordering': ('order', 'name'), 'verbose_name': '板块', 'verbose_name_plural': '板块管理'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='section',
|
||||
name='description',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='section',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subsections', to='article.section', verbose_name='所属主板块'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='section',
|
||||
name='code',
|
||||
field=models.SlugField(help_text='用于模板或URL识别,建议英文', unique=True, verbose_name='唯一标识'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='section',
|
||||
name='enabled',
|
||||
field=models.BooleanField(default=True, verbose_name='启用状态'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='section',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, unique=True, verbose_name='板块名称'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='section',
|
||||
name='order',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='排序权重'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,116 @@
|
||||
from django.db import models
|
||||
from django_ckeditor_5.fields import CKEditor5Field
|
||||
|
||||
from users.models import User, Department
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
class Section(models.Model):
|
||||
name = models.CharField('板块名称', max_length=100, unique=True)
|
||||
code = models.SlugField('唯一标识', max_length=50, unique=True, help_text='用于模板或URL识别,建议英文')
|
||||
parent = models.ForeignKey(
|
||||
'self',
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='subsections',
|
||||
verbose_name='所属主板块'
|
||||
)
|
||||
order = models.PositiveSmallIntegerField('排序权重', default=0)
|
||||
enabled = models.BooleanField('启用状态', default=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '板块'
|
||||
verbose_name_plural = '板块管理'
|
||||
ordering = ('order', 'name')
|
||||
|
||||
def __str__(self):
|
||||
if self.parent:
|
||||
return f"{self.parent.name} → {self.name}"
|
||||
return self.name
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
# 禁止子板块再设父级(即最多两级)
|
||||
if self.parent and self.parent.parent:
|
||||
raise ValidationError('子板块不能再设置子级,最多只允许一级嵌套。')
|
||||
# 防止自引用
|
||||
if self.pk and self.parent and self.parent.pk == self.pk:
|
||||
raise ValidationError('板块不能以自身为父级。')
|
||||
|
||||
@property
|
||||
def is_sub(self):
|
||||
"""是否为子板块"""
|
||||
return self.parent is not None
|
||||
|
||||
@property
|
||||
def is_main(self):
|
||||
"""是否为主板块(无父级)"""
|
||||
return self.parent is None
|
||||
|
||||
|
||||
class Carousel(models.Model):
|
||||
title = models.CharField("轮播标题", max_length=100)
|
||||
image = models.ImageField("图片", upload_to='carousel/')
|
||||
link_url = models.URLField("跳转链接", blank=True)
|
||||
order = models.IntegerField("排序", default=0)
|
||||
active = models.BooleanField("启用", default=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "轮播图"
|
||||
verbose_name_plural = "轮播管理"
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
title = models.CharField("标题", max_length=200)
|
||||
section = models.ForeignKey(Section, on_delete=models.CASCADE, verbose_name="所属板块")
|
||||
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="作者")
|
||||
content = CKEditor5Field("正文") # 需安装 django-ckeditor
|
||||
cover = models.ImageField("封面图", upload_to='articles/', blank=True)
|
||||
is_published = models.BooleanField("已发布", default=True)
|
||||
created_at = models.DateTimeField("创建时间", auto_now_add=True)
|
||||
updated_at = models.DateTimeField("更新时间", auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "文章"
|
||||
verbose_name_plural = "文章管理"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class Notice(models.Model):
|
||||
title = models.CharField("通知标题", max_length=200)
|
||||
content = CKEditor5Field("通知内容")
|
||||
publisher = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="发布人")
|
||||
target_departments = models.ManyToManyField(Department, blank=True, verbose_name="接收部门") # 可指定发给哪些部门
|
||||
require_sign = models.BooleanField("是否需要签收", default=False)
|
||||
is_published = models.BooleanField("已发布", default=True)
|
||||
created_at = models.DateTimeField("发布时间", auto_now_add=True)
|
||||
updated_at = models.DateTimeField("更新时间", auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "通知"
|
||||
verbose_name_plural = "通知管理"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class NoticeReceipt(models.Model):
|
||||
"""签收记录"""
|
||||
notice = models.ForeignKey(Notice, on_delete=models.CASCADE, related_name='receipts')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="签收人")
|
||||
signed_at = models.DateTimeField("签收时间", auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('notice', 'user') # 防止重复签收
|
||||
verbose_name = "签收记录"
|
||||
verbose_name_plural = "签收管理"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.cname} 已签收《{self.notice.title}》"
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,20 @@
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.shortcuts import render
|
||||
|
||||
from article.models import Article
|
||||
from utils.permission import user_has_permission
|
||||
|
||||
from ninja import Router
|
||||
|
||||
# Create your views here.
|
||||
|
||||
# Create your views here.
|
||||
router = Router(tags=['content'])
|
||||
|
||||
|
||||
@router.get('/article', description="获取文章列表")
|
||||
def article_list(request):
|
||||
if not user_has_permission(request.user, 'article', 'view'):
|
||||
return HttpResponseForbidden("无权访问")
|
||||
articles = Article.objects.all()
|
||||
return render(request, 'articles/list.html', {'articles': articles})
|
||||
@@ -0,0 +1,24 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
|
||||
from article.models import Section, Carousel, Article, Notice, NoticeReceipt
|
||||
from base.models import MainMenu, FooterLink
|
||||
from users.models import User
|
||||
|
||||
|
||||
# —————— 主菜单 ——————
|
||||
@admin.register(MainMenu)
|
||||
class MainMenuAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'url', 'parent', 'order', 'visible')
|
||||
list_editable = ('order', 'visible')
|
||||
list_filter = ('parent', 'visible')
|
||||
search_fields = ('title', 'url')
|
||||
ordering = ('order',)
|
||||
|
||||
|
||||
# —————— 页脚链接 ——————
|
||||
@admin.register(FooterLink)
|
||||
class FooterLinkAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'url', 'order')
|
||||
list_editable = ('order',)
|
||||
ordering = ('order',)
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BaseConfig(AppConfig):
|
||||
name = 'base'
|
||||
verbose_name = '系统设置'
|
||||
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 6.0.3 on 2026-03-24 10:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FooterLink',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=50, verbose_name='链接文字')),
|
||||
('url', models.URLField(verbose_name='链接地址')),
|
||||
('order', models.IntegerField(default=0, verbose_name='排序')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '页脚链接',
|
||||
'verbose_name_plural': '页脚管理',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MainMenu',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=50, verbose_name='菜单标题')),
|
||||
('url', models.CharField(help_text='如 /news/ 或 外链 https://xxx', max_length=200, verbose_name='链接地址')),
|
||||
('is_external', models.BooleanField(default=False, verbose_name='是否外链')),
|
||||
('order', models.IntegerField(default=0, verbose_name='排序')),
|
||||
('visible', models.BooleanField(default=True, verbose_name='是否显示')),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.mainmenu', verbose_name='父菜单')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '主菜单',
|
||||
'verbose_name_plural': '主菜单管理',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
class MainMenu(models.Model):
|
||||
title = models.CharField("菜单标题", max_length=50)
|
||||
url = models.CharField("链接地址", max_length=200, help_text="如 /news/ 或 外链 https://xxx")
|
||||
is_external = models.BooleanField("是否外链", default=False)
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, verbose_name="父菜单")
|
||||
order = models.IntegerField("排序", default=0)
|
||||
visible = models.BooleanField("是否显示", default=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "主菜单"
|
||||
verbose_name_plural = "主菜单管理"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class FooterLink(models.Model):
|
||||
title = models.CharField("链接文字", max_length=50)
|
||||
url = models.URLField("链接地址")
|
||||
order = models.IntegerField("排序", default=0)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "页脚链接"
|
||||
verbose_name_plural = "页脚管理"
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.shortcuts import render
|
||||
from ninja import Router
|
||||
|
||||
# Create your views here.
|
||||
router = Router(tags=['content'])
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ContentConfig(AppConfig):
|
||||
name = 'front'
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,13 @@
|
||||
# your_app/templatetags/dict_tools.py
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
"""
|
||||
允许在模板中通过变量 key 获取字典值: {{ my_dict|get_item:key }}
|
||||
"""
|
||||
if dictionary is None:
|
||||
return None
|
||||
return dictionary.get(key)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,17 @@
|
||||
from django.urls import path, include
|
||||
|
||||
from front import views
|
||||
|
||||
app_name = 'front' # 可选,用于命名空间(如 {% url 'main:index' %})
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
path('h/', views.index, name='h'),
|
||||
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('party/', views.party, name='party'),
|
||||
path('interaction/', views.interaction, name='interaction'),
|
||||
path('science/', views.science, name='science'),
|
||||
]
|
||||
@@ -0,0 +1,444 @@
|
||||
from unicodedata import category
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
from utils.format import get_now_strftime_format
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
||||
def h(request):
|
||||
return render(request, 'front/h.html', )
|
||||
|
||||
|
||||
def index(request):
|
||||
# 时间字符串
|
||||
time_str = get_now_strftime_format()
|
||||
# 新闻内容
|
||||
news_data = {
|
||||
"key": "important",
|
||||
"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_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'},
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
carousel = {
|
||||
"key": "index",
|
||||
"data": [
|
||||
{
|
||||
"id": 0,
|
||||
"image_url": "/media/upload/images/2022/12/15/11146164001.jpg",
|
||||
"title": "First slide label",
|
||||
# "description": "Some representative placeholder content for the first slide."
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"image_url": "/media/upload/images/2022/12/15/11142163743.jpg",
|
||||
"title": "Second slide label",
|
||||
# "description": "Some representative placeholder content for the second slide."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"image_url": "/media/upload/images/2022/12/15/11150164804.jpg",
|
||||
"title": "Third slide label",
|
||||
# "description": "Some representative placeholder content for the third slide."
|
||||
}
|
||||
]
|
||||
}
|
||||
return render(request, 'front/index.html', {
|
||||
'time_str': time_str,
|
||||
'news_data': news_data,
|
||||
'section_data': section_data,
|
||||
'carousel': carousel,
|
||||
})
|
||||
|
||||
|
||||
def about(request):
|
||||
return render(request, 'front/about.html', {
|
||||
'leaders': [],
|
||||
'about': {'content': '这里是单位简介'},
|
||||
'org_list': [],
|
||||
})
|
||||
|
||||
|
||||
def news(request):
|
||||
sections = [
|
||||
{
|
||||
"key": "fire_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'},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "local_dynamic",
|
||||
"data": {
|
||||
"基层动态": [
|
||||
{'title': '某支队开展冬季练兵比武活动', 'date': '2023-12-01'},
|
||||
{'title': '社区微型消防站进行应急演练', 'date': '2023-12-05'},
|
||||
{'title': '朝阳区举办消防安全知识讲座', 'date': '2024-01-15'},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "state_council",
|
||||
"data": {
|
||||
"国务院新闻": [
|
||||
{'title': '国务院安委会部署全国安全生产大检查', 'date': '2023-11-20'},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
context = {
|
||||
'sections': sections,
|
||||
}
|
||||
return render(request, 'front/news.html', context)
|
||||
|
||||
|
||||
def info(request):
|
||||
"""信息公开"""
|
||||
|
||||
sections = [
|
||||
{
|
||||
"key": "notice",
|
||||
"data": {
|
||||
"通知通告": [
|
||||
{'title': '关于第二轮中央生态环境保护督察第四十一项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '关于第二轮中央生态环境保护督察第三十八项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '公告送达通知', 'date': '2026-03-10'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "law",
|
||||
"data": {
|
||||
"法律法规": [
|
||||
# ⚠️ 请替换为真实的法律法规数据
|
||||
{'title': '关于第二轮中央生态环境保护督察第四十一项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '关于第二轮中央生态环境保护督察第三十八项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '公告送达通知', 'date': '2026-03-10'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "policy_doc",
|
||||
"data": {
|
||||
"政策文件": [
|
||||
{'title': '中共长白山保护开发区工委办公室关于印发杨文慧同志在 2026...', 'date': '2026-03-05'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区落实<吉林省...', 'date': '2026-02-28'},
|
||||
{'title': '长白山保护开发区管理委员会 2025 年度行政执法工作报告', 'date': '2026-01-30'},
|
||||
{'title': '长白山管委会办公室关于印发《美丽长白山建设行动方案(202...', 'date': '2025-12-31'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区碳达峰实施...', 'date': '2025-12-31'},
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "policy_interp",
|
||||
"data": {
|
||||
"政策解读": [
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
{'title': '《长白山保护开发区旅游漂流活动管理规范》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山 40 米射电望远镜电磁波宁静区保护办法》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山保护开发区古树名木管理办法(试行)》政策解读', 'date': '2025-06-18'},
|
||||
{'title': '《长白山保护开发区公共租赁住房管理办法》政策解读', 'date': '2025-04-08'},
|
||||
{'title': '《长白山保护开发区旅游民宿管理与服务规范(试行)》政策解读', 'date': '2024-12-31'},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
context = {
|
||||
'sections': sections,
|
||||
}
|
||||
return render(request, 'front/info.html', context=context)
|
||||
|
||||
|
||||
def service(request):
|
||||
"""办事服务"""
|
||||
q = request.GET.get('q') or "办事指南"
|
||||
sections = [
|
||||
"办事指南",
|
||||
"政务服务",
|
||||
"资料下载",
|
||||
"办事统计",
|
||||
]
|
||||
data = {
|
||||
"办事指南": [
|
||||
{'title': '中共长白山保护开发区工委办公室关于印发杨文慧同志在 2026...', 'date': '2026-03-05'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区落实<吉林省...', 'date': '2026-02-28'},
|
||||
{'title': '长白山保护开发区管理委员会 2025 年度行政执法工作报告', 'date': '2026-01-30'},
|
||||
{'title': '长白山管委会办公室关于印发《美丽长白山建设行动方案(202...', 'date': '2025-12-31'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区碳达峰实施...', 'date': '2025-12-31'},
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
],
|
||||
"政务服务": [
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
{'title': '《长白山保护开发区旅游漂流活动管理规范》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山 40 米射电望远镜电磁波宁静区保护办法》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山保护开发区古树名木管理办法(试行)》政策解读', 'date': '2025-06-18'},
|
||||
{'title': '《长白山保护开发区公共租赁住房管理办法》政策解读', 'date': '2025-04-08'},
|
||||
{'title': '《长白山保护开发区旅游民宿管理与服务规范(试行)》政策解读', 'date': '2024-12-31'},
|
||||
],
|
||||
"资料下载": [
|
||||
{'title': '关于第二轮中央生态环境保护督察第四十一项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '关于第二轮中央生态环境保护督察第三十八项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '公告送达通知', 'date': '2026-03-10'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
]
|
||||
}
|
||||
context = {
|
||||
"sections": sections,
|
||||
"data_list": data.get(q),
|
||||
"q": q,
|
||||
}
|
||||
return render(request, 'front/service.html', context)
|
||||
|
||||
|
||||
def party(request):
|
||||
"""党建引领"""
|
||||
sections = [
|
||||
{
|
||||
"key": "party_work",
|
||||
"data": {
|
||||
"党建工作": [
|
||||
{'title': '关于第二轮中央生态环境保护督察第四十一项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '关于第二轮中央生态环境保护督察第三十八项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '公告送达通知', 'date': '2026-03-10'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "integrity",
|
||||
"data": {
|
||||
"党风廉政": [
|
||||
# ⚠️ 请替换为真实的法律法规数据
|
||||
{'title': '关于第二轮中央生态环境保护督察第四十一项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '关于第二轮中央生态环境保护督察第三十八项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '公告送达通知', 'date': '2026-03-10'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "advance",
|
||||
"data": {
|
||||
"先进典型": [
|
||||
{'title': '中共长白山保护开发区工委办公室关于印发杨文慧同志在 2026...', 'date': '2026-03-05'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区落实<吉林省...', 'date': '2026-02-28'},
|
||||
{'title': '长白山保护开发区管理委员会 2025 年度行政执法工作报告', 'date': '2026-01-30'},
|
||||
{'title': '长白山管委会办公室关于印发《美丽长白山建设行动方案(202...', 'date': '2025-12-31'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区碳达峰实施...', 'date': '2025-12-31'},
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
]
|
||||
carousel = {
|
||||
"key": "index",
|
||||
"data": [
|
||||
{
|
||||
"id": 0,
|
||||
"image_url": "/media/upload/images/2022/12/15/11146164001.jpg",
|
||||
"title": "First slide label",
|
||||
# "description": "Some representative placeholder content for the first slide."
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"image_url": "/media/upload/images/2022/12/15/11142163743.jpg",
|
||||
"title": "Second slide label",
|
||||
# "description": "Some representative placeholder content for the second slide."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"image_url": "/media/upload/images/2022/12/15/11150164804.jpg",
|
||||
"title": "Third slide label",
|
||||
# "description": "Some representative placeholder content for the third slide."
|
||||
}
|
||||
]
|
||||
}
|
||||
sections.insert(1, carousel)
|
||||
context = {
|
||||
'sections': sections,
|
||||
}
|
||||
|
||||
return render(request, 'front/party.html', context)
|
||||
|
||||
|
||||
def interaction(request):
|
||||
"""互动交流"""
|
||||
q = request.GET.get('q') or "专家访谈"
|
||||
sections = [
|
||||
"专家访谈",
|
||||
"调查征集",
|
||||
"在线咨询",
|
||||
]
|
||||
data = {
|
||||
"专家访谈": [
|
||||
{'title': '中共长白山保护开发区工委办公室关于印发杨文慧同志在 2026...', 'date': '2026-03-05'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区落实<吉林省...', 'date': '2026-02-28'},
|
||||
{'title': '长白山保护开发区管理委员会 2025 年度行政执法工作报告', 'date': '2026-01-30'},
|
||||
{'title': '长白山管委会办公室关于印发《美丽长白山建设行动方案(202...', 'date': '2025-12-31'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区碳达峰实施...', 'date': '2025-12-31'},
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
],
|
||||
"调查征集": [
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
{'title': '《长白山保护开发区旅游漂流活动管理规范》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山 40 米射电望远镜电磁波宁静区保护办法》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山保护开发区古树名木管理办法(试行)》政策解读', 'date': '2025-06-18'},
|
||||
{'title': '《长白山保护开发区公共租赁住房管理办法》政策解读', 'date': '2025-04-08'},
|
||||
{'title': '《长白山保护开发区旅游民宿管理与服务规范(试行)》政策解读', 'date': '2024-12-31'},
|
||||
],
|
||||
"在线咨询": [
|
||||
{'title': '关于第二轮中央生态环境保护督察第四十一项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '关于第二轮中央生态环境保护督察第三十八项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '公告送达通知', 'date': '2026-03-10'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
]
|
||||
}
|
||||
context = {
|
||||
"sections": sections,
|
||||
"data_list": data.get(q),
|
||||
"q": q,
|
||||
}
|
||||
return render(request, 'front/interaction.html', context)
|
||||
|
||||
|
||||
def science(request):
|
||||
"""消防科普"""
|
||||
q = request.GET.get('q') or "数字消防"
|
||||
sections = [
|
||||
"数字消防",
|
||||
"火灾预防",
|
||||
"自救逃生",
|
||||
]
|
||||
data = {
|
||||
"数字消防": [
|
||||
{'title': '中共长白山保护开发区工委办公室关于印发杨文慧同志在 2026...', 'date': '2026-03-05'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区落实<吉林省...', 'date': '2026-02-28'},
|
||||
{'title': '长白山保护开发区管理委员会 2025 年度行政执法工作报告', 'date': '2026-01-30'},
|
||||
{'title': '长白山管委会办公室关于印发《美丽长白山建设行动方案(202...', 'date': '2025-12-31'},
|
||||
{'title': '长白山管委会办公室关于印发《长白山保护开发区碳达峰实施...', 'date': '2025-12-31'},
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
],
|
||||
"火灾预防": [
|
||||
{'title': '《长白山保护开发区防雷减灾管理办法》政策解读', 'date': '2025-12-12'},
|
||||
{'title': '《长白山保护开发区旅游漂流活动管理规范》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山 40 米射电望远镜电磁波宁静区保护办法》政策解读', 'date': '2025-07-23'},
|
||||
{'title': '《长白山保护开发区古树名木管理办法(试行)》政策解读', 'date': '2025-06-18'},
|
||||
{'title': '《长白山保护开发区公共租赁住房管理办法》政策解读', 'date': '2025-04-08'},
|
||||
{'title': '《长白山保护开发区旅游民宿管理与服务规范(试行)》政策解读', 'date': '2024-12-31'},
|
||||
],
|
||||
"自救逃生": [
|
||||
{'title': '关于第二轮中央生态环境保护督察第四十一项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '关于第二轮中央生态环境保护督察第三十八项整改任务销号...', 'date': '2026-03-11'},
|
||||
{'title': '公告送达通知', 'date': '2026-03-10'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
{'title': '吉林省长白山保护开发区管理委员会规划和自然资源局行政...', 'date': '2026-03-06'},
|
||||
]
|
||||
}
|
||||
context = {
|
||||
"sections": sections,
|
||||
"data_list": data.get(q),
|
||||
"q": q,
|
||||
}
|
||||
return render(request, 'front/science.html', context)
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
After Width: | Height: | Size: 908 KiB |
|
After Width: | Height: | Size: 908 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 382 KiB |
|
After Width: | Height: | Size: 382 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 382 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,5 @@
|
||||
[virtualenvs]
|
||||
in-project = true
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
[project]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [
|
||||
{name = "ngfchl",email = "ngfchl@126.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
[tool.poetry.dependencies]
|
||||
|
||||
python = ">=3.13,<3.14"
|
||||
django = ">=6.0.3,<7.0.0"
|
||||
django-ninja = "^1.6"
|
||||
django-redis = "^6.0.0"
|
||||
pyjwt = "^2.8.0"
|
||||
python-dateutil = "^2.8.2"
|
||||
toml = "^0.10.2"
|
||||
uvicorn = { version = "^0.42.0", extras = ["standard"] }
|
||||
django-simpleui = "^2026.1.13"
|
||||
markdown = "^3.9"
|
||||
psycopg2-binary = "^2.9.11"
|
||||
django-environ = "^0.13.0"
|
||||
django-db-connection-pool = "^1.2.6"
|
||||
pillow = "^12.1.1"
|
||||
django-ckeditor-5 = "^0.2.20"
|
||||
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
[[tool.poetry.source]]
|
||||
name = "mirrors"
|
||||
url = "http://192.168.123.5:3141/root/pypi/+simple/"
|
||||
priority = "primary"
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for server project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
@@ -0,0 +1,316 @@
|
||||
"""
|
||||
Django settings for server project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 6.0.3.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/6.0/ref/settings/
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# 日志文件夹不存在时创建
|
||||
logs_path = os.path.join(BASE_DIR, 'db/logs')
|
||||
if not os.path.exists(logs_path):
|
||||
os.makedirs(logs_path)
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-gr&k$v)_t*ax)yr1v9iz3x%$-j812ajpxs+fe2dn*dgy=a!730'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'simpleui',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_ckeditor_5',
|
||||
'base.apps.BaseConfig',
|
||||
'article.apps.ArticleConfig',
|
||||
'users.apps.UsersConfig',
|
||||
'front.apps.ContentConfig'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'server.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates']
|
||||
,
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'server.wsgi.application'
|
||||
ASGI_APPLICATION = 'server.asgi.application'
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "zh-Hans"
|
||||
|
||||
TIME_ZONE = "Asia/Shanghai"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/6.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
# STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(os.path.join(BASE_DIR, 'static')),
|
||||
)
|
||||
|
||||
# 日志配置
|
||||
LOGGING = {
|
||||
'version': 1, # 版本
|
||||
'disable_existing_loggers': False, # 是否禁用已经存在的日志器
|
||||
'formatters': { # 日志信息显示的格式
|
||||
'verbose': {
|
||||
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
|
||||
},
|
||||
},
|
||||
'filters': { # 过滤器
|
||||
'require_debug_true': { # django在debug模式下才输出日志
|
||||
'()': 'django.utils.log.RequireDebugTrue',
|
||||
},
|
||||
},
|
||||
'handlers': { # 日志处理方法
|
||||
'console': { # 向终端中输出日志
|
||||
'level': 'DEBUG', # 输出等级为“INFO”
|
||||
'filters': ['require_debug_true'],
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple'
|
||||
},
|
||||
'file': { # 向文件中输出日志
|
||||
'level': os.getenv('LOGGER_LEVEL') if os.getenv('LOGGER_LEVEL') else 'INFO', # 输出等级为“INFO”
|
||||
# 新增内容
|
||||
# 'class': 'logging.handlers.TimedRotatingFileHandler',
|
||||
# 'filename': os.path.join(BASE_DIR, 'logs/logs.log'),
|
||||
# 'when': 'm',
|
||||
# 'interval': 10,
|
||||
# 'backupCount': 10,
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': BASE_DIR / "db/logs/django_server.log", # 日志文件的位置
|
||||
'maxBytes': 5 * 1024 * 1024, # 日志文件的大小(300*1024*1024为300MB)
|
||||
'backupCount': 10, # 日志文件的数量(超过设定的最大值会自动备份,备份数量最大值为10)
|
||||
'formatter': 'verbose', # 日志输出格式:使用了在之前定义的'verbose'
|
||||
'encoding': 'utf-8' # 新增此行,指定文件编码为UTF-8
|
||||
},
|
||||
},
|
||||
'loggers': { # 日志器
|
||||
'ptools': { # 定义了一个名为django的日志器
|
||||
'handlers': ['console', 'file'], # 可以同时向终端与文件中输出日志
|
||||
'propagate': True, # 是否继续传递日志信息
|
||||
'level': os.getenv('LOGGER_LEVEL') if os.getenv('LOGGER_LEVEL') else 'INFO', # 日志器接收的最低日志级别
|
||||
},
|
||||
'requests': {
|
||||
'level': 'WARNING',
|
||||
'handlers': ['console'], # 或你定义的其他 handler
|
||||
'propagate': False,
|
||||
},
|
||||
'urllib3': {
|
||||
'level': 'WARNING',
|
||||
'handlers': ['console'],
|
||||
'propagate': False,
|
||||
},
|
||||
}
|
||||
}
|
||||
# 调整POST传输数据文件大小限制
|
||||
DATA_UPLOAD_MAX_MEMORY_SIZE = 25 * 1024 * 1024 * 1024
|
||||
FILE_UPLOAD_MAX_MEMORY_SIZE = 25 * 1024 * 1024 * 1024
|
||||
|
||||
# 自定义UI配置
|
||||
SIMPLEUI_HOME_TITLE = '长白山消防支队'
|
||||
SIMPLEUI_HOME_ICON = 'fa fa-optin-monster'
|
||||
SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'
|
||||
SIMPLEUI_HOME_INFO = False
|
||||
SIMPLEUI_INDEX = ''
|
||||
SIMPLEUI_ANALYSIS = False
|
||||
SIMPLEUI_STATIC_OFFLINE = True
|
||||
SIMPLEUI_HOME_ACTION = False
|
||||
|
||||
CKEDITOR_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar': 'full',
|
||||
'height': 300,
|
||||
'width': '100%',
|
||||
},
|
||||
}
|
||||
STATIC_URL = '/static/'
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
|
||||
customColorPalette = [
|
||||
{
|
||||
'color': 'hsl(4, 90%, 58%)',
|
||||
'label': 'Red'
|
||||
},
|
||||
{
|
||||
'color': 'hsl(340, 82%, 52%)',
|
||||
'label': 'Pink'
|
||||
},
|
||||
{
|
||||
'color': 'hsl(291, 64%, 42%)',
|
||||
'label': 'Purple'
|
||||
},
|
||||
{
|
||||
'color': 'hsl(262, 52%, 47%)',
|
||||
'label': 'Deep Purple'
|
||||
},
|
||||
{
|
||||
'color': 'hsl(231, 48%, 48%)',
|
||||
'label': 'Indigo'
|
||||
},
|
||||
{
|
||||
'color': 'hsl(207, 90%, 54%)',
|
||||
'label': 'Blue'
|
||||
},
|
||||
]
|
||||
|
||||
CKEDITOR_5_CUSTOM_CSS = 'path_to.css' # optional
|
||||
CKEDITOR_5_FILE_STORAGE = "path_to_storage.CustomStorage" # optional
|
||||
CKEDITOR_5_CONFIGS = {
|
||||
'default': {
|
||||
'toolbar': {
|
||||
'items': ['heading', '|', 'bold', 'italic', 'link',
|
||||
'bulletedList', 'numberedList', 'blockQuote', 'imageUpload', ],
|
||||
}
|
||||
|
||||
},
|
||||
'extends': {
|
||||
'blockToolbar': [
|
||||
'paragraph', 'heading1', 'heading2', 'heading3',
|
||||
'|',
|
||||
'bulletedList', 'numberedList',
|
||||
'|',
|
||||
'blockQuote',
|
||||
],
|
||||
'toolbar': {
|
||||
'items': ['heading', '|', 'outdent', 'indent', '|', 'bold', 'italic', 'link', 'underline', 'strikethrough',
|
||||
'code', 'subscript', 'superscript', 'highlight', '|', 'codeBlock', 'sourceEditing', 'insertImage',
|
||||
'bulletedList', 'numberedList', 'todoList', '|', 'blockQuote', 'imageUpload', '|',
|
||||
'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'mediaEmbed', 'removeFormat',
|
||||
'insertTable',
|
||||
],
|
||||
'shouldNotGroupWhenFull': 'true'
|
||||
},
|
||||
'image': {
|
||||
'toolbar': ['imageTextAlternative', '|', 'imageStyle:alignLeft',
|
||||
'imageStyle:alignRight', 'imageStyle:alignCenter', 'imageStyle:side', '|'],
|
||||
'styles': [
|
||||
'full',
|
||||
'side',
|
||||
'alignLeft',
|
||||
'alignRight',
|
||||
'alignCenter',
|
||||
]
|
||||
|
||||
},
|
||||
'table': {
|
||||
'contentToolbar': ['tableColumn', 'tableRow', 'mergeTableCells',
|
||||
'tableProperties', 'tableCellProperties'],
|
||||
'tableProperties': {
|
||||
'borderColors': customColorPalette,
|
||||
'backgroundColors': customColorPalette
|
||||
},
|
||||
'tableCellProperties': {
|
||||
'borderColors': customColorPalette,
|
||||
'backgroundColors': customColorPalette
|
||||
}
|
||||
},
|
||||
'heading': {
|
||||
'options': [
|
||||
{'model': 'paragraph', 'title': 'Paragraph', 'class': 'ck-heading_paragraph'},
|
||||
{'model': 'heading1', 'view': 'h1', 'title': 'Heading 1', 'class': 'ck-heading_heading1'},
|
||||
{'model': 'heading2', 'view': 'h2', 'title': 'Heading 2', 'class': 'ck-heading_heading2'},
|
||||
{'model': 'heading3', 'view': 'h3', 'title': 'Heading 3', 'class': 'ck-heading_heading3'}
|
||||
]
|
||||
}
|
||||
},
|
||||
'list': {
|
||||
'properties': {
|
||||
'styles': 'true',
|
||||
'startIndex': 'true',
|
||||
'reversed': 'true',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Define a constant in settings.py to specify file upload permissions
|
||||
CKEDITOR_5_FILE_UPLOAD_PERMISSION = "staff" # Possible values: "staff", "authenticated", "any"
|
||||
|
||||
# CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME = "custom_upload_file"
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
URL configuration for server project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/6.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import path, include
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
import traceback
|
||||
import importlib
|
||||
|
||||
import jwt
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import JsonResponse
|
||||
from jwt import exceptions
|
||||
from ninja import NinjaAPI
|
||||
from ninja.errors import ValidationError
|
||||
from ninja.security import HttpBearer
|
||||
|
||||
from .settings import SECRET_KEY
|
||||
from utils.common_response import CommonResponse
|
||||
|
||||
logger = logging.getLogger('ptools')
|
||||
|
||||
|
||||
class GlobalAuth(HttpBearer):
|
||||
def authenticate(self, request, token):
|
||||
try:
|
||||
logger.debug('用户认证中')
|
||||
logger.debug(f'解析 Auth Token:{token}')
|
||||
res = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
||||
user = User.objects.filter(id=res.get('id'), username=res.get('username')).first()
|
||||
logger.debug(user)
|
||||
if not user:
|
||||
raise exceptions.PyJWKError('用户不存在')
|
||||
request.user = user
|
||||
return token
|
||||
except exceptions.PyJWKError:
|
||||
msg = 'Token 认证失败,请重新登陆!'
|
||||
logger.error(msg)
|
||||
raise
|
||||
except exceptions.ExpiredSignatureError:
|
||||
msg = '认证令牌已过期,请重新登陆!'
|
||||
logger.error(msg)
|
||||
raise
|
||||
except Exception as e:
|
||||
msg = '认证失败,请检查用户是否存在'
|
||||
logger.error(msg)
|
||||
raise
|
||||
|
||||
|
||||
# 2. 获取当前文件所在的目录路径 (假设此文件与各个 app 文件夹同级,或者在特定配置目录下)
|
||||
# 如果你的此文件在项目根目录,而 app 文件夹也在根目录:
|
||||
from django.conf import settings
|
||||
project_root = settings.BASE_DIR
|
||||
|
||||
|
||||
# 如果 app 文件夹是 Django 的已安装应用,可能需要通过 settings.INSTALLED_APPS 获取,
|
||||
# 但通常直接扫描物理目录更灵活。这里假设扫描当前目录下的所有子文件夹。
|
||||
|
||||
# 3. 自动发现并注册路由
|
||||
def auto_load_routers(base_path, package_name_prefix=None):
|
||||
"""
|
||||
base_path: 物理路径,例如 /path/to/project
|
||||
package_name_prefix: 包名前缀,例如 'myproject.' (如果这些文件夹是包的一部分)
|
||||
"""
|
||||
|
||||
# 获取该路径下所有子模块/包
|
||||
# 注意:这需要这些文件夹是合法的 Python 包 (即包含 __init__.py),或者在 Python 路径中
|
||||
for importer, modname, ispkg in pkgutil.iter_modules([base_path]):
|
||||
# 过滤条件:
|
||||
# 1. 必须是包 (文件夹)
|
||||
# 2. 不以 '_' 开头 (排除 __pycache__, _private 等)
|
||||
# 3. 排除当前文件所在的脚本名 (防止递归或错误)
|
||||
if ispkg and not modname.startswith('_'):
|
||||
|
||||
# 构造完整的模块路径:例如 "authorize.views"
|
||||
# 如果这些文件夹直接位于根路径且不是大包的一部分,package_name_prefix 可能为空
|
||||
if package_name_prefix:
|
||||
full_module_path = f"{package_name_prefix}{modname}.views"
|
||||
else:
|
||||
full_module_path = f"{modname}.views"
|
||||
|
||||
try:
|
||||
# 动态导入 views 模块
|
||||
module = importlib.import_module(full_module_path)
|
||||
|
||||
# 检查是否存在 'router' 变量
|
||||
if hasattr(module, 'router'):
|
||||
router = getattr(module, 'router')
|
||||
|
||||
# 使用文件夹名称 (modname) 作为 URL 前缀
|
||||
# 例如: 文件夹 'authorize' -> 前缀 '/authorize'
|
||||
url_prefix = f"/{modname}"
|
||||
|
||||
api_v1.add_router(url_prefix, router)
|
||||
print(f"✅ 自动注册: {url_prefix} (来自 {full_module_path})")
|
||||
else:
|
||||
print(f"⚠️ 跳过: {modname} (在 {full_module_path} 中未找到 'router' 变量)")
|
||||
|
||||
except ImportError as e:
|
||||
# 如果文件夹里没有 views.py,会报 ImportError,这是正常的,跳过即可
|
||||
if "No module named" in str(e) and "views" in str(e):
|
||||
print(f"ℹ️ 跳过: {modname} (没有 views.py 模块)")
|
||||
else:
|
||||
print(f"❌ 导入错误 {modname}: {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ 注册失败 {modname}: {e}")
|
||||
|
||||
api_v1 = NinjaAPI(version='1.0.0', auth=GlobalAuth())
|
||||
auto_load_routers(project_root)
|
||||
|
||||
|
||||
@api_v1.exception_handler(ValidationError)
|
||||
def validation_errors(request, exc):
|
||||
logger.error(request.body)
|
||||
logger.error(exc)
|
||||
logger.debug(traceback.format_exc())
|
||||
return JsonResponse(data=CommonResponse.error(msg=f"输入参数验证失败!").to_dict(), status=422, safe=False)
|
||||
|
||||
|
||||
@api_v1.exception_handler(exceptions.PyJWKError) # 捕获 HttpBearer 认证异常
|
||||
def custom_http_bearer_exception_handler(request, exc):
|
||||
logger.error(exc)
|
||||
return JsonResponse(data=CommonResponse.error(msg=f"用户Token认证失败:{exc}").to_dict(), status=401)
|
||||
|
||||
|
||||
@api_v1.exception_handler(exceptions.ExpiredSignatureError) # 捕获 HttpBearer 认证异常
|
||||
def custom_http_bearer_exception_handler(request, exc):
|
||||
logger.error(exc)
|
||||
return JsonResponse(data=CommonResponse.error(msg=f"用户登录信息已过期,请重新登录!!").to_dict(), status=401)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', include("front.urls")),
|
||||
path('admin/', admin.site.urls),
|
||||
path("api/", api_v1.urls),
|
||||
path("ckeditor5/", include('django_ckeditor_5.urls')),
|
||||
# path("upload/", custom_upload_function, name="custom_upload_file"),
|
||||
|
||||
]
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for server project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
@@ -0,0 +1,429 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Microsoft YaHei", Arial, Helvetica, sans-serif;
|
||||
/* 添加线性渐变背景 */
|
||||
background: linear-gradient(180deg, #E6F0FC, #D5E5F8);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 20px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-header {
|
||||
background: #c62828;
|
||||
color: #fff;
|
||||
padding: 20px 0;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
/*width: 100%;*/
|
||||
/*margin: 0 auto;*/
|
||||
line-height: 1.0em;
|
||||
max-width: 1200px;
|
||||
min-width: 840px;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
/*margin: 0 auto;*/
|
||||
line-height: 1.0em;
|
||||
display: block;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.nav-main {
|
||||
background: #1e3a8a;
|
||||
/*margin: -50px auto 0;*/
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.nav-main ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
|
||||
.nav-main li {
|
||||
padding: 15px 20px;
|
||||
border-left: #c62828 solid 1px;
|
||||
border-right: #c62828 solid 1px;
|
||||
}
|
||||
|
||||
.nav-main li:hover {
|
||||
padding: 15px 20px;
|
||||
background-color: #092268;
|
||||
}
|
||||
|
||||
.nav-main a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
display: block; /* 确保点击区域填满 li */
|
||||
}
|
||||
|
||||
/* ✅ 新增:激活状态 (Active) */
|
||||
.nav-main li.active {
|
||||
background-color: #0d3bac; /* 比 hover 更深,或者用红色 #c62828 突出 */
|
||||
font-weight: bold; /* 加粗文字 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.banner {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.banner img {
|
||||
width: 100%;
|
||||
/*min-width: 1140px;*/
|
||||
height: 300px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative; /* 确保 .content 是相对于最近的定位上下文定位 */
|
||||
background: #ffffff;
|
||||
max-width: 1200px;
|
||||
padding-top: 20px;
|
||||
margin: -50px auto 0 auto;
|
||||
border: #fff solid 5px;
|
||||
z-index: 99;
|
||||
min-height: 800px;
|
||||
}
|
||||
|
||||
.date-bar {
|
||||
background-color: #c62828;
|
||||
height: 50px;
|
||||
/*min-width: 992px;*/
|
||||
width: 100%;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/*margin-left: -10px;*/
|
||||
justify-content: space-between;
|
||||
margin: 0 0 30px;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #ffffff68;
|
||||
border: 0 solid #ddd; /* 外边框颜色 */
|
||||
border-radius: 50px; /* 大圆角,形成胶囊效果 */
|
||||
overflow: hidden; /* 确保内部元素不溢出圆角 */
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
max-width: 400px; /* 限制最大宽度 */
|
||||
min-width: 200px; /* 限制最小宽度 */
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* 聚焦时的效果 */
|
||||
.search-wrapper:focus-within {
|
||||
border-color: #1e3a8a; /* 聚焦时变为主题色 (深蓝) */
|
||||
box-shadow: 0 0 8px rgba(30, 58, 138, 0.2);
|
||||
}
|
||||
|
||||
/* 输入框:去掉默认边框和圆角,让它融入容器 */
|
||||
.search-input {
|
||||
flex: 1; /* 占据剩余空间 */
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 6px;
|
||||
font-size: 13px;
|
||||
background: transparent;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 按钮:去掉默认样式,固定大小,背景透明或浅色 */
|
||||
.search-btn {
|
||||
border: none;
|
||||
background: transparent; /* 按钮背景色,可与白色区分 */
|
||||
width: 16px; /* 固定宽度 */
|
||||
height: 16px; /* 固定高度,与输入框高度匹配 */
|
||||
border-radius: 50%; /* 按钮自身也是圆的 */
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.3s;
|
||||
margin-right: 2px; /* 稍微留点缝隙,更有层次感 */
|
||||
}
|
||||
|
||||
.search-btn:hover {
|
||||
background: #1e3a8a; /* 悬停时变为主题色 */
|
||||
color: #fff; /* 图标变白 */
|
||||
}
|
||||
|
||||
/* 如果使用的是 FontAwesome 等图标库,确保图标大小合适 */
|
||||
.search-btn i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.user-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
object-fit: fill;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.main-section {
|
||||
padding: 0;
|
||||
/*margin: 0 -15px !important;*/
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/*.card {*/
|
||||
/* flex: 1;*/
|
||||
/* margin-right: 20px;*/
|
||||
/* background: #fff;*/
|
||||
/* padding: 20px;*/
|
||||
/*}*/
|
||||
|
||||
/* --- 自定义新闻卡片样式开始 --- */
|
||||
|
||||
/* --- 卡片容器 --- */
|
||||
.news-card {
|
||||
border: none;
|
||||
/*border-radius: 8px; !* 圆角稍微小一点,更干练 *!*/
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
/*max-width: 800px;*/
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
/*margin: 20px auto;*/
|
||||
}
|
||||
|
||||
/* --- Tab 栏区域 (关键修改) --- */
|
||||
.custom-tabs-header {
|
||||
/* 1. 体现 Tab 栏背景颜色:浅蓝色 */
|
||||
background-color: #e3f2fd;
|
||||
/* 左右留白 */
|
||||
/*padding: 10px 15px 0;*/
|
||||
|
||||
/* 2. 体现下方的强调线:深蓝色粗线 */
|
||||
border-bottom: 3px solid #0d2c5e;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: none; /* 去掉 Bootstrap 默认的底边 */
|
||||
}
|
||||
|
||||
.nav-tabs .nav-item {
|
||||
margin-bottom: -3px; /* 负边距,让 Tab 沉下去盖住强调线 */
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
border: none;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
|
||||
/* 4. 字体大小修正 */
|
||||
font-size: 15px;
|
||||
font-weight: 600; /* 加粗 */
|
||||
|
||||
color: #555; /* 未选中时的文字颜色 */
|
||||
/*padding: 8px 20px;*/
|
||||
transition: all 0.2s;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link:hover {
|
||||
color: #0d2c5e;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
/* 激活状态 */
|
||||
.nav-tabs .nav-link.active {
|
||||
/* 选中背景:深蓝色 */
|
||||
background-color: #0d2c5e !important;
|
||||
color: #fff !important;
|
||||
border-bottom-color: #0d2c5e !important; /* 盖住底部的强调线 */
|
||||
z-index: 2; /* 确保层级最高 */
|
||||
}
|
||||
|
||||
.tabs-header-wrapper {
|
||||
display: flex; /* 启用 Flexbox */
|
||||
justify-content: space-between; /* 关键:两端对齐 */
|
||||
align-items: flex-end; /* 底部对齐,让 Tab 和“更多”在视觉上处于同一基线 */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* “更多”链接独立样式 */
|
||||
.more-link-standalone {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #0d2c5e;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px; /* 与 Tab 的高度视觉对齐 */
|
||||
white-space: nowrap;
|
||||
transition: color 0.2s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.more-link-standalone:hover {
|
||||
color: #c0392b;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* --- 列表内容区域 --- */
|
||||
.news-list-body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
/* 3. 去除虚线,改用 padding 控制间距 */
|
||||
padding: 7px 0;
|
||||
border-bottom: none; /* 确保无底线 */
|
||||
width: 100%;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
/* 鼠标悬停整行微变色,提升交互感 */
|
||||
.news-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
padding-left: 5px; /* 轻微位移 */
|
||||
padding-right: 5px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.news-title-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 小圆点 */
|
||||
.bullet-point {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #0d2c5e; /* 圆点直接用主题色 */
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.news-title-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
/* 4. 字体大小修正 */
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.news-date {
|
||||
/* 4. 字体大小修正 */
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* --- 自定义样式结束 --- */
|
||||
|
||||
.ad-banner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 30px 0 15px 0;
|
||||
}
|
||||
|
||||
.ad-item {
|
||||
width: 280px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.service {
|
||||
background: #e3f2fd;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
|
||||
.footer {
|
||||
/* 1. 启用 Flex 布局 */
|
||||
display: flex;
|
||||
|
||||
/* 2. 设置主轴方向为垂直(从上到下) */
|
||||
flex-direction: column;
|
||||
|
||||
/* 3. 关键:让子元素在交叉轴(这里是垂直方向)上对齐到底部 */
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.footer-info {
|
||||
/* 可选:如果 footer 有固定高度或最小高度,这个属性才生效明显 */
|
||||
/* min-height: 150px; <-- 如果你的 footer 没有设高度,可以加上这个测试效果 */
|
||||
background: #1e3a8a;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
padding: 20px 0 10px 0;
|
||||
width: 100% !important;
|
||||
/*min-width: 1140px;*/
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-right: 5px !important;
|
||||
padding-left: 5px !important;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg t="1774400055168" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="7911" width="16" height="16">
|
||||
<path d="M937.4 423.9c-84 0-165.7-27.3-232.9-77.8v352.3c0 179.9-138.6 325.6-309.6 325.6S85.3 878.3 85.3 698.4c0-179.9 138.6-325.6 309.6-325.6 17.1 0 33.7 1.5 49.9 4.3v186.6c-15.5-6.1-32-9.2-48.6-9.2-76.3 0-138.2 65-138.2 145.3 0 80.2 61.9 145.3 138.2 145.3 76.2 0 138.1-65.1 138.1-145.3V0H707c0 134.5 103.7 243.5 231.6 243.5v180.3l-1.2 0.1"
|
||||
p-id="7912" fill="#ffffff"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 626 B |
@@ -0,0 +1,5 @@
|
||||
<svg t="1774399989459" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="5869" width="16" height="16">
|
||||
<path d="M869.347328 268.355584l-165.218304-165.234688 82.655232-82.610176c27.373568-27.344896 71.749632-27.344896 99.125248 0L951.97184 86.58944c27.373568 27.392 27.373568 71.798784 0 99.188736l-82.624512 82.577408zM389.347328 748.343296l-165.251072-165.220352L673.316864 137.465856l165.220352 165.218304-449.189888 445.659136zM91.377664 883.843072L191.909888 615.34208l163.844096 163.876864-264.37632 104.624128z m742.033408 56.469504c23.123968 0 41.844736 18.74944 41.844736 41.875456 0 23.093248-18.720768 41.811968-41.844736 41.811968H93.329408c-23.093248 0-41.828352-18.71872-41.828352-41.811968 0-23.126016 18.733056-41.875456 41.828352-41.875456h740.081664z"
|
||||
p-id="5870" fill="#ffffff"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 952 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1774446230298" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13396" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M593.94368 715.648a10.688 10.688 0 0 0-14.976 0L424.21568 870.4c-71.68 71.68-192.576 79.232-271.68 0-79.232-79.232-71.616-200 0-271.616l154.752-154.752a10.688 10.688 0 0 0 0-15.04l-52.992-52.992a10.688 10.688 0 0 0-15.04 0L84.50368 530.688a287.872 287.872 0 0 0 0 407.488 288 288 0 0 0 407.488 0l154.752-154.752a10.688 10.688 0 0 0 0-15.04l-52.736-52.736z m344.384-631.168a288.256 288.256 0 0 1 0 407.616l-154.752 154.752a10.688 10.688 0 0 1-15.04 0l-52.992-52.992a10.688 10.688 0 0 1 0-15.104l154.752-154.688c71.68-71.68 79.232-192.448 0-271.68-79.104-79.232-200-71.68-271.68 0L443.92768 307.2a10.688 10.688 0 0 1-15.04 0l-52.864-52.864a10.688 10.688 0 0 1 0-15.04l154.88-154.752a287.872 287.872 0 0 1 407.424 0z m-296.32 240.896l52.672 52.736a10.688 10.688 0 0 1 0 15.04l-301.504 301.44a10.688 10.688 0 0 1-15.04 0l-52.736-52.672a10.688 10.688 0 0 1 0-15.04l301.632-301.504a10.688 10.688 0 0 1 15.04 0z" fill="#13227a" p-id="13397"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1774400789288" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12319" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M971.650873 875.890274c-20.428928-20.428928-183.860349-194.074813-183.860349-194.074813 102.144638-163.431421 91.930175-398.36409-51.07232-551.581047C573.286783-43.411471 307.710723-43.411471 144.279302 130.234414c-163.431421 173.645885-163.431421 449.436409 0 617.975062 148.109726 153.216958 367.720698 158.32419 531.152119 56.179551l194.074813 188.967581c40.857855 35.750623 86.822943 40.857855 122.573566 0 25.53616-35.750623 20.428928-71.501247-20.428927-117.466334zM445.605985 732.887781c-158.32419 0-280.897756-132.78803-280.897756-291.11222 0-163.431421 127.680798-291.112219 280.897756-291.112219 158.32419 0 280.897756 132.78803 280.897756 291.112219 5.107232 163.431421-122.573566 291.112219-280.897756 291.11222z m0 0" p-id="12320" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg t="1774400103777" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="9795" width="16" height="16">
|
||||
<path d="M958.4 848.9c-24.6-53.6-59.7-101.8-104.3-143.3-44.6-41.4-96.4-73.9-154-96.6-14.8-5.8-29.7-10.9-44.9-15.3C756.6 541.6 826.1 436 826.1 314.1 826.1 140.6 685.5 0 512 0S197.9 140.6 197.9 314.1c0 121.9 69.4 227.5 170.9 279.6-15.2 4.4-30.2 9.5-44.9 15.3-57.6 22.7-109.4 55.2-154 96.6-44.7 41.4-79.8 89.6-104.3 143.3-25.5 55.6-38.5 114.5-38.5 175.1h969.8c0-60.6-13-119.5-38.5-175.1z"
|
||||
p-id="9796" fill="#ffffff"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 671 B |
@@ -0,0 +1 @@
|
||||
<svg t="1774400549556" class="icon" viewBox="0 0 1171 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11140" width="16" height="16"><path d="M331.428571 263.428571q0-23.428571-14.285714-37.714286t-37.714286-14.285714q-24.571429 0-43.428571 14.571429t-18.857143 37.428571q0 22.285714 18.857143 36.857143t43.428571 14.571429q23.428571 0 37.714286-14t14.285714-37.428571zm424.571429 289.714286q0-16-14.571429-28.571429t-37.428571-12.571429q-15.428571 0-28.285714 12.857143t-12.857143 28.285714q0 16 12.857143 28.857143t28.285714 12.857143q22.857143 0 37.428571-12.571429t14.571429-29.142857zm-134.857143-289.714286q0-23.428571-14-37.714286t-37.428571-14.285714q-24.571429 0-43.428571 14.571429t-18.857143 37.428571q0 22.285714 18.857143 36.857143t43.428571 14.571429q23.428571 0 37.428571-14t14-37.428571zm362.857143 289.714286q0-16-14.857143-28.571429t-37.142857-12.571429q-15.428571 0-28.285714 12.857143t-12.857143 28.285714q0 16 12.857143 28.857143t28.285714 12.857143q22.285714 0 37.142857-12.571429t14.857143-29.142857zm-152-226.857143q-17.714286-2.285714-40-2.285714-96.571429 0-177.714286 44t-127.714286 119.142857-46.571429 164.285714q0 44.571429 13.142857 86.857143-20 1.714286-38.857143 1.714286-14.857143 0-28.571429-0.857143t-31.428571-3.714286-25.428571-4-31.142857-6-28.571429-6l-144.571429 72.571429 41.142857-124.571429q-165.714286-116-165.714286-280 0-96.571429 55.714286-177.714286t150.857143-127.714286 207.714286-46.571429q100.571429 0 190 37.714286t149.714286 104.285714 78 148.857143zm338.285714 320.571429q0 66.857143-39.142857 127.714286t-106 110.571429l31.428571 103.428571-113.714286-62.285714q-85.714286 21.142857-124.571429 21.142857-96.571429 0-177.714286-40.285714t-127.714286-109.428571-46.571429-150.857143 46.571429-150.857143 127.714286-109.428571 177.714286-40.285714q92 0 173.142857 40.285714t130 109.714286 48.857143 150.571429z" p-id="11141" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 916 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 194 KiB |
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,155 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}长白山消防救援支队{% endblock %}</title>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/style.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部 -->
|
||||
<header class="top-header">
|
||||
<span></span>
|
||||
<div class="header-container flex">
|
||||
<img src="{% static 'images/logo.png' %}" class="logo" alt="">
|
||||
<div>
|
||||
<h1>长白山消防救援支队</h1>
|
||||
<p>CHANGBAI MOUNTAIN FIRE AND RESCUE BRIGADE</p>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</header>
|
||||
|
||||
<!-- 导航 -->
|
||||
<nav class="nav-main">
|
||||
<span></span>
|
||||
<div class="header-nav">
|
||||
<ul>
|
||||
<!-- 网站首页 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'index' %}active{% endif %}">
|
||||
<a href="{% url 'front:index' %}">网站首页</a>
|
||||
</li>
|
||||
|
||||
<!-- 单位简介 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'about' %}active{% endif %}">
|
||||
<a href="{% url 'front:about' %}">单位简介</a>
|
||||
</li>
|
||||
|
||||
<!-- 新闻中心 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'news' %}active{% endif %}">
|
||||
<a href="{% url 'front:news' %}">新闻中心</a>
|
||||
</li>
|
||||
|
||||
<!-- 信息公开 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'info' %}active{% endif %}">
|
||||
<a href="{% url 'front:info' %}">信息公开</a>
|
||||
</li>
|
||||
<!-- 党建引领 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'party' %}active{% endif %}">
|
||||
<a href="{% url 'front:party' %}">党建引领</a>
|
||||
</li>
|
||||
<!-- 办事服务 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'service' %}active{% endif %}">
|
||||
<a href="{% url 'front:service' %}">办事服务</a>
|
||||
</li>
|
||||
<!-- 互动交流 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'interaction' %}active{% endif %}">
|
||||
<a href="{% url 'front:interaction' %}">互动交流</a>
|
||||
</li>
|
||||
<!-- 消防科普 -->
|
||||
<li class="{% if request.resolver_match.url_name == 'science' %}active{% endif %}">
|
||||
<a href="{% url 'front:science' %}">消防科普</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</nav>
|
||||
|
||||
<!-- Banner -->
|
||||
{#<div class="d-xl-none" style="height: 50px;"></div>#}
|
||||
<div class="banner">
|
||||
<img src="{% static 'images/img.jpg' %}" alt="">
|
||||
</div>
|
||||
|
||||
<!-- 主体 -->
|
||||
<main class="container main" style="padding: 10px 5px !important;">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- 底部 -->
|
||||
<footer class="footer">
|
||||
<div class=" d-none d-md-block">
|
||||
<div class="row"
|
||||
style="align-items: center;justify-content: space-evenly;padding: 15px 0;background-color: #F5F5F5;width: 100%!important;margin-left: 0 !important;gap: 10px;">
|
||||
<div style="display: flex;align-items: center;">
|
||||
<img src="{% static "icons/link.svg" %}" alt="" style="width: 13px; height: 13px; object-fit: contain;">
|
||||
<a href="#"
|
||||
style="margin-left: 6px; text-decoration: none; color: #13227a; font-size: 14px; white-space: nowrap;">
|
||||
友情链接
|
||||
</a>
|
||||
</div>
|
||||
<div style="width: 280px;">
|
||||
<select class="form-control" id="exampleFormControlSelect1"
|
||||
x-placement="市政府网站"
|
||||
>
|
||||
<option>市政府网站</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
<option>4</option>
|
||||
<option>5</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="display: block;width: 280px;">
|
||||
<select class="form-control" id="exampleFormControlSelect1"
|
||||
x-placement="国内消防站点"
|
||||
>
|
||||
<option>国内消防站点</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
<option>4</option>
|
||||
<option>5</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-info">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-6 text-center">
|
||||
<img src="{% static 'images/red.png' %}">
|
||||
<img class="footerImg" src="{% static 'images/jiucuo.png' %}">
|
||||
</div>
|
||||
<div class="col-lg-6 d-none d-xl-block">
|
||||
<p class="margin-top-20 font-size-14 line-height-2 footerColor">
|
||||
地址:某某市某某东路锦海·某某区大厦B栋14楼 邮编:xxxxxx
|
||||
</p>
|
||||
<p class="font-size-14 line-height-2 footerColor">
|
||||
服务热线:0791-00000000 传真:0791-00000000
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 text-center">
|
||||
<img src="/media/upload/images/2026/03/25/wechat.png" alt="">
|
||||
<img class="footerImg" src="/media/upload/images/2026/03/25/weibo.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="copyright text-center line-height-2 padding-bottom-20 padding-top-20">
|
||||
<p class="margin-top-20 font-size-14 line-height-2 footerColor">
|
||||
版权所有:长白山消防救援支队 备案证号:<a href="http://beian.miit.gov.cn/"
|
||||
target="_blank">辽ICP备xxxxxxxx号</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
{% comment %}
|
||||
用法: {% include "includes/carousel.html" with carousel_id="mainBanner" slides=carousel_data height="500px" show_caption=True %}
|
||||
|
||||
参数说明:
|
||||
- carousel_id: 唯一ID,用于区分页面上的多个轮播图 (必填)
|
||||
- slides: 列表,包含字典 {'image_url': '...', 'title': '...', 'description': '...'} (必填)
|
||||
- height: 轮播图高度,默认 400px (可选)
|
||||
- show_caption: 是否显示文字描述,默认 True (可选)
|
||||
- object_fit: 图片填充模式,默认 'cover' (可选: cover, contain, fill)
|
||||
{% endcomment %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
<div id="{{ carousel_id }}" class="carousel slide" data-ride="carousel">
|
||||
|
||||
<!-- 1. 指示器 (Indicators) -->
|
||||
{% if slides|length > 1 %}
|
||||
<ol class="carousel-indicators">
|
||||
{% for slide in slides %}
|
||||
<li data-target="#{{ carousel_id }}" data-slide-to="{{ forloop.counter0 }}"
|
||||
{% if forloop.first %}class="active"{% endif %}></li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endif %}
|
||||
|
||||
<!-- 2. 图片区域 (Inner) -->
|
||||
<div class="carousel-inner" style="height: {{ height|default:'400px' }}; background: #f8f9fa;">
|
||||
{% for slide in slides %}
|
||||
<div class="carousel-item {% if forloop.first %}active{% endif %}" style="height: 100%;">
|
||||
|
||||
<!-- 图片 -->
|
||||
<img src="{{ slide.image_url }}"
|
||||
class="d-block w-100 h-100"
|
||||
alt="{{ slide.title|default:'Slide image' }}"
|
||||
style="object-fit: {{ object_fit|default:'cover' }}; width: 100%; height: 100%;">
|
||||
|
||||
<!-- 文字描述 (可选) -->
|
||||
{% if show_caption|default:True and slide.title %}
|
||||
<div class="carousel-caption d-none d-md-block"
|
||||
style="background: rgba(0,0,0,0.3); border-radius: 8px; padding: 10px 20px; bottom: 20px;">
|
||||
<h5 class="text-white text-shadow">{{ slide.title }}</h5>
|
||||
{% if slide.description %}
|
||||
<p class="text-white text-shadow">{{ slide.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% empty %}
|
||||
<!-- 空状态占位 -->
|
||||
<div class="carousel-item active" style="height: 100%; display: flex; align-items: center; justify-content: center; color: #999;">
|
||||
<span>暂无轮播图片</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- 3. 控制按钮 (Controls) - 仅当多于1张图时显示 -->
|
||||
{% if slides|length > 1 %}
|
||||
<button class="carousel-control-prev" type="button" data-target="#{{ carousel_id }}" data-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-target="#{{ carousel_id }}" data-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">Next</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 可选:增加文字阴影以确保在亮色图片上可见 */
|
||||
.text-shadow {
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.6);
|
||||
}
|
||||
/* 修复 Bootstrap 默认 carousel-item 高度问题 */
|
||||
.carousel-inner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.carousel-item {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!-- templates/components/_news_card.html -->
|
||||
{% load dict_tools %} {# 确保加载了过滤器,虽然这里可能不再需要 get_item,但保留以防万一 #}
|
||||
|
||||
<div class="news-card">
|
||||
<div class="custom-tabs-header">
|
||||
<div class="tabs-header-wrapper">
|
||||
<ul class="nav nav-tabs" id="newsTab_{{ unique_id }}" role="tablist">
|
||||
{# 核心修改:直接遍历 data 字典的 key #}
|
||||
{% for tab_id, items in data.items %}
|
||||
<li class="nav-item">
|
||||
{# 确定显示名称:如果有 labels 字典则取 labels.tab_id,否则直接用 tab_id (或进行capitalize处理) #}
|
||||
{% with display_name=tab_id %}
|
||||
{# 如果 default 返回的是英文 key,可以在这里做简单的格式化,比如首字母大写,或者保持原样 #}
|
||||
<a class="nav-link {% if forloop.first %}active{% endif %}"
|
||||
id="{{ tab_id }}-tab_{{ unique_id }}"
|
||||
data-toggle="tab"
|
||||
href="#{{ tab_id }}_{{ unique_id }}"
|
||||
role="tab"
|
||||
aria-selected="{% if forloop.first %}true{% else %}false{% endif %}">
|
||||
{{ display_name }}
|
||||
</a>
|
||||
{% endwith %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</ul>
|
||||
<!-- 右侧:更多链接 (独立于 ul) -->
|
||||
<a class="more-link-standalone"
|
||||
href="{{ more_url|default:'#' }}">
|
||||
更多 >
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content news-list-body" id="newsTabContent_{{ unique_id }}">
|
||||
|
||||
{# 再次遍历 data 生成内容面板 #}
|
||||
{% for tab_id, items in data.items %}
|
||||
<div class="tab-pane fade {% if forloop.first %}show active{% endif %}"
|
||||
id="{{ tab_id }}_{{ unique_id }}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="{{ tab_id }}-tab_{{ unique_id }}">
|
||||
|
||||
<ul class="list-unstyled mb-0">
|
||||
{# items 就是当前 key 对应的列表数据,直接使用 #}
|
||||
{% if items %}
|
||||
{% for item in items %}
|
||||
<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>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,45 @@
|
||||
{% load static %}
|
||||
<footer class="site-footer">
|
||||
<div class="footer-top">
|
||||
<div class="footer-links">
|
||||
<span class="link-title">友情链接:</span>
|
||||
<select name="links" id="links">
|
||||
<option value="">市政务网站</option>
|
||||
<option value="">国内消防站点</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<div class="footer-left">
|
||||
<div class="gov-logo">
|
||||
<img src="{% static 'images/gov_logo.png' %}" alt="政府机关">
|
||||
<img src="{% static 'images/search_logo.png' %}" alt="政府网站找错">
|
||||
</div>
|
||||
<div class="footer-text">
|
||||
<a href="#">关于我们</a>
|
||||
<a href="#">版权声明</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-center">
|
||||
<p><strong>政府服务热线:</strong> 0433-5312088</p>
|
||||
<p><strong>地址:</strong> 长白山保护开发区消防救援支队防火监督与政策法规科</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-right">
|
||||
<div class="qrcode">
|
||||
<img src="{% static 'images/wechat_qr.png' %}" alt="微信公众号">
|
||||
<p>微信公众号</p>
|
||||
</div>
|
||||
<div class="qrcode">
|
||||
<img src="{% static 'images/weibo_qr.png' %}" alt="政务微博">
|
||||
<p>政务微博</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-copyright">
|
||||
<p>版权所有:长白山消防救援支队 | 网站标识码:XXXXXX | 公网安备:XXXXXXXX</p>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,176 @@
|
||||
{% 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: 500px;margin: auto auto 15px;">
|
||||
<div class="col-4">
|
||||
<div class="news-card" style="height: 100%;">
|
||||
<div class="custom-tabs-header">
|
||||
<div class="tabs-header-wrapper">
|
||||
<ul class="nav nav-tabs" id="newsTab_{{ unique_id }}" role="tablist">
|
||||
<li class="nav-item">
|
||||
{# 确定显示名称:如果有 labels 字典则取 labels.tab_id,否则直接用 tab_id (或进行capitalize处理) #}
|
||||
|
||||
{# 如果 default 返回的是英文 key,可以在这里做简单的格式化,比如首字母大写,或者保持原样 #}
|
||||
<a class="nav-link active"
|
||||
id="{{ tab_id }}-tab_{{ unique_id }}"
|
||||
data-toggle="tab"
|
||||
href="#"
|
||||
role="tab"
|
||||
aria-selected="true">
|
||||
领导信息
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content intro-text">
|
||||
|
||||
<p>
|
||||
长白山消防救援支队,是隶属于吉林省长白山保护开发区管理委员会的综合性消防救援队伍,负责辖区内的火灾预防、扑救和应急救援工作。
|
||||
</p>
|
||||
|
||||
<p><strong>官方信息与联系方式:</strong></p>
|
||||
<p>
|
||||
官方信息平台与联系方式:长白山消防救援支队的相关信息主要通过长白山保护开发区管理委员会官方网站的“政务公开
|
||||
- 机构设置”栏目进行发布。根据其 2025 年政府信息公开年度报告,公众可通过以下方式联系该支队:
|
||||
</p>
|
||||
|
||||
<div class="contact-info">
|
||||
<p><strong>联系电话/传真:</strong>0433-5312088</p>
|
||||
<p><strong>办公地址:</strong>长白山保护开发区消防救援支队防火监督与政策法规科 101
|
||||
室(邮编:133613)</p>
|
||||
<p><strong>电子邮箱:</strong>306098022@qq.com</p>
|
||||
</div>
|
||||
|
||||
<p><strong>主要职责与工作重点:</strong></p>
|
||||
<p>
|
||||
作为长白山地区的核心消防救援力量,该支队承担着“全灾种、大应急”的综合性救援任务,其工作重点紧密围绕辖区特点展开。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="news-card" style=" height: 500px;">
|
||||
<div class="custom-tabs-header">
|
||||
<div class="tabs-header-wrapper">
|
||||
<ul class="nav nav-tabs" id="newsTab_{{ unique_id }}" role="tablist">
|
||||
|
||||
<li class="nav-item">
|
||||
{# 确定显示名称:如果有 labels 字典则取 labels.tab_id,否则直接用 tab_id (或进行capitalize处理) #}
|
||||
|
||||
{# 如果 default 返回的是英文 key,可以在这里做简单的格式化,比如首字母大写,或者保持原样 #}
|
||||
<a class="nav-link active"
|
||||
id="{{ tab_id }}-tab_{{ unique_id }}"
|
||||
data-toggle="tab"
|
||||
href="#"
|
||||
role="tab"
|
||||
aria-selected="true">
|
||||
单位简介
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content intro-text">
|
||||
|
||||
<p>
|
||||
长白山消防救援支队,是隶属于吉林省长白山保护开发区管理委员会的综合性消防救援队伍,负责辖区内的火灾预防、扑救和应急救援工作。
|
||||
</p>
|
||||
|
||||
<p><strong>官方信息与联系方式:</strong></p>
|
||||
<p>
|
||||
官方信息平台与联系方式:长白山消防救援支队的相关信息主要通过长白山保护开发区管理委员会官方网站的“政务公开
|
||||
- 机构设置”栏目进行发布。根据其 2025 年政府信息公开年度报告,公众可通过以下方式联系该支队:
|
||||
</p>
|
||||
|
||||
<div class="contact-info">
|
||||
<p><strong>联系电话/传真:</strong>0433-5312088</p>
|
||||
<p><strong>办公地址:</strong>长白山保护开发区消防救援支队防火监督与政策法规科 101
|
||||
室(邮编:133613)</p>
|
||||
<p><strong>电子邮箱:</strong>306098022@qq.com</p>
|
||||
</div>
|
||||
|
||||
<p><strong>主要职责与工作重点:</strong></p>
|
||||
<p>
|
||||
作为长白山地区的核心消防救援力量,该支队承担着“全灾种、大应急”的综合性救援任务,其工作重点紧密围绕辖区特点展开。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="height: 300px;margin: auto auto 15px;">
|
||||
<div class="col-12">
|
||||
<div class="news-card">
|
||||
<div class="custom-tabs-header">
|
||||
<div class="tabs-header-wrapper">
|
||||
<ul class="nav nav-tabs" id="newsTab_{{ unique_id }}" role="tablist">
|
||||
|
||||
<li class="nav-item">
|
||||
{# 确定显示名称:如果有 labels 字典则取 labels.tab_id,否则直接用 tab_id (或进行capitalize处理) #}
|
||||
|
||||
{# 如果 default 返回的是英文 key,可以在这里做简单的格式化,比如首字母大写,或者保持原样 #}
|
||||
<a class="nav-link active"
|
||||
id="{{ tab_id }}-tab_{{ unique_id }}"
|
||||
data-toggle="tab"
|
||||
href="#"
|
||||
role="tab"
|
||||
aria-selected="true">
|
||||
组织结构
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content news-list-body" id="newsTabContent_{{ unique_id }}">
|
||||
<ul class="list-unstyled">
|
||||
{# items 就是当前 key 对应的列表数据,直接使用 #}
|
||||
{% if items %}
|
||||
{% for item in items %}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.intro-text {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>长白山消防救援支队</title>
|
||||
<link rel="stylesheet" href="{% static 'css/style.css' %}">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 顶部 -->
|
||||
<header class="top-header">
|
||||
<div class="container flex" >
|
||||
<img src="{% static 'images/logo.png' %}" class="logo" alt="">
|
||||
<div>
|
||||
<h1>长白山消防救援支队</h1>
|
||||
<p>CHANGBAI MOUNTAIN FIRE AND RESCUE BRIGADE</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!-- 这个模板继承自 base.html -->
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}首页 - 长白山消防救援支队{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- 顶部横幅 -->
|
||||
<div class="row d-none d-md-block">
|
||||
<div class="col-12">
|
||||
<img src="{% static 'images/slogan_bg.png' %}" alt="标语背景"
|
||||
style="object-fit: fill;max-width: 1160px;width: 100%;">
|
||||
{# <h2>对党忠诚 纪律严明 赴汤蹈火 竭诚为民</h2>#}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row d-none d-md-block" style="padding:0 5px;">
|
||||
<div class="col-12 date-bar">
|
||||
<span style="font-size: 14px;padding-left: 25px;">{{ time_str }}</span>
|
||||
<div class="user-icons">
|
||||
<form class="search-form">
|
||||
<div class="search-wrapper">
|
||||
<input type="text" class="search-input">
|
||||
<button type="submit" class="search-btn">
|
||||
<img src="{% static "icons/search.svg" %}" alt="">
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<img src="{% static "icons/user.svg" %}" alt="">
|
||||
<img src="{% static "icons/edit.svg" %}" alt="">
|
||||
<img src="{% static "icons/sina.svg" %}" alt="">
|
||||
<img src="{% static "icons/wechat.svg" %}" alt="">
|
||||
<img src="{% static "icons/douyin.svg" %}" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 主要内容区 -->
|
||||
<!-- 左侧大图 -->
|
||||
<div class="row" style="height: 300px;">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
{% 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 %}
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% for section in section_data %}
|
||||
<div class="col-md-6 col-sm-12" style="margin: 15px auto">
|
||||
{% include 'components/_news_card.html' with data=section.data unique_id=section.key %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% 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: 15px auto ">
|
||||
{% for section in sections %}
|
||||
<div class="col-6" style="margin: auto auto 15px">
|
||||
{% include 'components/_news_card.html' with data=section.data unique_id=section.key %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,168 @@
|
||||
{% 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">«</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">»</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 %}
|
||||
@@ -0,0 +1,22 @@
|
||||
{% 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 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 %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% 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 %}
|
||||
@@ -0,0 +1,168 @@
|
||||
{% 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">«</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">»</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 %}
|
||||