Initial commit
@@ -30,3 +30,12 @@ export function clearFiles() {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function replaceUrl(data) {
|
||||
return request({
|
||||
url: '/file/replace_url/',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<template slot-scope="{row}"><span>{{ row.teacher_name }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="封面" width="80px" align="center">
|
||||
<template slot-scope="{row}"><img :src="row.image" width="50" height="50" style="object-fit:cover"></template>
|
||||
<template slot-scope="{row}"><img :src="resolveUrl(row.image)" width="50" height="50" style="object-fit:cover"></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学习人数" width="80px" align="center">
|
||||
<template slot-scope="{row}"><span>{{ row.students }}</span></template>
|
||||
@@ -92,7 +92,7 @@
|
||||
:show-file-list="false"
|
||||
:on-success="handleAvatarSuccess"
|
||||
:headers="upHeaders">
|
||||
<img v-if="temp.image" :src="temp.image" class="avatar">
|
||||
<img v-if="temp.image" :src="resolveUrl(temp.image)" class="avatar">
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
@@ -362,6 +362,16 @@ export default {
|
||||
detail: ''
|
||||
}
|
||||
},
|
||||
resolveUrl(url) {
|
||||
if (!url) return ''
|
||||
if (url.startsWith('http://localhost:8000/media/')) {
|
||||
return url.replace('http://localhost:8000', '')
|
||||
}
|
||||
if (url.startsWith('http://127.0.0.1:8000/media/')) {
|
||||
return url.replace('http://127.0.0.1:8000', '')
|
||||
}
|
||||
return url
|
||||
},
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
this.dialogStatus = 'create'
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
@click="handleClear"
|
||||
size="small"
|
||||
>一键清空</el-button>
|
||||
<el-button
|
||||
class="filter-item"
|
||||
type="warning"
|
||||
icon="el-icon-edit"
|
||||
@click="handleReplaceUrl"
|
||||
size="small"
|
||||
>一键修改IP/URL</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="listLoading"
|
||||
@@ -86,6 +93,21 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="一键修改IP/URL" :visible.sync="dialogFormVisible" width="30%">
|
||||
<el-form :model="temp" label-position="left" label-width="100px" style="width: 400px; margin-left:50px;">
|
||||
<el-form-item label="原IP/URL" prop="old_url">
|
||||
<el-input v-model="temp.old_url" placeholder="请输入原IP/URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新IP/URL" prop="new_url">
|
||||
<el-input v-model="temp.new_url" placeholder="请输入新IP/URL" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="replaceUrlData">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<pagination
|
||||
v-show="fileList.count>0"
|
||||
:total="fileList.count"
|
||||
@@ -96,7 +118,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getFileList, deleteFile, clearFiles } from "@/api/file"
|
||||
import { getFileList, deleteFile, clearFiles, replaceUrl } from "@/api/file"
|
||||
import Pagination from "@/components/Pagination"
|
||||
export default {
|
||||
components: { Pagination },
|
||||
@@ -108,6 +130,11 @@ export default {
|
||||
page: 1,
|
||||
page_size: 20
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
temp: {
|
||||
old_url: '',
|
||||
new_url: ''
|
||||
},
|
||||
enabledOptions: [
|
||||
{ key: "文档", display_name: "文档" },
|
||||
{ key: "图片", display_name: "图片" },
|
||||
@@ -175,6 +202,35 @@ export default {
|
||||
this.getList()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleReplaceUrl() {
|
||||
this.temp = {
|
||||
old_url: '',
|
||||
new_url: ''
|
||||
}
|
||||
this.dialogFormVisible = true
|
||||
},
|
||||
replaceUrlData() {
|
||||
if (!this.temp.old_url || !this.temp.new_url) {
|
||||
this.$message.error('请填写完整')
|
||||
return
|
||||
}
|
||||
this.$confirm('确认修改? 此操作不可恢复!', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
replaceUrl(this.temp).then(response => {
|
||||
this.$notify({
|
||||
title: 'Success',
|
||||
message: response.data.message || '修改成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.dialogFormVisible = false
|
||||
this.getList()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,7 +41,11 @@ module.exports = {
|
||||
proxy: {
|
||||
'/api': {
|
||||
// target: 'http://localhost:8000',
|
||||
target: process.env.PROXY_TARGET || 'http://192.168.5.95:8000',
|
||||
target: process.env.PROXY_TARGET || 'http://192.168.5.112:8000',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/media': {
|
||||
target: process.env.PROXY_TARGET || 'http://192.168.5.112:8000',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import shutil
|
||||
import random
|
||||
import socket
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.conf import settings
|
||||
from apps.crm.models import Project, StudentShowcase, Teacher
|
||||
@@ -9,7 +10,18 @@ class Command(BaseCommand):
|
||||
help = 'Populate database with business mock data using existing local images'
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
self.base_url = "http://192.168.5.95:8000"
|
||||
# Get local IP
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(('8.8.8.8', 80))
|
||||
ip = s.getsockname()[0]
|
||||
s.close()
|
||||
except Exception:
|
||||
ip = '127.0.0.1'
|
||||
|
||||
self.base_url = f"http://{ip}:8000"
|
||||
self.stdout.write(f"Using Base URL: {self.base_url}")
|
||||
|
||||
self.media_root = settings.MEDIA_ROOT
|
||||
self.projects_dir = os.path.join(self.media_root, 'projects')
|
||||
self.showcases_dir = os.path.join(self.media_root, 'showcases')
|
||||
@@ -108,14 +120,12 @@ class Command(BaseCommand):
|
||||
src_img_name = source_project_images[i % len(source_project_images)]
|
||||
local_path = self.copy_image('projects', src_img_name, 'projects', p_data['target_name'])
|
||||
|
||||
full_url = f"{self.base_url}{local_path}"
|
||||
|
||||
Project.objects.update_or_create(
|
||||
title=p_data['title'],
|
||||
defaults={
|
||||
'project_type': p_data['project_type'],
|
||||
'teacher': p_data['teacher'],
|
||||
'image': full_url,
|
||||
'image': local_path,
|
||||
'detail': p_data['detail']
|
||||
}
|
||||
)
|
||||
@@ -149,12 +159,10 @@ class Command(BaseCommand):
|
||||
# Wait, LS showed showcase_1.jpg in 'projects' folder!
|
||||
local_path = self.copy_image('projects', src_img_name, 'showcases', v_data['target_name'])
|
||||
|
||||
full_url = f"{self.base_url}{local_path}"
|
||||
|
||||
StudentShowcase.objects.update_or_create(
|
||||
title=v_data['title'],
|
||||
defaults={
|
||||
'cover_image': full_url,
|
||||
'cover_image': local_path,
|
||||
'video_url': v_data['video_url'],
|
||||
'description': v_data['description']
|
||||
}
|
||||
|
||||
43
admin/server/apps/crm/migrations/0032_auto_20251212_1146.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 3.2.23 on 2025-12-12 03:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('crm', '0031_student_city'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='banner',
|
||||
name='image',
|
||||
field=models.CharField(max_length=500, verbose_name='图片URL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='image',
|
||||
field=models.CharField(default='https://images.unsplash.com/photo-1526379095098-d400fd0bf935', max_length=500, verbose_name='封面图片URL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='student',
|
||||
name='avatar',
|
||||
field=models.CharField(default='https://images.unsplash.com/photo-1535713875002-d1d0cf377fde', max_length=500, verbose_name='微信头像'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='studenthonor',
|
||||
name='image',
|
||||
field=models.CharField(default='https://images.unsplash.com/photo-1579548122080-c35fd6820ecb', max_length=500, verbose_name='证书图片URL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='studentshowcase',
|
||||
name='cover_image',
|
||||
field=models.CharField(max_length=500, verbose_name='封面图片URL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='studentshowcase',
|
||||
name='video_url',
|
||||
field=models.CharField(blank=True, max_length=500, null=True, verbose_name='视频链接URL'),
|
||||
),
|
||||
]
|
||||
@@ -49,7 +49,7 @@ class Project(models.Model):
|
||||
# category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name="所属分类", related_name="projects")
|
||||
teacher = models.ForeignKey(Teacher, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="教学中心", related_name="projects")
|
||||
custom_teacher = models.CharField(max_length=100, verbose_name="自定义教学中心", default="", blank=True)
|
||||
image = models.URLField(verbose_name="封面图片URL", default="https://images.unsplash.com/photo-1526379095098-d400fd0bf935")
|
||||
image = models.CharField(max_length=500, verbose_name="封面图片URL", default="https://images.unsplash.com/photo-1526379095098-d400fd0bf935")
|
||||
detail = models.TextField(verbose_name="项目详情", blank=True, default="")
|
||||
students = models.IntegerField(default=0, verbose_name="学习人数")
|
||||
address = models.CharField(max_length=200, verbose_name="地址", default="", blank=True)
|
||||
@@ -106,7 +106,7 @@ class Coupon(models.Model):
|
||||
return self.title
|
||||
|
||||
class Banner(models.Model):
|
||||
image = models.URLField(verbose_name="图片URL")
|
||||
image = models.CharField(max_length=500, verbose_name="图片URL")
|
||||
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联项目")
|
||||
link = models.CharField(max_length=200, blank=True, null=True, verbose_name="跳转链接")
|
||||
sort_order = models.IntegerField(default=0, verbose_name="排序")
|
||||
@@ -134,7 +134,7 @@ class Student(models.Model):
|
||||
address = models.CharField(max_length=200, verbose_name="地址", null=True, blank=True)
|
||||
city = models.CharField(max_length=50, verbose_name="城市", null=True, blank=True)
|
||||
# 已经有avatar字段,对应微信头像
|
||||
avatar = models.URLField(verbose_name="微信头像", default="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde")
|
||||
avatar = models.CharField(max_length=500, verbose_name="微信头像", default="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde")
|
||||
is_active = models.BooleanField(default=True, verbose_name="是否活跃")
|
||||
|
||||
enrolled_projects = models.ManyToManyField(Project, through='StudentProject', related_name='enrolled_students', verbose_name="已报名项目", blank=True)
|
||||
@@ -255,7 +255,7 @@ def update_student_learning_count_on_delete(sender, instance, **kwargs):
|
||||
class StudentHonor(models.Model):
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name="honors", verbose_name="学员")
|
||||
title = models.CharField(max_length=100, verbose_name="荣誉标题")
|
||||
image = models.URLField(verbose_name="证书图片URL", default="https://images.unsplash.com/photo-1579548122080-c35fd6820ecb")
|
||||
image = models.CharField(max_length=500, verbose_name="证书图片URL", default="https://images.unsplash.com/photo-1579548122080-c35fd6820ecb")
|
||||
date = models.DateField(verbose_name="获得日期")
|
||||
description = models.TextField(verbose_name="荣誉描述", blank=True, default="")
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
@@ -270,8 +270,8 @@ class StudentHonor(models.Model):
|
||||
|
||||
class StudentShowcase(models.Model):
|
||||
title = models.CharField(max_length=100, verbose_name="标题")
|
||||
cover_image = models.URLField(verbose_name="封面图片URL")
|
||||
video_url = models.URLField(verbose_name="视频链接URL", blank=True, null=True)
|
||||
cover_image = models.CharField(max_length=500, verbose_name="封面图片URL")
|
||||
video_url = models.CharField(max_length=500, verbose_name="视频链接URL", blank=True, null=True)
|
||||
description = models.TextField(verbose_name="描述", blank=True, default="")
|
||||
student = models.ForeignKey(Student, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联学员", related_name="showcases")
|
||||
sort_order = models.IntegerField(default=0, verbose_name="排序")
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Category, Teacher, TeachingCenter, Project, Coupon, Banner, Student, StudentCoupon, StudentProject, StudentHonor, StudentShowcase, Notification, NotificationBatch
|
||||
|
||||
class AbsoluteURLField(serializers.CharField):
|
||||
def to_representation(self, value):
|
||||
if not value:
|
||||
return value
|
||||
if value.startswith(('http://', 'https://')):
|
||||
return value
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
return request.build_absolute_uri(value)
|
||||
return value
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if data and isinstance(data, str) and data.startswith(('http://', 'https://')):
|
||||
# If it's an absolute URL, try to extract the relative path
|
||||
# We assume standard media URL structure '/media/'
|
||||
if '/media/' in data:
|
||||
return '/media/' + data.split('/media/', 1)[1]
|
||||
return super().to_internal_value(data)
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Category
|
||||
@@ -19,13 +38,24 @@ class TeachingCenterSerializer(serializers.ModelSerializer):
|
||||
class ProjectSerializer(serializers.ModelSerializer):
|
||||
# category_name = serializers.CharField(source='category.name', read_only=True)
|
||||
# category_color = serializers.CharField(source='category.color', read_only=True)
|
||||
category_name = serializers.CharField(source='get_project_type_display', read_only=True)
|
||||
category_color = serializers.SerializerMethodField()
|
||||
teacher_name = serializers.SerializerMethodField()
|
||||
project_type_display = serializers.CharField(source='get_project_type_display', read_only=True)
|
||||
image = AbsoluteURLField()
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = '__all__'
|
||||
|
||||
def get_category_color(self, obj):
|
||||
colors = {
|
||||
'training': 'bg-cyan-100 text-cyan-600',
|
||||
'competition': 'bg-purple-100 text-purple-600',
|
||||
'grading': 'bg-blue-100 text-blue-600'
|
||||
}
|
||||
return colors.get(obj.project_type, 'bg-gray-100 text-gray-600')
|
||||
|
||||
def get_teacher_name(self, obj):
|
||||
if obj.teacher:
|
||||
return obj.teacher.name
|
||||
@@ -62,6 +92,7 @@ class StudentCouponSerializer(serializers.ModelSerializer):
|
||||
|
||||
class BannerSerializer(serializers.ModelSerializer):
|
||||
project_title = serializers.CharField(source='project.title', read_only=True)
|
||||
image = AbsoluteURLField()
|
||||
class Meta:
|
||||
model = Banner
|
||||
fields = '__all__'
|
||||
@@ -74,6 +105,7 @@ class StudentSerializer(serializers.ModelSerializer):
|
||||
coupons = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='student_coupons')
|
||||
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
||||
teacher = serializers.PrimaryKeyRelatedField(source='responsible_teacher', queryset=Teacher.objects.all(), required=False, allow_null=True)
|
||||
avatar = AbsoluteURLField()
|
||||
|
||||
class Meta:
|
||||
model = Student
|
||||
@@ -102,7 +134,7 @@ class StudentProjectSerializer(serializers.ModelSerializer):
|
||||
student_name = serializers.CharField(source='student.name', read_only=True)
|
||||
student_phone = serializers.CharField(source='student.phone', read_only=True)
|
||||
project_title = serializers.CharField(source='project.title', read_only=True)
|
||||
project_image = serializers.CharField(source='project.image', read_only=True)
|
||||
project_image = AbsoluteURLField(source='project.image', read_only=True)
|
||||
project_type = serializers.CharField(source='project.project_type', read_only=True)
|
||||
project_type_display = serializers.CharField(source='project.get_project_type_display', read_only=True)
|
||||
|
||||
@@ -113,10 +145,11 @@ class StudentProjectSerializer(serializers.ModelSerializer):
|
||||
class StudentHonorSerializer(serializers.ModelSerializer):
|
||||
student_name = serializers.CharField(source='student.name', read_only=True)
|
||||
student_phone = serializers.CharField(source='student.phone', read_only=True)
|
||||
student_avatar = serializers.CharField(source='student.avatar', read_only=True)
|
||||
student_avatar = AbsoluteURLField(source='student.avatar', read_only=True)
|
||||
student_openid = serializers.CharField(source='student.openid', read_only=True)
|
||||
student_teaching_center = serializers.CharField(source='student.teacher.name', read_only=True)
|
||||
student_company_name = serializers.CharField(source='student.company_name', read_only=True)
|
||||
image = AbsoluteURLField()
|
||||
|
||||
class Meta:
|
||||
model = StudentHonor
|
||||
@@ -124,6 +157,8 @@ class StudentHonorSerializer(serializers.ModelSerializer):
|
||||
|
||||
class StudentShowcaseSerializer(serializers.ModelSerializer):
|
||||
student_name = serializers.CharField(source='student.name', read_only=True)
|
||||
cover_image = AbsoluteURLField()
|
||||
video_url = AbsoluteURLField()
|
||||
|
||||
class Meta:
|
||||
model = StudentShowcase
|
||||
|
||||
@@ -212,7 +212,7 @@ class DashboardStatsView(APIView):
|
||||
pie_chart_data.append({
|
||||
'type': type_code,
|
||||
'name': type_mapping.get(type_code, type_code),
|
||||
'value': item['total_students'],
|
||||
'value': item['total_students'] or 0,
|
||||
'itemStyle': { 'color': color }
|
||||
})
|
||||
|
||||
@@ -344,6 +344,24 @@ class CategoryViewSet(viewsets.ModelViewSet):
|
||||
pagination_class = None # Return all categories without pagination for the app
|
||||
search_fields = ['name']
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
# Instead of database categories, return Project.PROJECT_TYPE_CHOICES
|
||||
data = []
|
||||
# Define some default colors if needed, or mapped by type
|
||||
colors = {
|
||||
'training': 'bg-cyan-100 text-cyan-600',
|
||||
'competition': 'bg-purple-100 text-purple-600',
|
||||
'grading': 'bg-blue-100 text-blue-600'
|
||||
}
|
||||
|
||||
for code, name in Project.PROJECT_TYPE_CHOICES:
|
||||
data.append({
|
||||
'id': code,
|
||||
'name': name,
|
||||
'color': colors.get(code, 'bg-gray-100 text-gray-600')
|
||||
})
|
||||
return Response(data)
|
||||
|
||||
class TeacherViewSet(viewsets.ModelViewSet):
|
||||
queryset = Teacher.objects.all()
|
||||
serializer_class = TeacherSerializer
|
||||
|
||||
@@ -54,6 +54,14 @@ class FileSerializer(serializers.ModelSerializer):
|
||||
model = File
|
||||
fields = "__all__"
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
request = self.context.get('request')
|
||||
if instance.path and request:
|
||||
if not instance.path.startswith(('http://', 'https://')):
|
||||
ret['path'] = request.build_absolute_uri(instance.path)
|
||||
return ret
|
||||
|
||||
class DictTypeSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
数据字典类型序列化
|
||||
|
||||
@@ -424,26 +424,8 @@ class FileViewSet(CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListM
|
||||
# Ensure forward slashes for URL
|
||||
file_name = instance.file.name.replace('\\', '/')
|
||||
|
||||
# 开发阶段,本机上传的视频或图片用本机IP保存
|
||||
if settings.DEBUG and (type == '视频' or type == '图片'):
|
||||
try:
|
||||
# 获取本机IP
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(('8.8.8.8', 80))
|
||||
ip = s.getsockname()[0]
|
||||
s.close()
|
||||
|
||||
# 获取端口
|
||||
host = self.request.get_host()
|
||||
port = host.split(':')[1] if ':' in host else '80'
|
||||
|
||||
# 构建URL
|
||||
instance.path = f"{self.request.scheme}://{ip}:{port}{settings.MEDIA_URL}{file_name}"
|
||||
except Exception as e:
|
||||
logger.error(f"获取本机IP失败: {e}")
|
||||
instance.path = self.request.build_absolute_uri(settings.MEDIA_URL + file_name)
|
||||
else:
|
||||
instance.path = self.request.build_absolute_uri(settings.MEDIA_URL + file_name)
|
||||
# Save relative path
|
||||
instance.path = settings.MEDIA_URL + file_name
|
||||
|
||||
logger.info(f"File uploaded: {instance.path}")
|
||||
instance.save()
|
||||
@@ -459,3 +441,21 @@ class FileViewSet(CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListM
|
||||
file_obj.file.delete(save=False)
|
||||
file_obj.delete(soft=False)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@action(methods=['post'], detail=False)
|
||||
def replace_url(self, request):
|
||||
old_url = request.data.get('old_url')
|
||||
new_url = request.data.get('new_url')
|
||||
|
||||
if not old_url or not new_url:
|
||||
return Response({'error': 'Please provide both old_url and new_url'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
queryset = self.get_queryset()
|
||||
count = 0
|
||||
for file_obj in queryset:
|
||||
if file_obj.path and old_url in file_obj.path:
|
||||
file_obj.path = file_obj.path.replace(old_url, new_url)
|
||||
file_obj.save()
|
||||
count += 1
|
||||
|
||||
return Response({'message': f'Updated {count} files'}, status=status.HTTP_200_OK)
|
||||
|
||||
|
Before Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 91 KiB |
BIN
admin/server/media/2025/12/12/上海台风.mp4
Normal file
29
project.config.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"appid": "wx2d9b9759137ef46b",
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "3.12.1",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"setting": {
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"enhance": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"condition": false
|
||||
},
|
||||
"condition": {},
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 2
|
||||
}
|
||||
}
|
||||
7
project.private.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
|
||||
"projectname": "geminiWX",
|
||||
"setting": {
|
||||
"compileHotReLoad": true
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
const env = 'development'
|
||||
const configs = {
|
||||
development: {
|
||||
baseUrl: 'http://192.168.5.95:8000/api'
|
||||
baseUrl: 'http://192.168.5.112:8000/api'
|
||||
},
|
||||
production: {
|
||||
baseUrl: 'https://your-domain.example.com/api'
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
<view class="category-item {{selectedCategory === 'all' ? 'active' : ''}}" bindtap="handleCategorySelect" data-type="all">
|
||||
<text>全部</text>
|
||||
</view>
|
||||
<view class="category-item {{selectedCategory === 'training' ? 'active' : ''}}" bindtap="handleCategorySelect" data-type="training" wx:if="{{categoryStats.training > 0}}">
|
||||
<view class="category-item {{selectedCategory === 'training' ? 'active' : ''}}" bindtap="handleCategorySelect" data-type="training">
|
||||
<text>在职项目</text>
|
||||
</view>
|
||||
<view class="category-item {{selectedCategory === 'competition' ? 'active' : ''}}" bindtap="handleCategorySelect" data-type="competition" wx:if="{{categoryStats.competition > 0}}">
|
||||
<view class="category-item {{selectedCategory === 'competition' ? 'active' : ''}}" bindtap="handleCategorySelect" data-type="competition">
|
||||
<text>典礼&论坛</text>
|
||||
</view>
|
||||
<view class="category-item {{selectedCategory === 'grading' ? 'active' : ''}}" bindtap="handleCategorySelect" data-type="grading" wx:if="{{categoryStats.grading > 0}}">
|
||||
<view class="category-item {{selectedCategory === 'grading' ? 'active' : ''}}" bindtap="handleCategorySelect" data-type="grading">
|
||||
<text>校友活动</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||