feat: 首页配置封面图上传预览,修复系列活动跳转,活动卡片样式优化
All checks were successful
Deploy to Server / deploy (push) Successful in 1m56s

This commit is contained in:
爽哒哒
2026-03-22 00:31:34 +08:00
parent 21f892fdf6
commit 96be8b9eee
6 changed files with 37 additions and 14 deletions

View File

@@ -50,10 +50,11 @@ class Activity(models.Model):
@property @property
def display_banner_url(self): def display_banner_url(self):
""" """
获取Banner显示的URL优先使用上传的图片 获取Banner显示的URL优先使用上传的图片,返回相对路径避免容器内部地址
""" """
if self.banner: if self.banner and self.banner.name:
return self.banner.url from django.conf import settings
return settings.MEDIA_URL + self.banner.name
return self.banner_url return self.banner_url
@property @property

View File

@@ -7,12 +7,13 @@ from .models import Competition, CompetitionEnrollment, ScoreDimension, Project,
@admin.register(HomePageConfig) @admin.register(HomePageConfig)
class HomePageConfigAdmin(ModelAdmin): class HomePageConfigAdmin(ModelAdmin):
list_display = ['id', 'main_title', 'organizer', 'undertaker', 'is_active'] list_display = ['id', 'main_title', 'organizer', 'undertaker', 'is_active', 'banner_preview']
list_editable = ['main_title', 'organizer', 'undertaker', 'is_active'] list_editable = ['main_title', 'organizer', 'undertaker', 'is_active']
readonly_fields = ['banner_preview']
fieldsets = ( fieldsets = (
('封面图', { ('封面图', {
'fields': ('banner_image', 'banner_image_url'), 'fields': ('banner_image', 'banner_image_url', 'banner_preview'),
'description': '首页标题下方的封面图可上传本地图片或填写URL' 'description': '首页标题下方的封面图可上传本地图片或填写URL,优先使用上传的图片'
}), }),
('标题设置', { ('标题设置', {
'fields': ('main_title',) 'fields': ('main_title',)
@@ -25,6 +26,18 @@ class HomePageConfigAdmin(ModelAdmin):
}), }),
) )
@display(description="封面图预览")
def banner_preview(self, obj):
url = None
if obj.banner_image and obj.banner_image.name:
from django.conf import settings
url = settings.MEDIA_URL + obj.banner_image.name
elif obj.banner_image_url:
url = obj.banner_image_url
if url:
return format_html('<img src="{}" style="max-height:120px;max-width:300px;border-radius:6px;object-fit:cover;" />', url)
return "暂无图片"
class ScoreDimensionInline(admin.TabularInline): class ScoreDimensionInline(admin.TabularInline):
model = ScoreDimension model = ScoreDimension

View File

@@ -17,7 +17,8 @@ class HomePageConfigSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = HomePageConfig model = HomePageConfig
fields = ['id', 'banner_image', 'banner_image_url', 'display_banner', fields = ['id', 'banner_image', 'banner_image_url', 'display_banner',
'main_title', 'organizer', 'undertaker'] 'main_title', 'organizer', 'undertaker', 'is_active']
def get_display_banner(self, obj): def get_display_banner(self, obj):
return _media_url(obj.banner_image) or obj.banner_image_url return _media_url(obj.banner_image) or obj.banner_image_url

View File

@@ -14,14 +14,24 @@ from .serializers import (
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
@api_view(['GET']) @api_view(['GET', 'PATCH'])
@permission_classes([permissions.AllowAny]) @permission_classes([permissions.AllowAny])
def get_homepage_config(request): def get_homepage_config(request):
"""获取首页配置""" """获取或更新首页配置PATCH 支持 multipart/form-data 上传图片)"""
try: try:
config = HomePageConfig.objects.filter(is_active=True).first() config = HomePageConfig.objects.filter(is_active=True).first()
if not config: if not config:
config = HomePageConfig.objects.create() config = HomePageConfig.objects.create()
if request.method == 'PATCH':
serializer = HomePageConfigSerializer(
config, data=request.data, partial=True, context={'request': request}
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializer = HomePageConfigSerializer(config, context={'request': request}) serializer = HomePageConfigSerializer(config, context={'request': request})
return Response(serializer.data) return Response(serializer.data)
except Exception as e: except Exception as e:

View File

@@ -7,9 +7,8 @@ const { Title } = Typography;
const getImageUrl = (url) => { const getImageUrl = (url) => {
if (!url) return ''; if (!url) return '';
if (url.startsWith('http') || url.startsWith('//')) { // 外部 URL 直接返回,不提取 pathname
try { return new URL(url).pathname; } catch { return url; } if (url.startsWith('http') || url.startsWith('//')) return url;
}
return url; return url;
}; };

View File

@@ -396,8 +396,7 @@ const ActivityDetail = () => {
{/* Hero Image with LayoutId for shared transition */} {/* Hero Image with LayoutId for shared transition */}
<div className={styles.detailHeader}> <div className={styles.detailHeader}>
<motion.img <img
layoutId={`activity-card-${id}`}
src={activity.display_banner_url || cleanUrl(activity.banner_url) || activity.cover_image || 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=800&h=600&fit=crop'} src={activity.display_banner_url || cleanUrl(activity.banner_url) || activity.cover_image || 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=800&h=600&fit=crop'}
alt={activity.title} alt={activity.title}
className={styles.detailImage} className={styles.detailImage}