🎯 课程目标
完成本课程后,你将能够:
- 设计和实现完整的博客应用功能
- 掌握文章管理(增删改查)功能开发
- 实现分类和标签系统
- 添加评论互动功能
- 使用localStorage实现数据持久化
📖 博客应用概述
博客是互联网时代最经典的应用类型之一。一个完整的博客系统通常包含文章管理、分类标签、评论互动等功能。本节课我们将综合运用前面学到的知识,从零构建一个功能完善的个人博客应用。
✨ 博客核心功能
- 文章管理:创建、编辑、查看、删除文章
- 分类系统:按类别组织文章
- 标签系统:使用标签实现文章关联
- 评论系统:读者可以发表评论
- 数据持久化:使用localStorage保存数据
🏗️ 项目结构设计
首先设计博客的数据结构和整体架构。
// 文章数据结构
const blogData = {
posts: [
{
id: 1,
title: '我的第一篇博客',
content: '这是文章内容...',
category: '技术',
tags: ['JavaScript', 'Web开发'],
author: '博主',
createdAt: '2024-01-15T10:30:00',
views: 123,
comments: []
}
],
categories: ['技术', '生活', '随笔', '学习'],
settings: {
siteName: '我的博客',
siteDescription: '分享知识,记录生活'
}
};
// 从localStorage加载数据
function loadBlogData() {
const saved = localStorage.getItem('blogData');
if (saved) {
return JSON.parse(saved);
}
return blogData;
}
// 保存数据到localStorage
function saveBlogData(data) {
localStorage.setItem('blogData', JSON.stringify(data));
}
📝 文章列表页面
实现博客首页,展示文章列表。
// 渲染文章列表
function renderPostList(posts) {
const container = document.getElementById('postList');
if (!container) return;
if (posts.length === 0) {
container.innerHTML = '
暂无文章,去写一篇吧!
';
return;
}
container.innerHTML = posts.map(post => `
${post.category}
${formatDate(post.createdAt)}
${escapeHtml(post.content.substring(0, 150))}...
`).join('');
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}
// HTML转义(防止XSS)
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
✏️ 文章编辑器
创建一个功能完善的文章编辑器。
// 文章编辑器HTML结构
const editorHTML = `
|
|
|
`;
// 工具栏点击事件
document.querySelector('.editor-toolbar').addEventListener('click', (e) => {
const button = e.target.closest('button');
if (!button) return;
const command = button.dataset.command;
execCommand(command);
});
// 执行编辑器命令
function execCommand(command, value = null) {
document.execCommand(command, false, value);
document.getElementById('editor').focus();
}
// 发布文章
function publishPost() {
const title = document.getElementById('postTitle').value.trim();
const content = document.getElementById('editor').innerHTML;
const category = document.getElementById('postCategory').value;
const tags = document.getElementById('postTags').value
.split(',')
.map(t => t.trim())
.filter(t => t);
if (!title) {
alert('请输入文章标题');
return;
}
if (!content || content === '
') {
alert('请输入文章内容');
return;
}
const blogData = loadBlogData();
const newPost = {
id: Date.now(),
title,
content,
category,
tags,
author: '博主',
createdAt: new Date().toISOString(),
views: 0,
comments: []
};
blogData.posts.unshift(newPost);
saveBlogData(blogData);
alert('发布成功!');
window.location.href = 'index.html';
}
📄 文章详情页面
实现文章详情展示,包括评论功能。
// 获取URL参数
function getUrlParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// 渲染文章详情
function renderPostDetail() {
const postId = parseInt(getUrlParam('id'));
const blogData = loadBlogData();
const post = blogData.posts.find(p => p.id === postId);
if (!post) {
document.getElementById('postDetail').innerHTML = '
文章不存在
';
return;
}
// 增加阅读量
post.views++;
saveBlogData(blogData);
const container = document.getElementById('postDetail');
container.innerHTML = `
${post.content}
`;
}
// 格式化日期时间
function formatDateTime(dateString) {
const date = new Date(dateString);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}
// 渲染单条评论
function renderComment(comment) {
return `
`;
}
// 提交评论
function submitComment() {
const postId = parseInt(getUrlParam('id'));
const content = document.getElementById('commentContent').value.trim();
const author = document.getElementById('commentAuthor').value.trim() || '匿名用户';
if (!content) {
alert('请输入评论内容');
return;
}
const blogData = loadBlogData();
const post = blogData.posts.find(p => p.id === postId);
if (post) {
post.comments.push({
id: Date.now(),
author,
content,
createdAt: new Date().toISOString()
});
saveBlogData(blogData);
// 刷新页面
renderPostDetail();
document.getElementById('commentContent').value = '';
document.getElementById('commentAuthor').value = '';
}
}
🏷️ 分类和标签功能
实现分类筛选和标签云功能。
// 渲染分类列表
function renderCategories() {
const blogData = loadBlogData();
const container = document.getElementById('categoryList');
if (!container) return;
const categoryCounts = {};
blogData.posts.forEach(post => {
categoryCounts[post.category] = (categoryCounts[post.category] || 0) + 1;
});
container.innerHTML = `
全部文章
${blogData.posts.length}
${blogData.categories.map(cat => `
${cat}
${categoryCounts[cat] || 0}
`).join('')}
`;
// 绑定点击事件
container.querySelectorAll('.category-item').forEach(item => {
item.addEventListener('click', () => {
const category = item.dataset.category;
filterPostsByCategory(category);
// 更新选中状态
container.querySelectorAll('.category-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
});
});
}
// 按分类筛选文章
function filterPostsByCategory(category) {
const blogData = loadBlogData();
let posts = blogData.posts;
if (category !== 'all') {
posts = posts.filter(p => p.category === category);
}
renderPostList(posts);
}
// 渲染标签云
function renderTagCloud() {
const blogData = loadBlogData();
const container = document.getElementById('tagCloud');
if (!container) return;
const tagCounts = {};
blogData.posts.forEach(post => {
post.tags.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
});
const tags = Object.entries(tagCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 20);
container.innerHTML = tags.map(([tag, count]) => `
${tag} (${count})
`).join('');
}
🔍 搜索功能
实现文章搜索功能,支持标题和内容搜索。
// 搜索文章
function searchPosts(keyword) {
if (!keyword.trim()) {
return loadBlogData().posts;
}
const blogData = loadBlogData();
const lowerKeyword = keyword.toLowerCase();
return blogData.posts.filter(post => {
return post.title.toLowerCase().includes(lowerKeyword) ||
post.content.toLowerCase().includes(lowerKeyword) ||
post.tags.some(tag => tag.toLowerCase().includes(lowerKeyword));
});
}
// 实时搜索(防抖)
let searchTimeout;
function handleSearch(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const keyword = e.target.value;
const results = searchPosts(keyword);
renderPostList(results);
// 显示搜索结果提示
const resultInfo = document.getElementById('searchResultInfo');
if (resultInfo) {
resultInfo.textContent = keyword ? `找到 ${results.length} 篇相关文章` : '';
}
}, 300);
}
🗑️ 文章管理
实现文章编辑和删除功能。
// 删除文章
function deletePost(postId) {
if (!confirm('确定要删除这篇文章吗?此操作不可恢复。')) {
return;
}
const blogData = loadBlogData();
const index = blogData.posts.findIndex(p => p.id === postId);
if (index !== -1) {
blogData.posts.splice(index, 1);
saveBlogData(blogData);
alert('删除成功');
window.location.href = 'index.html';
}
}
// 跳转到编辑页面
function editPost(postId) {
window.location.href = `editor.html?id=${postId}`;
}
// 加载编辑已有文章
function loadPostForEdit() {
const postId = parseInt(getUrlParam('id'));
if (!postId) return;
const blogData = loadBlogData();
const post = blogData.posts.find(p => p.id === postId);
if (post) {
document.getElementById('postTitle').value = post.title;
document.getElementById('editor').innerHTML = post.content;
document.getElementById('postCategory').value = post.category;
document.getElementById('postTags').value = post.tags.join(', ');
// 修改发布按钮行为(更新而非新建)
document.getElementById('publish').textContent = '更新文章';
document.getElementById('publish').onclick = () => updatePost(postId);
}
}
// 更新文章
function updatePost(postId) {
const blogData = loadBlogData();
const post = blogData.posts.find(p => p.id === postId);
if (!post) {
alert('文章不存在');
return;
}
post.title = document.getElementById('postTitle').value.trim();
post.content = document.getElementById('editor').innerHTML;
post.category = document.getElementById('postCategory').value;
post.tags = document.getElementById('postTags').value
.split(',')
.map(t => t.trim())
.filter(t => t);
saveBlogData(blogData);
alert('更新成功!');
window.location.href = `post.html?id=${postId}`;
}
⚠️ 常见问题
| 问题 | 原因分析 | 解决方案 |
| localStorage存储限制 | 约5MB限制 | 压缩数据或使用IndexedDB |
| 数据跨设备同步 | localStorage仅限本设备 | 需要后端服务器支持 |
| HTML内容显示异常 | 富文本内容未正确渲染 | 使用innerHTML而非textContent |
| 评论提交后不显示 | 页面未刷新 | 调用renderPostDetail()刷新 |
| 搜索结果不准确 | 搜索逻辑过于简单 | 实现全文搜索或使用搜索库 |
✅ 课后练习
练习要求:请独立完成以下练习任务:
- 练习 1:完成博客应用的基础功能,实现文章的增删改查
- 练习 2:添加分类页面和标签页面,支持按分类和标签筛选
- 选做练习:实现文章草稿功能,支持保存和恢复草稿
评论 (${post.comments.length})