This commit is contained in:
@@ -211,9 +211,10 @@ class TopicAdmin(OrderableAdminMixin, ModelAdmin):
|
|||||||
list_filter = ('status', 'category', 'is_pinned', 'created_at', 'related_product', 'related_service', 'related_course')
|
list_filter = ('status', 'category', 'is_pinned', 'created_at', 'related_product', 'related_service', 'related_course')
|
||||||
search_fields = ('title', 'content', 'author__nickname')
|
search_fields = ('title', 'content', 'author__nickname')
|
||||||
autocomplete_fields = ['author', 'related_product', 'related_service', 'related_course']
|
autocomplete_fields = ['author', 'related_product', 'related_service', 'related_course']
|
||||||
|
filter_horizontal = ('likes',)
|
||||||
inlines = [TopicMediaInline, ReplyInline]
|
inlines = [TopicMediaInline, ReplyInline]
|
||||||
actions = ['reset_ordering', 'approve_topics', 'reject_topics']
|
actions = ['reset_ordering', 'approve_topics', 'reject_topics']
|
||||||
list_editable = ('status', 'is_pinned')
|
list_editable = ('status', 'is_pinned', 'view_count')
|
||||||
|
|
||||||
@admin.action(description="批量通过审核")
|
@admin.action(description="批量通过审核")
|
||||||
def approve_topics(self, request, queryset):
|
def approve_topics(self, request, queryset):
|
||||||
@@ -245,7 +246,7 @@ class TopicAdmin(OrderableAdminMixin, ModelAdmin):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('帖子内容', {
|
('帖子内容', {
|
||||||
'fields': ('title', 'status', 'category', 'content', 'is_pinned')
|
'fields': ('title', 'status', 'category', 'content', 'is_pinned', 'likes')
|
||||||
}),
|
}),
|
||||||
('关联信息', {
|
('关联信息', {
|
||||||
'fields': ('author', 'related_product', 'related_service', 'related_course'),
|
'fields': ('author', 'related_product', 'related_service', 'related_course'),
|
||||||
@@ -274,16 +275,17 @@ class TopicAdmin(OrderableAdminMixin, ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Reply)
|
@admin.register(Reply)
|
||||||
class ReplyAdmin(ModelAdmin):
|
class ReplyAdmin(ModelAdmin):
|
||||||
list_display = ('short_content', 'topic', 'author', 'is_pinned', 'created_at')
|
list_display = ('short_content', 'topic', 'author', 'is_pinned', 'like_count', 'created_at')
|
||||||
list_filter = ('is_pinned', 'created_at')
|
list_filter = ('is_pinned', 'created_at')
|
||||||
search_fields = ('content', 'author__nickname', 'topic__title')
|
search_fields = ('content', 'author__nickname', 'topic__title')
|
||||||
autocomplete_fields = ['author', 'topic', 'reply_to']
|
autocomplete_fields = ['author', 'topic', 'reply_to']
|
||||||
|
filter_horizontal = ('likes',)
|
||||||
list_editable = ('is_pinned',)
|
list_editable = ('is_pinned',)
|
||||||
inlines = [TopicMediaInline]
|
inlines = [TopicMediaInline]
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('回复内容', {
|
('回复内容', {
|
||||||
'fields': ('topic', 'reply_to', 'content')
|
'fields': ('topic', 'reply_to', 'content', 'likes')
|
||||||
}),
|
}),
|
||||||
('发布信息', {
|
('发布信息', {
|
||||||
'fields': ('author', 'is_pinned', 'created_at')
|
'fields': ('author', 'is_pinned', 'created_at')
|
||||||
@@ -291,6 +293,10 @@ class ReplyAdmin(ModelAdmin):
|
|||||||
)
|
)
|
||||||
readonly_fields = ('created_at',)
|
readonly_fields = ('created_at',)
|
||||||
|
|
||||||
|
@display(description="点赞数")
|
||||||
|
def like_count(self, obj):
|
||||||
|
return obj.likes.count()
|
||||||
|
|
||||||
@display(description="内容摘要")
|
@display(description="内容摘要")
|
||||||
def short_content(self, obj):
|
def short_content(self, obj):
|
||||||
return obj.content[:30] + '...' if len(obj.content) > 30 else obj.content
|
return obj.content[:30] + '...' if len(obj.content) > 30 else obj.content
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ class TopicViewSet(viewsets.ModelViewSet):
|
|||||||
return Response({'error': '请先登录'}, status=401)
|
return Response({'error': '请先登录'}, status=401)
|
||||||
return super().create(request, *args, **kwargs)
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
||||||
def like(self, request, pk=None):
|
def like(self, request, pk=None):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
user = get_current_wechat_user(request)
|
user = get_current_wechat_user(request)
|
||||||
@@ -326,7 +326,7 @@ class ReplyViewSet(viewsets.ModelViewSet):
|
|||||||
return Response({'error': '请先登录'}, status=401)
|
return Response({'error': '请先登录'}, status=401)
|
||||||
return super().create(request, *args, **kwargs)
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
@action(detail=True, methods=['post'])
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
||||||
def like(self, request, pk=None):
|
def like(self, request, pk=None):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
user = get_current_wechat_user(request)
|
user = get_current_wechat_user(request)
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ const ForumDetail = () => {
|
|||||||
<EyeOutlined />
|
<EyeOutlined />
|
||||||
<span style={{ fontSize: isMobile ? 12 : 14 }}>{topic.view_count} 阅读</span>
|
<span style={{ fontSize: isMobile ? 12 : 14 }}>{topic.view_count} 阅读</span>
|
||||||
</Space>
|
</Space>
|
||||||
<Space style={{ cursor: 'pointer' }} onClick={handleLikeTopic}>
|
<Space style={{ cursor: 'pointer', background: topic.is_liked ? 'rgba(0, 185, 107, 0.15)' : 'rgba(255,255,255,0.05)', padding: '4px 10px', borderRadius: 12, transition: 'all 0.3s' }} onClick={handleLikeTopic}>
|
||||||
{topic.is_liked ? <LikeFilled style={{ color: '#00b96b' }} /> : <LikeOutlined />}
|
{topic.is_liked ? <LikeFilled style={{ color: '#00b96b' }} /> : <LikeOutlined />}
|
||||||
<span style={{ fontSize: isMobile ? 12 : 14, color: topic.is_liked ? '#00b96b' : 'inherit' }}>{topic.like_count || 0} 点赞</span>
|
<span style={{ fontSize: isMobile ? 12 : 14, color: topic.is_liked ? '#00b96b' : 'inherit' }}>{topic.like_count || 0} 点赞</span>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -362,7 +362,7 @@ const ForumDetail = () => {
|
|||||||
<Text style={{ color: '#666', fontSize: 12 }}>{new Date(reply.created_at).toLocaleString()}</Text>
|
<Text style={{ color: '#666', fontSize: 12 }}>{new Date(reply.created_at).toLocaleString()}</Text>
|
||||||
</Space>
|
</Space>
|
||||||
<Space size={isMobile ? 'small' : 'middle'} align="center">
|
<Space size={isMobile ? 'small' : 'middle'} align="center">
|
||||||
<Space onClick={() => handleLikeReply(reply.id)} style={{ cursor: 'pointer' }}>
|
<Space onClick={() => handleLikeReply(reply.id)} style={{ cursor: 'pointer', background: reply.is_liked ? 'rgba(0, 185, 107, 0.15)' : 'transparent', padding: '2px 8px', borderRadius: 4 }}>
|
||||||
{reply.is_liked ? <LikeFilled style={{ color: '#00b96b' }} /> : <LikeOutlined style={{ color: '#666' }} />}
|
{reply.is_liked ? <LikeFilled style={{ color: '#00b96b' }} /> : <LikeOutlined style={{ color: '#666' }} />}
|
||||||
<span style={{ fontSize: 12, color: reply.is_liked ? '#00b96b' : '#666' }}>{reply.like_count || 0}</span>
|
<span style={{ fontSize: 12, color: reply.is_liked ? '#00b96b' : '#666' }}>{reply.like_count || 0}</span>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -239,9 +239,9 @@ const ForumDetail = () => {
|
|||||||
<AtIcon value='eye' size='14' color='#666' style={{marginRight: 4}} />
|
<AtIcon value='eye' size='14' color='#666' style={{marginRight: 4}} />
|
||||||
<Text>{topic.view_count}</Text>
|
<Text>{topic.view_count}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{display: 'flex', alignItems: 'center', marginLeft: 10}} onClick={handleLikeTopic}>
|
<View style={{display: 'flex', alignItems: 'center', marginLeft: 20, padding: 8, borderRadius: 12, backgroundColor: topic.is_liked ? '#fff0f0' : '#f5f5f5'}} onClick={handleLikeTopic}>
|
||||||
<AtIcon value={topic.is_liked ? 'heart-2' : 'heart'} size='14' color={topic.is_liked ? '#ff4d4f' : '#666'} style={{marginRight: 4}} />
|
<AtIcon value={topic.is_liked ? 'heart-2' : 'heart'} size='18' color={topic.is_liked ? '#ff4d4f' : '#666'} style={{marginRight: 4}} />
|
||||||
<Text style={{color: topic.is_liked ? '#ff4d4f' : '#666'}}>{topic.like_count || 0}</Text>
|
<Text style={{color: topic.is_liked ? '#ff4d4f' : '#666', fontSize: 14}}>{topic.like_count || 0}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{userInfo && topic.author === userInfo.id && (
|
{userInfo && topic.author === userInfo.id && (
|
||||||
@@ -284,9 +284,9 @@ const ForumDetail = () => {
|
|||||||
<Text style={{fontSize: 10, color: '#666', marginTop: 2}}>#{idx + 1} • {new Date(reply.created_at).toLocaleDateString()}</Text>
|
<Text style={{fontSize: 10, color: '#666', marginTop: 2}}>#{idx + 1} • {new Date(reply.created_at).toLocaleDateString()}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{display: 'flex', alignItems: 'center'}}>
|
<View style={{display: 'flex', alignItems: 'center'}}>
|
||||||
<View onClick={() => handleLikeReply(reply.id)} style={{marginRight: 10, display: 'flex', alignItems: 'center'}}>
|
<View onClick={() => handleLikeReply(reply.id)} style={{marginRight: 15, display: 'flex', alignItems: 'center', padding: '4px 8px', borderRadius: 4, backgroundColor: reply.is_liked ? '#fff0f0' : 'transparent'}}>
|
||||||
<AtIcon value={reply.is_liked ? 'heart-2' : 'heart'} size='14' color={reply.is_liked ? '#ff4d4f' : '#ccc'} />
|
<AtIcon value={reply.is_liked ? 'heart-2' : 'heart'} size='16' color={reply.is_liked ? '#ff4d4f' : '#ccc'} />
|
||||||
<Text style={{fontSize: 10, color: reply.is_liked ? '#ff4d4f' : '#999', marginLeft: 2}}>{reply.like_count || 0}</Text>
|
<Text style={{fontSize: 12, color: reply.is_liked ? '#ff4d4f' : '#999', marginLeft: 4}}>{reply.like_count || 0}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View onClick={() => handleReplyToUser(reply.author_info?.nickname)} style={{marginRight: 10, padding: '2px 6px', background: '#f0f0f0', borderRadius: 4}}>
|
<View onClick={() => handleReplyToUser(reply.author_info?.nickname)} style={{marginRight: 10, padding: '2px 6px', background: '#f0f0f0', borderRadius: 4}}>
|
||||||
<Text style={{fontSize: 10, color: '#666'}}>回复</Text>
|
<Text style={{fontSize: 10, color: '#666'}}>回复</Text>
|
||||||
|
|||||||
Reference in New Issue
Block a user