fix: 修复图片显示问题,添加whitenoise优化静态文件性能
Some checks failed
Deploy to Server / deploy (push) Has been cancelled

This commit is contained in:
爽哒哒
2026-03-21 12:18:51 +08:00
parent 68ebf7f100
commit 555b5badbf
6 changed files with 29 additions and 32 deletions

View File

@@ -14,7 +14,6 @@ RUN pip install --upgrade pip && pip install -r requirements.txt
# 复制项目 # 复制项目
COPY . /app/ COPY . /app/
COPY .env /app/
# 暴露端口 # 暴露端口
EXPOSE 8876 EXPOSE 8876

View File

@@ -1,8 +1,16 @@
from rest_framework import serializers from rest_framework import serializers
from django.conf import settings
from .models import Competition, CompetitionEnrollment, ScoreDimension, Project, ProjectFile, Score, Comment, HomePageConfig, CarouselItem from .models import Competition, CompetitionEnrollment, ScoreDimension, Project, ProjectFile, Score, Comment, HomePageConfig, CarouselItem
from shop.serializers import WeChatUserSerializer from shop.serializers import WeChatUserSerializer
def _media_url(file_field):
"""返回 media 文件的相对路径,避免 build_absolute_uri 生成容器内部地址"""
if file_field and file_field.name:
return settings.MEDIA_URL + file_field.name
return None
class CarouselItemSerializer(serializers.ModelSerializer): class CarouselItemSerializer(serializers.ModelSerializer):
display_image = serializers.SerializerMethodField() display_image = serializers.SerializerMethodField()
@@ -13,9 +21,7 @@ class CarouselItemSerializer(serializers.ModelSerializer):
'order', 'is_active'] 'order', 'is_active']
def get_display_image(self, obj): def get_display_image(self, obj):
if obj.image: return _media_url(obj.image) or obj.image_url
return obj.image.url
return obj.image_url
class HomePageConfigSerializer(serializers.ModelSerializer): class HomePageConfigSerializer(serializers.ModelSerializer):
@@ -30,9 +36,7 @@ class HomePageConfigSerializer(serializers.ModelSerializer):
'organizer', 'undertaker', 'carousel1_items', 'carousel2_items'] 'organizer', 'undertaker', 'carousel1_items', 'carousel2_items']
def get_display_banner(self, obj): def get_display_banner(self, obj):
if obj.banner_image: return _media_url(obj.banner_image) or obj.banner_image_url
return obj.banner_image.url
return obj.banner_image_url
def get_carousel1_items(self, obj): def get_carousel1_items(self, obj):
items = CarouselItem.objects.filter(carousel_type='carousel1', is_active=True) items = CarouselItem.objects.filter(carousel_type='carousel1', is_active=True)
@@ -62,9 +66,7 @@ class CompetitionSerializer(serializers.ModelSerializer):
'score_dimensions', 'created_at'] 'score_dimensions', 'created_at']
def get_display_cover_image(self, obj): def get_display_cover_image(self, obj):
if obj.cover_image: return _media_url(obj.cover_image) or obj.cover_image_url
return obj.cover_image.url
return obj.cover_image_url
class CompetitionEnrollmentSerializer(serializers.ModelSerializer): class CompetitionEnrollmentSerializer(serializers.ModelSerializer):
@@ -109,9 +111,7 @@ class ProjectSerializer(serializers.ModelSerializer):
} }
def get_display_cover_image(self, obj): def get_display_cover_image(self, obj):
if obj.cover_image: return _media_url(obj.cover_image) or obj.cover_image_url
return obj.cover_image.url
return obj.cover_image_url
class ScoreSerializer(serializers.ModelSerializer): class ScoreSerializer(serializers.ModelSerializer):

View File

@@ -58,6 +58,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [ MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', 'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
@@ -173,6 +174,15 @@ STATICFILES_DIRS = [
BASE_DIR / 'static', BASE_DIR / 'static',
] ]
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# 媒体文件配置 # 媒体文件配置
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media' MEDIA_ROOT = BASE_DIR / 'media'

View File

@@ -23,6 +23,7 @@ requests
django-filter django-filter
django-admin-sortable2 django-admin-sortable2
openpyxl openpyxl
whitenoise==6.9.0
aliyun-python-sdk-core==2.16.0 aliyun-python-sdk-core==2.16.0
aliyun-python-sdk-tingwu==1.0.7 aliyun-python-sdk-tingwu==1.0.7

View File

@@ -22,11 +22,10 @@ import 'github-markdown-css/github-markdown-dark.css';
*/ */
const getImageUrl = (url) => { const getImageUrl = (url) => {
if (!url) return ''; if (!url) return '';
if (url.startsWith('http') || url.startsWith('//')) return url; if (url.startsWith('http') || url.startsWith('//')) {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000/api'; try { return new URL(url).pathname; } catch { return url; }
// Remove /api suffix if present to get the root URL for media files }
const baseUrl = apiUrl.replace(/\/api\/?$/, ''); return url;
return `${baseUrl}${url}`;
}; };
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;

View File

@@ -3,7 +3,7 @@ import { Card, Row, Col, Tag, Button, Spin, Typography, Carousel } from 'antd';
import { RocketOutlined, RightOutlined, LeftOutlined } from '@ant-design/icons'; import { RocketOutlined, RightOutlined, LeftOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { getConfigs, getHomePageConfig, getCompetitions, getActivities } from '../api'; import { getHomePageConfig, getCompetitions, getActivities } from '../api';
import './Home.css'; import './Home.css';
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
@@ -19,7 +19,7 @@ const getImageUrl = (url) => {
const Home = () => { const Home = () => {
const [products, setProducts] = useState([]); const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(false);
const [typedText, setTypedText] = useState(''); const [typedText, setTypedText] = useState('');
const [isTypingComplete, setIsTypingComplete] = useState(false); const [isTypingComplete, setIsTypingComplete] = useState(false);
const [currentSlide, setCurrentSlide] = useState(0); const [currentSlide, setCurrentSlide] = useState(0);
@@ -38,7 +38,6 @@ const Home = () => {
const carouselRef3 = useRef(null); const carouselRef3 = useRef(null);
useEffect(() => { useEffect(() => {
fetchProducts();
fetchHomePageConfig(); fetchHomePageConfig();
fetchCompetitions(); fetchCompetitions();
fetchActivities(); fetchActivities();
@@ -64,17 +63,6 @@ const Home = () => {
return () => clearInterval(mainTypingInterval); return () => clearInterval(mainTypingInterval);
}, [homeConfig?.main_title]); }, [homeConfig?.main_title]);
const fetchProducts = async () => {
try {
const response = await getConfigs();
setProducts(response.data);
} catch (error) {
console.error('Failed to fetch products:', error);
} finally {
setLoading(false);
}
};
const fetchHomePageConfig = async () => { const fetchHomePageConfig = async () => {
try { try {
const response = await getHomePageConfig(); const response = await getHomePageConfig();