diff --git a/backend/community/admin.py b/backend/community/admin.py index b9f85d3..a1a33e3 100644 --- a/backend/community/admin.py +++ b/backend/community/admin.py @@ -153,10 +153,11 @@ class TopicAdmin(ModelAdmin): @admin.register(Reply) class ReplyAdmin(ModelAdmin): - list_display = ('short_content', 'topic', 'author', 'created_at') - list_filter = ('created_at',) + list_display = ('short_content', 'topic', 'author', 'is_pinned', 'created_at') + list_filter = ('is_pinned', 'created_at') search_fields = ('content', 'author__nickname', 'topic__title') autocomplete_fields = ['author', 'topic', 'reply_to'] + list_editable = ('is_pinned',) inlines = [TopicMediaInline] fieldsets = ( @@ -164,7 +165,7 @@ class ReplyAdmin(ModelAdmin): 'fields': ('topic', 'reply_to', 'content') }), ('发布信息', { - 'fields': ('author', 'created_at') + 'fields': ('author', 'is_pinned', 'created_at') }), ) readonly_fields = ('created_at',) diff --git a/backend/community/migrations/0013_alter_reply_options_reply_is_pinned.py b/backend/community/migrations/0013_alter_reply_options_reply_is_pinned.py new file mode 100644 index 0000000..8876b8f --- /dev/null +++ b/backend/community/migrations/0013_alter_reply_options_reply_is_pinned.py @@ -0,0 +1,22 @@ +# Generated by Django 6.0.1 on 2026-02-24 16:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('community', '0012_activity_is_visible'), + ] + + operations = [ + migrations.AlterModelOptions( + name='reply', + options={'ordering': ['-is_pinned', '-created_at'], 'verbose_name': '帖子回复', 'verbose_name_plural': '帖子回复管理'}, + ), + migrations.AddField( + model_name='reply', + name='is_pinned', + field=models.BooleanField(default=False, verbose_name='置顶'), + ), + ] diff --git a/backend/community/models.py b/backend/community/models.py index 2493cdc..e1cf56e 100644 --- a/backend/community/models.py +++ b/backend/community/models.py @@ -186,6 +186,7 @@ class Reply(models.Model): content = models.TextField(verbose_name="回复内容", help_text="支持Markdown格式") author = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='replies', verbose_name="回复者") reply_to = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children', verbose_name="回复楼层") + is_pinned = models.BooleanField(default=False, verbose_name="置顶") created_at = models.DateTimeField(auto_now_add=True, verbose_name="回复时间") def __str__(self): @@ -194,7 +195,7 @@ class Reply(models.Model): class Meta: verbose_name = "帖子回复" verbose_name_plural = "帖子回复管理" - ordering = ['created_at'] + ordering = ['-is_pinned', '-created_at'] class TopicMedia(models.Model): diff --git a/backend/community/serializers.py b/backend/community/serializers.py index 0fb9498..00fe015 100644 --- a/backend/community/serializers.py +++ b/backend/community/serializers.py @@ -91,7 +91,7 @@ class ReplySerializer(serializers.ModelSerializer): class Meta: model = Reply - fields = ['id', 'topic', 'content', 'author', 'author_info', 'reply_to', 'media', 'created_at', 'media_ids'] + fields = ['id', 'topic', 'content', 'author', 'author_info', 'reply_to', 'media', 'created_at', 'media_ids', 'is_pinned'] read_only_fields = ['author', 'created_at'] def create(self, validated_data): diff --git a/backend/config/settings.py b/backend/config/settings.py index 954dc43..664f04c 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -101,19 +101,7 @@ DATABASES = { #从环境变量获取数据库配置 (Docker 环境会自动注入这些变量。 -DB_HOST = os.environ.get('DB_HOST', '6.6.6.66') -if DB_HOST: - DATABASES['default'] = { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.environ.get('DB_NAME', 'market'), - 'USER': os.environ.get('DB_USER', 'market'), - 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'), - 'HOST': DB_HOST, - 'PORT': os.environ.get('DB_PORT', '5432'), - } - - -# DB_HOST = os.environ.get('DB_HOST', '121.43.104.161') +# DB_HOST = os.environ.get('DB_HOST', '6.6.6.66') # if DB_HOST: # DATABASES['default'] = { # 'ENGINE': 'django.db.backends.postgresql', @@ -121,10 +109,22 @@ if DB_HOST: # 'USER': os.environ.get('DB_USER', 'market'), # 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'), # 'HOST': DB_HOST, -# 'PORT': os.environ.get('DB_PORT', '6433'), +# 'PORT': os.environ.get('DB_PORT', '5432'), # } +DB_HOST = os.environ.get('DB_HOST', '121.43.104.161') +if DB_HOST: + DATABASES['default'] = { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('DB_NAME', 'market'), + 'USER': os.environ.get('DB_USER', 'market'), + 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'), + 'HOST': DB_HOST, + 'PORT': os.environ.get('DB_PORT', '6433'), + } + + # Password validation # https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators diff --git a/backend/shop/admin.py b/backend/shop/admin.py index 6aef97e..5e75b3c 100644 --- a/backend/shop/admin.py +++ b/backend/shop/admin.py @@ -235,6 +235,29 @@ class VCCourseAdmin(OrderableAdminMixin, ModelAdmin): list_display = ('title', 'course_type', 'price', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at', 'order_actions') search_fields = ('title', 'description', 'instructor', 'tag') list_filter = ('course_type', 'instructor', 'tag') + actions = ['reset_ordering'] + + @admin.action(description="重置排序 (按ID顺序)") + def reset_ordering(self, request, queryset): + """ + 将选中的课程(或全部)按ID顺序重新分配order值 + """ + # 如果没有选中任何项,默认处理所有(Django Admin默认行为是选中了才会触发Action,但为了稳健) + # 这里既然是Action,用户必须选中。建议用户选中所有。 + # 为了方便,如果用户只选了一个,我们可以提示他选更多,或者我们其实可以忽略queryset,直接重置所有? + # 通常Action是针对queryset的。 + # 更好的做法:对选中的queryset按ID排序,然后更新order。 + + # 这种实现方式:只重置选中的部分,可能会导致order冲突。 + # 稳妥方式:重置整个表的排序。 + + all_objects = VCCourse.objects.all().order_by('id') + for index, obj in enumerate(all_objects, start=1): + obj.order = index + obj.save(update_fields=['order']) + + self.message_user(request, f"成功重置了 {all_objects.count()} 个课程的排序权重。") + fieldsets = ( ('基本信息', { 'fields': ('title', 'description', 'course_type', 'tag', 'price') diff --git a/frontend/src/pages/ForumDetail.jsx b/frontend/src/pages/ForumDetail.jsx index 04d10d5..1df7cc1 100644 --- a/frontend/src/pages/ForumDetail.jsx +++ b/frontend/src/pages/ForumDetail.jsx @@ -316,6 +316,7 @@ const ForumDetail = () => {