用户交互 - 实际例子:评论系统
通过一个完整的"评论系统"例子,展示常见的用户交互功能,包括输入、验证、点赞、回复、删除等操作。
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. 效果演示
交互要点总结
输入验证
实时字符计数,提交前检查内容是否为空
状态反馈
点赞状态切换,按钮禁用/启用状态
嵌套交互
回复功能展开/收起,多层嵌套显示
确认机制
删除操作前需要用户确认