Appearance
评论列表与编辑表单。支持表情、回复、删除、分页,以及登录与游客双模式。
该评论已被删除
评论组件支持表情,比如 :smile: :heart:
回复 @林晚: 回复也支持,而且是平铺的。
<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>
user 传 null 即进入游客模式:表单会多出昵称 / 邮箱字段, 且所有评论都不显示「删除」。
user
null
游客模式下表单会多出昵称 / 邮箱字段。
<!-- 游客模式 --> <SrComment :comments="comments" :user="null" /> <!-- 登录模式 --> <SrComment :comments="comments" :user="currentUser" />
是否显示「删除」由 author.isSelf 决定,不靠猜
author.isSelf
组件不猜测「这条是不是我发的」。它只看 author.isSelf。
按昵称比对是不行的——游客模式下所有评论都来自同一浏览器, 昵称重名极常见;靠 localStorage 里存的 id 判断也不可靠(用户清缓存就丢)。
localStorage
「我是谁」这个信息只有宿主知道,所以由宿主在数据里标明。
replies 里的项不再渲染自己的回复——所有回复平铺在同一层, 通过 replyTo 标明回复对象。
replies
replyTo
回复项里的「回复 @某某」由 replyTo 生成
{ id: 2, author: { id: 2, name: '沈青' }, content: '回复内容', replyTo: { id: 1, name: '林晚' } // 决定显示「回复 @林晚:」 }
无限嵌套在窄屏上不可用
每多一层缩进,正文宽度就少一截。手机屏幕上三层以后正文会被挤成一条竖线。
实际产品里也很少见到真正的无限嵌套——论坛常见做法就是诚实地平铺一层。 需要表达「回复了谁」时用 replyTo,比缩进更清楚也更省空间。
deleted: true 的评论显示占位而非直接移除。
deleted: true
还没有评论,来说两句吧
{ id: 3, content: '…', deleted: true }
为什么要占位:直接删掉的话,它下面的回复会失去上下文—— 「你说得对」变成了一句没头没尾的话。占位保留了对话的完整性。
pagination 有三种模式,对应两种数据策略:
pagination
<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
autoLoadMore 开启后滚动到底自动加载,但按钮不隐藏。
原因:容器被完全展开、或浏览器节流了滚动事件时, 自动加载可能不触发。此时按钮是用户唯一的入口——有总比没有好。
showSort 显示排序切换(最新 / 最早 / 最热)。
showSort
<SrComment sort="latest" show-sort />
组件提供十余个具名插槽(avatar / level / region / os / browser / meta / actions / footer / editor),所有槽位收到的数据形状一致:
avatar
level
region
os
browser
meta
actions
footer
editor
<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 的类型是 unknown—— 它们取自 meta 这个自由容器,组件不解释其内容,宿主按自己的约定断言即可。
unknown
comments
SrCommentItem[]
[]
SrCommentAuthor | null
allowGuest
boolean
true
requireGuestName
requireGuestEmail
false
maxLength
number
500
sort
'latest' | 'oldest' | 'hottest'
'latest'
'none' | 'pager' | 'more'
'none'
pageSize
10
hasMore
loadingMore
loading
submitting
likeable
replyable
deletable
emojiable
showEditor
submit
SrCommentSubmitPayload
like
(comment)
reply
delete
load-more
sort-change
前端校验只是「防误填」
邮箱格式、网址协议、昵称长度与敏感词,都必须在服务端再查一遍。
尤其是 author.website:若宿主把它渲染成链接,请务必在服务端校验协议, 只允许 http(s)。否则 javascript: 开头的「网址」就是一个可点击的 XSS 载体。
author.website
http(s)
javascript:
组件不做白名单校验——「这个站点该不该放行外链」是业务判断,不是组件能决定的。
<ul>
<li>
aria-pressed
aria-live
Comment 评论
评论列表与编辑表单。支持表情、回复、删除、分页,以及登录与游客双模式。
基础用法
评论
3该评论已被删除
评论组件支持表情,比如 :smile: :heart:
回复 @林晚: 回复也支持,而且是平铺的。
登录与游客双模式
user传null即进入游客模式:表单会多出昵称 / 邮箱字段, 且所有评论都不显示「删除」。评论
1游客模式下表单会多出昵称 / 邮箱字段。
是否显示「删除」由
author.isSelf决定,不靠猜组件不猜测「这条是不是我发的」。它只看
author.isSelf。按昵称比对是不行的——游客模式下所有评论都来自同一浏览器, 昵称重名极常见;靠
localStorage里存的 id 判断也不可靠(用户清缓存就丢)。「我是谁」这个信息只有宿主知道,所以由宿主在数据里标明。
只能回复一层
replies里的项不再渲染自己的回复——所有回复平铺在同一层, 通过replyTo标明回复对象。评论
3该评论已被删除
评论组件支持表情,比如 :smile: :heart:
回复 @林晚: 回复也支持,而且是平铺的。
回复项里的「回复 @某某」由 replyTo 生成
无限嵌套在窄屏上不可用
每多一层缩进,正文宽度就少一截。手机屏幕上三层以后正文会被挤成一条竖线。
实际产品里也很少见到真正的无限嵌套——论坛常见做法就是诚实地平铺一层。 需要表达「回复了谁」时用
replyTo,比缩进更清楚也更省空间。软删除
deleted: true的评论显示占位而非直接移除。评论
0还没有评论,来说两句吧
为什么要占位:直接删掉的话,它下面的回复会失去上下文—— 「你说得对」变成了一句没头没尾的话。占位保留了对话的完整性。
分页
pagination有三种模式,对应两种数据策略:评论
3该评论已被删除
评论组件支持表情,比如 :smile: :heart:
回复 @林晚: 回复也支持,而且是平铺的。
nonepagermore一句话:
pager是前端分页,more是服务端分页。 评论总数通常远超一页, 因此生产环境更常用more。autoLoadMore仍然保留按钮autoLoadMore开启后滚动到底自动加载,但按钮不隐藏。原因:容器被完全展开、或浏览器节流了滚动事件时, 自动加载可能不触发。此时按钮是用户唯一的入口——有总比没有好。
排序
showSort显示排序切换(最新 / 最早 / 最热)。评论
3评论组件支持表情,比如 :smile: :heart:
回复 @林晚: 回复也支持,而且是平铺的。
该评论已被删除
自定义槽位
组件提供十余个具名插槽(
avatar/level/region/os/browser/meta/actions/footer/editor),所有槽位收到的数据形状一致:commentauthorauthorMeta/commentMetalevel/region/os/browserlevel/region/os/browser的类型是unknown—— 它们取自meta这个自由容器,组件不解释其内容,宿主按自己的约定断言即可。Props
commentsSrCommentItem[][]userSrCommentAuthor | nullnullnull为游客模式allowGuestbooleantruerequireGuestNamebooleantruerequireGuestEmailbooleanfalsemaxLengthnumber500sort'latest' | 'oldest' | 'hottest''latest'showSortbooleantruepagination'none' | 'pager' | 'more''none'pageSizenumber10hasMorebooleantruemore模式)loadingMorebooleanfalseautoLoadMorebooleanfalseloadingbooleanfalsesubmittingbooleanfalselikeable/replyable/deletablebooleantrueemojiablebooleantrueshowEditorbooleantrue事件
submitSrCommentSubmitPayloadlike(comment)reply(comment)delete(comment)load-moresort-changesort关于安全
前端校验只是「防误填」
邮箱格式、网址协议、昵称长度与敏感词,都必须在服务端再查一遍。
尤其是
author.website:若宿主把它渲染成链接,请务必在服务端校验协议, 只允许http(s)。否则javascript:开头的「网址」就是一个可点击的 XSS 载体。组件不做白名单校验——「这个站点该不该放行外链」是业务判断,不是组件能决定的。
无障碍
<ul>/<li>,读屏软件能播报条数与位置aria-pressed,表达「已赞 / 未赞」的切换状态aria-live区域播报成功, 而不是让用户自己去猜有没有发出去