辣评评论管理系统全面优化(十九)

 

时间线说明

本文主体记录 2026-02-28 的评论管理优化。
其中“前台评论可编辑删除”“移动端独立详情页”“统一评论详情组件”等能力在 2026-03-06 有后续增强,本篇已合并追记。


评论管理优化概述

评论管理系统是辣评平台的核心功能之一。本次优化涵盖了评论筛选、详情展示、编辑删除、代评功能等多个方面,显著提升了管理效率和用户体验。

优化目标

  • 完善评论筛选功能
  • 优化评论详情展示
  • 改进编辑与删除功能
  • 修复代评功能验证
  • 优化评分处理逻辑

评论筛选功能

统一筛选组件

<template>
  <div class="comment-filter">
    <el-form :inline="true" :model="filters">
      <el-form-item label="比赛届次">
        <el-select
          v-model="filters.competitionId"
          placeholder="选择届次"
          @change="handleFilterChange"
          clearable
        >
          <el-option
            v-for="comp in competitions"
            :key="comp.id"
            :label="`第${comp.number}届`"
            :value="comp.id"
          />
        </el-select>
      </el-form-item>

      <el-form-item label="投稿作品">
        <el-select
          v-model="filters.submissionId"
          placeholder="选择作品"
          @change="handleFilterChange"
          clearable
          filterable
        >
          <el-option
            v-for="sub in submissions"
            :key="sub.id"
            :label="sub.title"
            :value="sub.id"
          />
        </el-select>
      </el-form-item>

      <el-form-item label="评论者">
        <el-input
          v-model="filters.username"
          placeholder="输入用户名"
          @input="handleUsernameInput"
          clearable
        />
      </el-form-item>

      <el-form-item label="评分">
        <el-select
          v-model="filters.score"
          placeholder="选择评分"
          @change="handleFilterChange"
          clearable
        >
          <el-option label="全部" :value="null" />
          <el-option label="5星" :value="5" />
          <el-option label="4星" :value="4" />
          <el-option label="3星" :value="3" />
          <el-option label="2星" :value="2" />
          <el-option label="1星" :value="1" />
          <el-option label="0星" :value="0" />
        </el-select>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { debounce } from 'lodash-es'

const emit = defineEmits(['filter-change'])

const filters = ref({
  competitionId: null,
  submissionId: null,
  username: '',
  score: null
})

const competitions = ref([])
const submissions = ref([])

const handleFilterChange = () => {
  emit('filter-change', filters.value)
}

const handleUsernameInput = debounce(() => {
  handleFilterChange()
}, 500)

onMounted(async () => {
  // 加载比赛和投稿列表
  await loadCompetitions()
  await loadSubmissions()
})
</script>

评论详情展示优化

PC端对话框展示

<template>
  <el-dialog
    v-model="visible"
    title="评论详情"
    width="600px"
    :close-on-click-modal="false"
  >
    <div class="comment-detail">
      <!-- 评论者信息 -->
      <div class="comment-header">
        <el-avatar :src="comment.user.avatar" />
        <div class="user-info">
          <div class="username"></div>
          <div class="time"></div>
        </div>
        <el-rate v-model="comment.score" disabled show-score />
      </div>

      <!-- 投稿信息 -->
      <div class="submission-info">
        <el-tag type="info"></el-tag>
        <span class="author">作者:</span>
      </div>

      <!-- 评论内容 -->
      <div class="comment-content">
        <el-input
          v-if="isEditing"
          v-model="editContent"
          type="textarea"
          :rows="10"
          placeholder="请输入评论内容"
        />
        <div v-else class="content-text">
          
        </div>
      </div>

      <!-- 统计信息 -->
      <div class="comment-stats">
        <el-descriptions :column="2" size="small" border>
          <el-descriptions-item label="字数">
            
          </el-descriptions-item>
          <el-descriptions-item label="评分"></el-descriptions-item>
          <el-descriptions-item label="创建时间">
            
          </el-descriptions-item>
          <el-descriptions-item label="更新时间">
            
          </el-descriptions-item>
        </el-descriptions>
      </div>
    </div>

    <template #footer>
      <div class="dialog-footer">
        <el-button v-if="!isEditing" @click="handleEdit">编辑</el-button>
        <el-button v-if="!isEditing" type="danger" @click="handleDelete">删除</el-button>
        <el-button v-if="isEditing" @click="cancelEdit">取消</el-button>
        <el-button v-if="isEditing" type="primary" @click="saveEdit">保存</el-button>
        <el-button @click="visible = false">关闭</el-button>
      </div>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { updateComment, deleteComment } from '@/api/comment'

