fix: 修复图片显示问题,添加whitenoise优化静态文件性能
Some checks failed
Deploy to Server / deploy (push) Has been cancelled
Some checks failed
Deploy to Server / deploy (push) Has been cancelled
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user