Skip to content

Comment 评论

评论列表与编辑表单。支持表情、回复、删除、分页,以及登录与游客双模式。

基础用法

评论

3
  • 周野

    该评论已被删除

  • 林晚作者

    评论组件支持表情,比如 :smile: :heart:

  • 沈青

    回复 @林晚: 回复也支持,而且是平铺的。

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

const comments = ref([
  {
    id: 1,
    author: { id: 1, name: '林晚' },
    content: '这是一条评论',
    createdAt: Date.now(),
    likes: 12
  }
])

// 传 null 或不传即进入游客模式
const user = ref({ id: 99, name: '我', isSelf: true })
</script>

<template>
  <SrComment :comments="comments" :user="user" />
</template>

登录与游客双模式

usernull 即进入游客模式:表单会多出昵称 / 邮箱字段, 且所有评论都不显示「删除」。

评论

1
  • 游客

    游客模式下表单会多出昵称 / 邮箱字段。

vue
<!-- 游客模式 -->
<SrComment :comments="comments" :user="null" />

<!-- 登录模式 -->
<SrComment :comments="comments" :user="currentUser" />

是否显示「删除」由 author.isSelf 决定,不靠猜

组件不猜测「这条是不是我发的」。它只看 author.isSelf

按昵称比对是不行的——游客模式下所有评论都来自同一浏览器, 昵称重名极常见;靠 localStorage 里存的 id 判断也不可靠(用户清缓存就丢)。

「我是谁」这个信息只有宿主知道,所以由宿主在数据里标明。

只能回复一层

replies 里的项不再渲染自己的回复——所有回复平铺在同一层, 通过 replyTo 标明回复对象。

评论

3
  • 周野

    该评论已被删除

  • 林晚作者

    评论组件支持表情,比如 :smile: :heart:

  • 沈青

    回复 @林晚: 回复也支持,而且是平铺的。

回复项里的「回复 @某某」由 replyTo 生成

vue
{
  id: 2,
  author: { id: 2, name: '沈青' },
  content: '回复内容',
  replyTo: { id: 1, name: '林晚' }   // 决定显示「回复 @林晚:」
}

无限嵌套在窄屏上不可用

每多一层缩进,正文宽度就少一截。手机屏幕上三层以后正文会被挤成一条竖线。

实际产品里也很少见到真正的无限嵌套——论坛常见做法就是诚实地平铺一层。 需要表达「回复了谁」时用 replyTo,比缩进更清楚也更省空间。

软删除

deleted: true 的评论显示占位而非直接移除

评论

0

还没有评论,来说两句吧

vue
{ id: 3, content: '…', deleted: true }

为什么要占位:直接删掉的话,它下面的回复会失去上下文—— 「你说得对」变成了一句没头没尾的话。占位保留了对话的完整性。

分页

pagination 有三种模式,对应两种数据策略:

评论

3
  • 周野

    该评论已被删除

  • 林晚作者

    评论组件支持表情,比如 :smile: :heart:

  • 沈青

    回复 @林晚: 回复也支持,而且是平铺的。

vue
<SrComment pagination="pager" :page-size="10" />
<SrComment pagination="more" :has-more="hasMore" :loading-more="loading" @load-more="onLoadMore" />
<SrComment pagination="none" />
模式数据策略谁负责切片
none全部一次性给出都不切
pager前端分页组件对已加载的数组切片
more服务端分页组件不做切片,只抛事件由宿主追加

一句话:pager 是前端分页,more 是服务端分页。 评论总数通常远超一页, 因此生产环境更常用 more

autoLoadMore 仍然保留按钮

autoLoadMore 开启后滚动到底自动加载,但按钮不隐藏

原因:容器被完全展开、或浏览器节流了滚动事件时, 自动加载可能不触发。此时按钮是用户唯一的入口——有总比没有好。

排序

showSort 显示排序切换(最新 / 最早 / 最热)。

评论

3
  • 林晚作者

    评论组件支持表情,比如 :smile: :heart:

  • 沈青

    回复 @林晚: 回复也支持,而且是平铺的。

  • 周野

    该评论已被删除

  • vue
    <SrComment sort="latest" show-sort />

    自定义槽位

    组件提供十余个具名插槽(avatar / level / region / os / browser / meta / actions / footer / editor),所有槽位收到的数据形状一致

    vue
    <SrComment :comments="comments" :user="user">
      <template #level="{ level }">
        <SrTag v-if="level" variant="primary">Lv.{{ level }}</SrTag>
      </template>
    
      <template #region="{ region }">
        <span class="region">{{ region }}</span>
      </template>
    </SrComment>
    字段说明
    comment当前这条评论
    author作者
    authorMeta / commentMeta扩展数据(组件不解释内容)
    level / region / os / browser从 meta 里提上来的便捷字段

    level / region / os / browser 的类型是 unknown—— 它们取自 meta 这个自由容器,组件不解释其内容,宿主按自己的约定断言即可。

    Props

    名称类型默认值说明
    commentsSrCommentItem[][]评论列表
    userSrCommentAuthor | nullnull当前用户,null 为游客模式
    allowGuestbooleantrue是否允许游客评论
    requireGuestNamebooleantrue游客昵称是否必填
    requireGuestEmailbooleanfalse游客邮箱是否必填
    maxLengthnumber500最大字数
    sort'latest' | 'oldest' | 'hottest''latest'初始排序
    showSortbooleantrue是否显示排序切换
    pagination'none' | 'pager' | 'more''none'分页方式
    pageSizenumber10每页条数
    hasMorebooleantrue是否还有更多(more 模式)
    loadingMorebooleanfalse是否正在加载更多
    autoLoadMorebooleanfalse滚到底自动加载
    loadingbooleanfalse是否加载中
    submittingbooleanfalse是否正在提交
    likeable / replyable / deletablebooleantrue是否允许点赞 / 回复 / 删除
    emojiablebooleantrue是否启用表情面板
    showEditorbooleantrue是否显示评论框

    事件

    事件参数说明
    submitSrCommentSubmitPayload提交评论或回复
    like(comment)点赞
    reply(comment)点击回复
    delete(comment)请求删除(不移除数据
    load-more请求加载更多
    sort-changesort排序变化

    关于安全

    前端校验只是「防误填」

    邮箱格式、网址协议、昵称长度与敏感词,都必须在服务端再查一遍

    尤其是 author.website:若宿主把它渲染成链接,请务必在服务端校验协议, 只允许 http(s)。否则 javascript: 开头的「网址」就是一个可点击的 XSS 载体。

    组件不做白名单校验——「这个站点该不该放行外链」是业务判断,不是组件能决定的。

    无障碍