index.html:网页的结构,包含留言表单和留言列表。style.css:网页的样式,让页面看起来美观、现代化。script.js:实现留言的添加、删除、本地存储和动态渲染等交互功能。
功能特点
- 响应式设计:适配桌面和移动设备。
- 实时添加:用户提交留言后,无需刷新页面即可看到新留言。
- 删除功能:每条留言旁边都有删除按钮,可以方便地移除。
- 本地存储:使用
localStorage保存留言,即使用户刷新页面或关闭浏览器,留言数据依然存在。 - 现代化UI:使用了卡片式布局、平滑的过渡动画和清晰的视觉层次。
- 输入验证:确保用户名和留言内容不为空。
第一步:创建 index.html 文件
这是网页的骨架,定义了页面上的所有元素。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">用户留言板</title>
<link rel="stylesheet" href="style.css">
<!-- 引入一个简单的图标库 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-comments"></i> 用户留言板</h1>
<p>欢迎留下您的宝贵意见!</p>
</header>
<main>
<!-- 留言表单 -->
<section class="message-form">
<h2>发表留言</h2>
<form id="leaveMessageForm">
<div class="form-group">
<label for="username">您的名字:</label>
<input type="text" id="username" name="username" placeholder="请输入您的昵称" required>
</div>
<div class="form-group">
<label for="message">留言内容:</label>
<textarea id="message" name="message" rows="4" placeholder="请输入您的留言内容..." required></textarea>
</div>
<button type="submit" class="submit-btn">
<i class="fas fa-paper-plane"></i> 发布留言
</button>
</form>
</section>
<!-- 留言列表 -->
<section class="message-list">
<h2>留言列表</h2>
<div id="messageContainer">
<!-- 留言将通过 JavaScript 动态添加到这里 -->
<p class="no-messages">暂无留言,快来抢沙发吧!</p>
</div>
</section>
</main>
</div>
<script src="script.js"></script>
</body>
</html>
第二步:创建 style.css 文件
这是网页的“衣服”,负责美化页面,设置颜色、字体、布局等。
/* 全局样式和变量 */
:root {
--primary-color: #3498db;
--secondary-color: #2980b9;
--danger-color: #e74c3c;
--light-bg: #ecf0f1;
--dark-text: #2c3e50;
--border-color: #bdc3c7;
--shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
--shadow-hover: 0 6px 15px rgba(0, 0, 0, 0.15);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--dark-text);
background-color: var(--light-bg);
padding: 20px;
}
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: var(--shadow);
}
header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid var(--light-bg);
padding-bottom: 20px;
}
header h1 {
color: var(--primary-color);
margin-bottom: 10px;
}
header p {
color: #7f8c8d;
}
/* 表单样式 */
.message-form {
margin-bottom: 40px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 5px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary-color);
}
.submit-btn {
display: inline-block;
background-color: var(--primary-color);
color: #fff;
padding: 12px 20px;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.submit-btn:hover {
background-color: var(--secondary-color);
transform: translateY(-2px);
}
/* 留言列表样式 */
.message-list h2 {
margin-bottom: 20px;
color: var(--dark-text);
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.message-item {
background-color: #fff;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: var(--shadow);
animation: fadeIn 0.5s ease-in;
transition: transform 0.2s, box-shadow 0.2s;
}
.message-item:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-hover);
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.message-author {
font-weight: bold;
color: var(--primary-color);
font-size: 1.1em;
}
.message-date {
color: #95a5a6;
font-size: 0.9em;
}
.message-content {
color: #34495e;
line-height: 1.6;
}
.delete-btn {
background: none;
border: none;
color: var(--danger-color);
cursor: pointer;
font-size: 1.2em;
transition: color 0.3s, transform 0.2s;
}
.delete-btn:hover {
color: #c0392b;
transform: scale(1.1);
}
.no-messages {
text-align: center;
color: #95a5a6;
font-style: italic;
padding: 30px;
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式设计 */
@media (max-width: 600px) {
.container {
padding: 15px;
}
header h1 {
font-size: 1.8em;
}
.message-header {
flex-direction: column;
align-items: flex-start;
}
.message-date {
margin-top: 5px;
font-size: 0.8em;
}
}
第三步:创建 script.js 文件
这是网页的“大脑”,负责处理所有的逻辑和交互。
document.addEventListener('DOMContentLoaded', () => {
// 获取DOM元素
const leaveMessageForm = document.getElementById('leaveMessageForm');
const messageContainer = document.getElementById('messageContainer');
const usernameInput = document.getElementById('username');
const messageInput = document.getElementById('message');
// 从 localStorage 加载留言
let messages = JSON.parse(localStorage.getItem('messages')) || [];
// 页面加载时,渲染所有留言
renderMessages();
// 监听表单提交事件
leaveMessageForm.addEventListener('submit', (event) => {
// 阻止表单默认的提交行为(页面刷新)
event.preventDefault();
// 获取输入值并去除首尾空格
const username = usernameInput.value.trim();
const message = messageInput.value.trim();
// 简单的输入验证
if (username === '' || message === '') {
// 使用 alert 提示用户,或者可以设计一个更友好的提示框
alert('用户名和留言内容不能为空!');
return;
}
// 创建一个新的留言对象
const newMessage = {
id: Date.now(), // 使用时间戳作为唯一ID
author: username,
content: message,
timestamp: new Date().toLocaleString('zh-CN') // 获取当前时间并格式化
};
// 将新留言添加到数组开头(最新的在最上面)
messages.unshift(newMessage);
// 保存更新后的留言数组到 localStorage
localStorage.setItem('messages', JSON.stringify(messages));
// 重新渲染留言列表
renderMessages();
// 清空表单输入
leaveMessageForm.reset();
usernameInput.focus(); // 将焦点重新放回用户名输入框
});
/**
* 渲染留言列表的函数
*/
function renderMessages() {
// 如果留言数组为空,显示提示信息
if (messages.length === 0) {
messageContainer.innerHTML = '<p class="no-messages">暂无留言,快来抢沙发吧!</p>';
return;
}
// 清空容器,准备重新渲染
messageContainer.innerHTML = '';
// 遍历留言数组,为每条留言创建一个HTML元素并添加到容器中
messages.forEach(msg => {
const messageElement = createMessageElement(msg);
messageContainer.appendChild(messageElement);
});
}
/**
* 创建单个留言元素的函数
* @param {object} message - 留言对象
* @returns {HTMLElement} - 返回创建好的留言DOM元素
*/
function createMessageElement(message) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message-item');
messageDiv.dataset.id = message.id; // 添加data-id属性,方便后续删除
messageDiv.innerHTML = `
<div class="message-header">
<span class="message-author"><i class="fas fa-user-circle"></i> ${message.author}</span>
<span class="message-date">${message.timestamp}</span>
</div>
<p class="message-content">${escapeHtml(message.content)}</p>
<button class="delete-btn" onclick="deleteMessage(${message.id})">
<i class="fas fa-trash-alt"></i> 删除
</button>
`;
return messageDiv;
}
/**
* 删除留言的函数
* @param {number} id - 要删除的留言ID
*/
function deleteMessage(id) {
// 从messages数组中过滤掉要删除的留言
messages = messages.filter(msg => msg.id !== id);
// 更新localStorage
localStorage.setItem('messages', JSON.stringify(messages));
// 重新渲染列表
renderMessages();
}
/**
* 防止XSS攻击的简单HTML转义函数
* @param {string} text - 需要转义的文本
* @returns {string} - 转义后的安全文本
*/
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
});
如何使用
- 创建文件:在您的项目文件夹中,创建三个文件:
index.html、style.css和script.js。 - 复制代码:将上面提供的代码分别复制到对应的文件中。
- 打开网页:使用浏览器打开
index.html文件。
您就拥有了一个功能齐全、美观实用的用户留言板了!您可以直接在浏览器中测试添加、删除和刷新页面的功能。
