用户交互 - 实际例子:评论系统

通过一个完整的"评论系统"例子,展示常见的用户交互功能,包括输入、验证、点赞、回复、删除等操作。

1. HTML结构

<div class="comment-system"> <!-- 发表评论 --> <div class="comment-form"> <textarea id="commentInput" placeholder="说点什么..." maxlength="500" @input="onInput" @focus="onFocus" ></textarea> <div class="form-footer"> <span class="char-count">{{ charCount }}/500</span> <button @click="submitComment" :disabled="!canSubmit">发表评论</button> </div> </div> <!-- 评论列表 --> <div class="comment-list"> <div class="comment-item" v-for="comment in comments" :key="comment.id"> <div class="avatar">{{ comment.author[0] }}</div> <div class="content"> <div class="header"> <span class="author">{{ comment.author }}</span> <span class="time">{{ comment.time }}</span> </div> <p>{{ comment.text }}</p> <div class="actions"> <button @click="likeComment(comment.id)" :class="{ liked: comment.liked }"> {{ comment.liked ? '❤️' : '🤍' }} {{ comment.likes }} </button> <button @click="showReply(comment.id)">回复</button> <button v-if="comment.canDelete" @click="deleteComment(comment.id)">删除</button> </div> <!-- 回复列表 --> <div class="replies" v-if="comment.replies && comment.replies.length"> <div class="reply-item" v-for="reply in comment.replies" :key="reply.id"> <span class="author">{{ reply.author }}</span> <span>: {{ reply.text }}</span> </div> </div> <!-- 回复输入框 --> <div class="reply-form" v-if="replyingTo === comment.id"> <input v-model="replyText" placeholder="写下你的回复..." @keyup.enter="submitReply(comment.id)" /> <button @click="submitReply(comment.id)">发送</button> <button @click="cancelReply">取消</button> </div> </div> </div> </div> </div>

2. JavaScript交互逻辑

const app = new Vue({ el: '.comment-system', data: { commentText: '', comments: [], replyingTo: null, replyText: '', isFocused: false }, computed: { charCount() { return this.commentText.length; }, canSubmit() { return this.commentText.trim().length > 0; } }, methods: { // 输入事件 - 实时更新字符计数 onInput(e) { this.commentText = e.target.value; }, // 获得焦点 - 显示更多操作 onFocus() { this.isFocused = true; }, // 发表评论 submitComment() { if (!this.canSubmit) return; const newComment = { id: Date.now(), author: '当前用户', text: this.commentText.trim(), time: '刚刚', likes: 0, liked: false, canDelete: true, replies: [] }; this.comments.unshift(newComment); this.commentText = ''; this.isFocused = false; }, // 点赞/取消点赞 likeComment(id) { const comment = this.comments.find(c => c.id === id); if (comment) { comment.liked = !comment.liked; comment.likes += comment.liked ? 1 : -1; } }, // 显示回复输入框 showReply(id) { this.replyingTo = id; this.replyText = ''; }, // 取消回复 cancelReply() { this.replyingTo = null; this.replyText = ''; }, // 提交回复 submitReply(commentId) { if (!this.replyText.trim()) return; const comment = this.comments.find(c => c.id === commentId); if (comment) { if (!comment.replies) comment.replies = []; comment.replies.push({ id: Date.now(), author: '当前用户', text: this.replyText.trim() }); } this.cancelReply(); }, // 删除评论 - 带确认 deleteComment(id) { confirm('确定删除这条评论吗?') && ( this.comments = this.comments.filter(c => c.id !== id) ); } } });

3. 拖拽排序评论

// 拖拽排序功能 let draggedItem = null; onMounted(() => { const list = document.querySelector('.comment-list'); list.addEventListener('dragstart', handleDragStart); list.addEventListener('dragover', handleDragOver); list.addEventListener('drop', handleDrop); list.addEventListener('dragend', handleDragEnd); }); function handleDragStart(e) { draggedItem = e.target; e.target.classList.add('dragging'); } function handleDragOver(e) { e.preventDefault(); // 允许drop const afterElement = getDragAfterElement(e.target); if (afterElement) { draggedItem.parentNode.insertBefore(draggedItem, afterElement); } } function handleDrop(e) { // 更新数据库中的排序... } function handleDragEnd(e) { e.target.classList.remove('dragging'); } function getDragAfterElement(container, y) { const draggableElements = [...container.querySelectorAll('.comment-item:not(.dragging)')]; return draggableElements.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = y - box.top - box.height / 2; if (offset < 0 && offset > closest.offset) { return { offset: offset, element: child }; } else { return closest; } }, { offset: Number.NEGATIVE_INFINITY }).element; }

4. 效果演示

10/500
张三 2小时前

学到了很多有用的知识!

李四 昨天

感谢分享,期待更多内容!

王五: 同意楼上观点!

交互要点总结

输入验证

实时字符计数,提交前检查内容是否为空

状态反馈

点赞状态切换,按钮禁用/启用状态

嵌套交互

回复功能展开/收起,多层嵌套显示

确认机制

删除操作前需要用户确认

← Interaction Touch Ts Basics →