const props = defineProps({
  modelValue: Boolean,
  comment: Object
})

const emit = defineEmits(['update:modelValue', 'refresh'])

const visible = ref(props.modelValue)
const isEditing = ref(false)
const editContent = ref('')

watch(() => props.modelValue, (val) => {
  visible.value = val
  if (val && props.comment) {
    editContent.value = props.comment.content
  }
})

watch(visible, (val) => {
  emit('update:modelValue', val)
})

const handleEdit = () => {
  isEditing.value = true
}

const cancelEdit = () => {
  isEditing.value = false
  editContent.value = props.comment.content
}

const saveEdit = async () => {
  try {
    await updateComment(props.comment.id, {
      content: editContent.value
    })
    ElMessage.success('保存成功')
    isEditing.value = false
    emit('refresh')
  } catch (error) {
    ElMessage.error('保存失败')
  }
}

const handleDelete = async () => {
  try {
    await ElMessageBox.confirm('确定删除此评论吗?', '提示', {
      type: 'warning'
    })
    await deleteComment(props.comment.id)
    ElMessage.success('删除成功')
    visible.value = false
    emit('refresh')
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}
</script>

<style scoped>
.comment-detail {
  padding: 16px;
}

.comment-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid #ebeef5;
}

.user-info {
  flex: 1;
}

.username {
  font-size: 16px;
  font-weight: bold;
  color: #303133;
}

.time {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}

.submission-info {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.author {
  font-size: 14px;
  color: #606266;
}

.comment-content {
  margin-bottom: 16px;
}

.content-text {
  line-height: 1.8;
  color: #606266;
  white-space: pre-wrap;
}

.comment-stats {
  margin-top: 16px;
}
</style>

移动端全屏页面

<template>
  <div class="mobile-comment-detail">
    <div class="detail-header">
      <el-button @click="goBack" circle>
        <el-icon><ArrowLeft /></el-icon>
      </el-button>
      <span class="title">评论详情</span>
      <el-dropdown @command="handleCommand">
        <el-button circle>
          <el-icon><More /></el-icon>
        </el-button>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item command="edit">编辑</el-dropdown-item>
            <el-dropdown-item command="delete">删除</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>

    <div class="detail-content">
      <!-- 评论卡片 -->
      <el-card class="info-card">
        <div class="user-section">
          <el-avatar :src="comment.user.avatar" :size="48" />
          <div class="user-info">
            <div class="username"></div>
            <div class="time"></div>
          </div>
        </div>
        <el-rate v-model="comment.score" disabled show-score />
      </el-card>

      <!-- 投稿信息卡片 -->
      <el-card class="submission-card">
        <template #header>
          <span>投稿作品</span>
        </template>
        <div class="submission-info">
          <div class="title"></div>
          <div class="author">作者:</div>
        </div>
      </el-card>

      <!-- 评论内容卡片 -->
      <el-card class="content-card">
        <template #header>
          <span>评论内容</span>
        </template>
        <div class="content-text">
          
        </div>
      </el-card>

      <!-- 统计信息卡片 -->
      <el-card class="stats-card">
        <template #header>
          <span>统计信息</span>
        </template>
        <el-descriptions :column="1" size="small">
          <el-descriptions-item label="字数">
            
          </el-descriptions-item>
          <el-descriptions-item label="评分"></el-descriptions-item>
          <el-descriptions-item label="创建时间">
            
          </el-descriptions-item>
        </el-descriptions>
      </el-card>
    </div>
  </div>
</template>

<script setup>
import { useRouter } from 'vue-router'
import { ArrowLeft, More } from '@element-plus/icons-vue'

const router = useRouter()

const goBack = () => {
  router.back()
}

const handleCommand = (command) => {
  if (command === 'edit') {
    // 跳转到编辑页面
    router.push(`/comment/edit/${props.comment.id}`)
  } else if (command === 'delete') {
    // 删除评论
    handleDelete()
  }
}
</script>

<style scoped>
.mobile-comment-detail {
  min-height: 100vh;
  background-color: #f5f7fa;
}

.detail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  background-color: #fff;
  border-bottom: 1px solid #ebeef5;
  position: sticky;
  top: 0;
  z-index: 100;
}

