Skip to content
# t-comment

Comment 评论列表组件 — 基于 list-view 实现的层级评论展示组件,支持递归回复、点赞、用户点击等交互。

t-comment 组件介绍 ​

t-comment 是一个评论列表组件,内部使用 list-view 渲染,配合 t-comment-item 支持递归嵌套的子评论展示。适用于帖子详情、社交动态、任务评价等需要展示层级评论的场景。

t-comment 基础用法 ​

vue
<template>
	<view class="p-15">
		<t-card class="mb-15"
				title="Comment 评论组件"
				sub-title="支持 list-view 顶层滚动、多级回复递归、点赞和回复动作演示。"></t-card>

		<view class="fv r-16 bg-#fff p-12 mb-15">
			<text class="tfs sfwb c-#111827">当前交互</text>
			<text class="tfs c-#475569 mt-8 slhm-1.6">{{ actionText }}</text>
		</view>

		<t-comment :list="commentList"
				   height="620px"
				   :indent="26"
				   :max-depth="5"
				   @reply="handleReply"
				   @like="handleLike"
				   @userclick="handleUserClick"></t-comment>
	</view>
</template>

<script setup lang="uts">
	const actionText = ref<string>('点击回复、点赞或头像昵称查看交互结果。')

	function createChildrenList() : UTSJSONObject[] {
		return [] as UTSJSONObject[]
	}

	function createImageList(...items : string[]) : string[] {
		return items
	}

	function createDemoReplies() : UTSJSONObject[] {
		return [
			{
				id: 'c1-1-1',
				avatar: '/static/image/avatar/2.jpg',
				name: '回眸倾城',
				replyToName: '七七是比熊',
				content: '多少钱一个',
				time: '3天前',
				region: '新疆',
				likeCount: 1,
				liked: false,
				children: createChildrenList()
			},
			{
				id: 'c1-1-2',
				avatar: '/static/image/avatar/2.jpg',
				name: '回眸倾城',
				replyToName: '七七是比熊',
				content: '我在乌鲁木齐。',
				time: '3天前',
				region: '新疆',
				likeCount: 1,
				liked: true,
				children: createChildrenList()
			},
			{
				id: 'c1-1-3',
				avatar: '/static/image/avatar/3.jpg',
				name: '晚风奶芙',
				replyToName: '回眸倾城',
				content: '我也想问下价格。',
				time: '2天前',
				region: '苏州',
				likeCount: 3,
				liked: false,
				children: createChildrenList()
			},
			{
				id: 'c1-1-4',
				avatar: '/static/image/avatar/1.jpg',
				name: '青提苏打',
				replyToName: '七七是比熊',
				content: '等开售的时候踢我一下。',
				time: '2天前',
				region: '南京',
				likeCount: 2,
				liked: false,
				children: createChildrenList()
			},
			{
				id: 'c1-1-5',
				avatar: '/static/image/avatar/2.jpg',
				name: '多云转晴',
				replyToName: '七七是比熊',
				content: '视频里真的太可爱了。',
				time: '1天前',
				region: '武汉',
				likeCount: 5,
				liked: true,
				images: createImageList('/static/image/D1.jpg', '/static/image/D2.jpg'),
				children: createChildrenList()
			},
			{
				id: 'c1-1-6',
				avatar: '/static/image/avatar/3.jpg',
				name: '星河入梦',
				replyToName: '回眸倾城',
				content: '我也在蹲完整更新。',
				time: '1天前',
				region: '成都',
				likeCount: 1,
				liked: false,
				children: createChildrenList()
			},
			{
				id: 'c1-1-7',
				avatar: '/static/image/avatar/1.jpg',
				name: '奶油可可',
				replyToName: '七七是比熊',
				content: '有没有喂养清单可以参考?',
				time: '1天前',
				region: '杭州',
				likeCount: 4,
				liked: false,
				children: createChildrenList()
			},
			{
				id: 'c1-1-8',
				avatar: '/static/image/avatar/2.jpg',
				name: '晨雾拿铁',
				replyToName: '七七是比熊',
				content: '我已经收藏这个帖子了,等后面继续看。',
				time: '20小时前',
				region: '宁波',
				likeCount: 2,
				liked: false,
				images: createImageList('/static/image/D2.jpg'),
				children: createChildrenList()
			},
			{
				id: 'c1-1-9',
				avatar: '/static/image/avatar/3.jpg',
				name: '海盐芝士',
				replyToName: '回眸倾城',
				content: '这个楼层已经快变成交流区了,哈哈。',
				time: '18小时前',
				region: '青岛',
				likeCount: 6,
				liked: true,
				children: createChildrenList()
			},
			{
				id: 'c1-1-10',
				avatar: '/static/image/avatar/1.jpg',
				name: '晚星汽水',
				replyToName: '七七是比熊',
				content: '如果后面有开团通知,也想顺带知道一下。',
				time: '16小时前',
				region: '福州',
				likeCount: 1,
				liked: false,
				children: createChildrenList()
			},
			{
				id: 'c1-1-11',
				avatar: '/static/image/avatar/2.jpg',
				name: '山野青柠',
				replyToName: '七七是比熊',
				content: '我主要想看后续喂养和日常护理部分。',
				time: '12小时前',
				region: '重庆',
				likeCount: 3,
				liked: false,
				children: createChildrenList()
			},
			{
				id: 'c1-1-12',
				avatar: '/static/image/avatar/3.jpg',
				name: '焦糖布丁',
				replyToName: '回眸倾城',
				content: '同问价格,也想知道现在有没有排队名单。',
				time: '8小时前',
				region: '合肥',
				likeCount: 2,
				liked: true,
				children: createChildrenList()
			}
		] as UTSJSONObject[]
	}

	const commentList = ref<UTSJSONObject[]>([
		{
			id: 'c1',
			avatar: '/static/image/avatar/1.jpg',
			name: '回眸倾城',
			content: '可不可以到时候卖一只给我?',
			time: '3天前',
			region: '新疆',
			likeCount: 7,
			liked: false,
			images: createImageList('/static/image/D1.jpg', '/static/image/D2.jpg', '/static/image/code.png'),
			children: [
				{
					id: 'c1-1',
					name: '七七是比熊',
					content: '可以,最近会整理一组新照片发出来。',
					time: '3天前',
					region: '山东',
					likeCount: 2,
					liked: false,
					isAuthor: true,
					images: createImageList('/static/image/D2.jpg'),
					children: createDemoReplies()
				}
			]
		},
		{
			id: 'c2',
			avatar: '/static/image/avatar/3.jpg',
			name: '一只椰子糖',
			content: '这个配色真的好温柔,已经蹲更新了。',
			time: '2天前',
			region: '杭州',
			likeCount: 12,
			liked: true,
			images: createImageList('/static/image/D2.jpg', '/static/image/D1.jpg'),
			children: [
				{
					id: 'c2-1',
					name: '七七是比熊',
					replyToName: '一只椰子糖',
					content: '下周会发完整视频,记得来看看。',
					time: '2天前',
					region: '山东',
					likeCount: 3,
					liked: false,
					isAuthor: true,
					children: createChildrenList()
				}
			]
		}
	])

	function getItemId(item : UTSJSONObject) : string {
		const id = item.getAny('id')
		return id == null ? '' : `${id}`
	}

	function findCommentById(list : UTSJSONObject[], id : string) : UTSJSONObject | null {
		for (let i = 0; i < list.length; i++) {
			const item = list[i]
			if (getItemId(item) == id) {
				return item
			}
			const children = item.getAny('children') as UTSJSONObject[] | null
			if (children != null && children.length > 0) {
				const child = findCommentById(children, id)
				if (child != null) {
					return child
				}
			}
		}
		return null
	}

	function handleReply(detail : any) {
		const payload = detail as UTSJSONObject
		const item = payload.getAny('item') as UTSJSONObject
		const name = item.getString('name') ?? '匿名用户'
		actionText.value = `reply -> ${name}`
		uni.showToast({
			title: `回复 ${name}`,
			icon: 'none'
		})
	}

	function handleLike(detail : any) {
		const payload = detail as UTSJSONObject
		const item = payload.getAny('item') as UTSJSONObject
		const id = getItemId(item)
		if (id == '') {
			return
		}
		const target = findCommentById(commentList.value, id)
		if (target == null) {
			return
		}
		const likedRaw = target.getAny('liked')
		const liked = likedRaw == true || `${likedRaw}` == 'true'
		const likeCountRaw = target.getAny('likeCount')
		const currentCount = likeCountRaw == null ? 0 : parseInt(`${likeCountRaw}`)
		const nextCount = liked ? Math.max(0, currentCount - 1) : currentCount + 1
		target.set('liked', !liked)
		target.set('likeCount', nextCount)
		actionText.value = `${liked ? '取消点赞' : '点赞'} -> ${target.getString('name') ?? '匿名用户'}`
	}

	function handleUserClick(detail : any) {
		const payload = detail as UTSJSONObject
		const item = payload.getAny('item') as UTSJSONObject
		const name = item.getString('name') ?? '匿名用户'
		actionText.value = `userclick -> ${name}`
	}
</script>

t-comment API ​

Props ​

属性名说明类型默认值
list评论数据列表Array[]
height列表高度,list-view 需明确高度String / Number'420px'
emptyText空列表文案String'暂无评论'
indent子回复缩进宽度(px)Number28
maxDepth最大递归层级Number5
divider是否显示分隔线Booleantrue
replyText回复动作文本String'回复'
authorBadgeText作者标识文本String'作者'
replyPageSize子评论每次展开数量Number5

Events ​

事件名说明回调参数
reply点击回复时触发{ item, index, replyItem, replyIndex }
like点击点赞时触发{ item, index, replyItem, replyIndex }
userclick点击用户头像或昵称时触发{ user, item }
scrolltolower滚动到底部时触发—

Slots ​

无内置插槽,子项通过 t-comment-item 组件递归渲染。

注意事项 ​

  • height 必须显式设置,list-view 需要明确高度
  • 评论数据结构中的 children 字段用于递归渲染子评论
  • 通过 maxDepth 控制递归深度,避免无限嵌套
  • 通过 replyPageSize 控制子评论按需展开的数量
  • 作者标识通过 authorBadgeText 配置,开启时会在对应评论上显示标识