| <template> |
| <div class="reply-tree"> |
| <div |
| v-for="reply in replies" |
| :key="reply.id" |
| class="reply-item" |
| :class="{ 'nested-reply': level > 0 }" |
| :style="{ marginLeft: level * 24 + 'px' }" |
| > |
| <div class="reply-header"> |
| <div class="reply-author"> |
| <el-avatar :size="Math.max(32 - level * 2, 22)"> |
| {{ reply.user?.username?.charAt(0) || 'A' }} |
| </el-avatar> |
| <span class="author-name">{{ reply.user?.username || '匿名' }}</span> |
| </div> |
| <div class="reply-meta"> |
| <div class="reply-actions"> |
| <el-button |
| type="text" |
| :size="level > 0 ? 'small' : 'default'" |
| @click="$emit('reply', reply)" |
| > |
| 回复 |
| </el-button> |
| <template v-if="reply.isAuthor"> |
| <el-button |
| type="text" |
| :size="level > 0 ? 'small' : 'default'" |
| @click="$emit('edit', reply)" |
| > |
| 编辑 |
| </el-button> |
| <el-button |
| type="text" |
| :size="level > 0 ? 'small' : 'default'" |
| @click="$emit('delete', reply.id)" |
| > |
| 删除 |
| </el-button> |
| </template> |
| </div> |
| </div> |
| </div> |
| <div class="reply-content" v-html="reply.content"></div> |
| |
| <!-- 递归显示子回复 --> |
| <ReplyTree |
| v-if="reply.replies?.length" |
| :replies="reply.replies" |
| :level="level + 1" |
| @reply="$emit('reply', $event)" |
| @edit="$emit('edit', $event)" |
| @delete="$emit('delete', $event)" |
| /> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| |
| |
| export default { |
| name: 'ReplyTree', |
| props: { |
| replies: { |
| type: Array, |
| required: true |
| }, |
| level: { |
| type: Number, |
| default: 0 |
| } |
| }, |
| emits: ['reply', 'edit', 'delete'], |
| setup() { |
| |
| |
| return {} |
| } |
| } |
| </script> |
| |
| <style lang="scss" scoped> |
| .reply-tree { |
| .reply-item { |
| padding: 12px 0; |
| border-bottom: 1px solid #f0f0f0; |
| |
| &:last-child { |
| border-bottom: none; |
| } |
| |
| &.nested-reply { |
| border-left: 2px solid #e0e0e0; |
| padding-left: 12px; |
| margin-top: 8px; |
| |
| &:hover { |
| border-left-color: #3498db; |
| } |
| } |
| |
| .reply-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 8px; |
| |
| .reply-author { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| |
| .author-name { |
| font-weight: 500; |
| color: #2c3e50; |
| font-size: 14px; |
| } |
| } |
| |
| .reply-meta { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| color: #7f8c8d; |
| font-size: 12px; |
| |
| .reply-actions { |
| display: flex; |
| gap: 6px; |
| } |
| } |
| } |
| |
| .reply-content { |
| font-size: 14px; |
| line-height: 1.6; |
| color: #2c3e50; |
| margin-bottom: 8px; |
| } |
| } |
| } |
| </style> |