.title {
  font-size: 16px;
  font-weight: bold;
}

.detail-content {
  padding: 16px;
}

.info-card,
.submission-card,
.content-card,
.stats-card {
  margin-bottom: 16px;
}

.user-section {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.content-text {
  line-height: 1.8;
  color: #606266;
  white-space: pre-wrap;
}
</style>

代评功能验证修复

问题描述

原有代码中,代评功能的验证逻辑存在错误,导致评分为0时无法通过验证。

修复前

// 错误的验证逻辑
if req.Score < 1 || req.Score > 5 {
    return errors.New("评分必须在1-5之间")
}

修复后

// 正确的验证逻辑 - 允许评分为0
func (s *CommentService) ValidateScore(score int) error {
    if score < 0 || score > 5 {
        return errors.New("评分必须在0-5之间")
    }
    return nil
}

// 代评功能中的特殊处理
func (s *CommentService) CreateCommentForUser(ctx context.Context, req *CreateCommentRequest) error {
    // 验证评分(允许0分)
    if err := s.ValidateScore(req.Score); err != nil {
        return err
    }

    // 创建评论
    comment := &Comment{
        UserID:       req.UserID,
        SubmissionID: req.SubmissionID,
        Content:      req.Content,
        Score:        req.Score, // 允许为0
        CreatedBy:    req.AdminID, // 记录管理员ID
    }

    return s.db.Create(comment).Error
}

评分处理逻辑优化

评分显示组件

<template>
  <div class="score-display">
    <el-rate
      v-model="displayScore"
      :disabled="disabled"
      :show-score="showScore"
      :allow-half="allowHalf"
    />
    <span v-if="score === 0" class="zero-score-tip">
      (未评分)
    </span>
  </div>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  score: {
    type: Number,
    default: 0
  },
  disabled: {
    type: Boolean,
    default: true
  },
  showScore: {
    type: Boolean,
    default: true
  },
  allowHalf: {
    type: Boolean,
    default: false
  }
})

// 处理0分的显示
const displayScore = computed(() => {
  return props.score === 0 ? 0 : props.score
})
</script>

<style scoped>
.score-display {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.zero-score-tip {
  font-size: 12px;
  color: #909399;
}
</style>

前台评论编辑删除功能

时间补充:该能力在 2026-03-06 完成主要实现并与详情组件统一改造联动。

功能实现

<template>
  <div class="comment-item">
    <div class="comment-header">
      <div class="user-info">
        <el-avatar :src="comment.user.avatar" :size="32" />
        <span class="username"></span>
      </div>
      <div class="actions" v-if="canEdit">
        <el-button type="text" @click="handleEdit">编辑</el-button>
        <el-button type="text" @click="handleDelete">删除</el-button>
      </div>
    </div>

    <div class="comment-content">
      
    </div>

    <div class="comment-footer">
      <el-rate v-model="comment.score" disabled show-score />
      <span class="time"></span>
    </div>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'

const props = defineProps({
  comment: Object
})

const userStore = useUserStore()

// 判断是否可以编辑(只能编辑自己的评论)
const canEdit = computed(() => {
  return userStore.user.id === props.comment.userId
})

const handleEdit = () => {
  emit('edit', props.comment)
}

const handleDelete = () => {
  emit('delete', props.comment)
}
</script>

总结

评论管理系统的全面优化显著提升了管理效率和用户体验。通过完善筛选功能、优化详情展示、修复代评验证、改进评分处理等措施,我们打造了一个功能完善、体验良好的评论管理系统。

关键改进

  • 统一了评论筛选功能
  • 优化了PC端和移动端的详情展示
  • 修复了代评功能的验证错误
  • 改进了评分处理逻辑
  • 实现了前台评论编辑删除功能

用户反馈

  • 筛选功能更加便捷
  • 详情展示更加清晰
  • 代评功能正常工作
  • 评分显示更加准确
  • 前台操作更加灵活

本文遵守 Attribution-NonCommercial 4.0 International 许可协议。 Attribution-NonCommercial 4.0 International