feat: 首页配置封面图上传预览,修复系列活动跳转,活动卡片样式优化
All checks were successful
Deploy to Server / deploy (push) Successful in 1m56s
All checks were successful
Deploy to Server / deploy (push) Successful in 1m56s
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user