基于MERN栈实现可动态生成的侧边栏及点击跳转组件需求
MERN栈实现动态可配置侧边栏(仿W3School结构)
一、后端实现(Node.js + Express + MongoDB)
1. 定义数据模型
创建两个关联的Mongoose模型,分别存储侧边栏菜单和对应内容:
// models/SidebarItem.js const mongoose = require('mongoose'); const sidebarItemSchema = new mongoose.Schema({ title: { type: String, required: true }, slug: { type: String, required: true, unique: true }, // 用作路由路径 order: { type: Number, default: 0 }, // 控制侧边栏排序 contentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Content', required: true } }); module.exports = mongoose.model('SidebarItem', sidebarItemSchema); // models/Content.js const mongoose = require('mongoose'); const contentSchema = new mongoose.Schema({ htmlContent: { type: String, required: true }, // 存储富文本/HTML内容 createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('Content', contentSchema);
2. 编写API接口
实现侧边栏项和内容的CRUD接口,供前端调用:
// routes/sidebar.js const express = require('express'); const router = express.Router(); const SidebarItem = require('../models/SidebarItem'); const Content = require('../models/Content'); // 获取所有侧边栏项(关联内容) router.get('/', async (req, res) => { try { const items = await SidebarItem.find().sort({ order: 1 }).populate('contentId'); res.json(items); } catch (err) { res.status(500).json({ message: err.message }); } }); // 创建侧边栏项及对应内容 router.post('/', async (req, res) => { const { title, slug, order, htmlContent } = req.body; try { const content = new Content({ htmlContent }); await content.save(); const sidebarItem = new SidebarItem({ title, slug, order, contentId: content._id }); const savedItem = await sidebarItem.save(); res.status(201).json(savedItem); } catch (err) { res.status(400).json({ message: err.message }); } }); // 更新侧边栏项及内容 router.put('/:id', async (req, res) => { const { title, slug, order, htmlContent } = req.body; try { const sidebarItem = await SidebarItem.findById(req.params.id); if (!sidebarItem) return res.status(404).json({ message: '侧边栏项不存在' }); sidebarItem.title = title || sidebarItem.title; sidebarItem.slug = slug || sidebarItem.slug; sidebarItem.order = order || sidebarItem.order; await sidebarItem.save(); await Content.findByIdAndUpdate(sidebarItem.contentId, { htmlContent }); res.json(sidebarItem); } catch (err) { res.status(400).json({ message: err.message }); } }); // 删除侧边栏项及内容 router.delete('/:id', async (req, res) => { try { const sidebarItem = await SidebarItem.findById(req.params.id); if (!sidebarItem) return res.status(404).json({ message: '侧边栏项不存在' }); await Content.findByIdAndDelete(sidebarItem.contentId); await SidebarItem.findByIdAndDelete(req.params.id); res.json({ message: '删除成功' }); } catch (err) { res.status(500).json({ message: err.message }); } }); module.exports = router;
在主app.js中注册路由:
const sidebarRoutes = require('./routes/sidebar'); app.use('/api/sidebar', sidebarRoutes);
二、前端实现(React + React Router Dom)
1. 动态路由配置
从后端获取数据后动态生成路由:
// App.js import { useState, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import Sidebar from './components/Sidebar'; import ContentPage from './components/ContentPage'; import AdminPanel from './components/AdminPanel'; function App() { const [sidebarItems, setSidebarItems] = useState([]); useEffect(() => { fetch('/api/sidebar') .then(res => res.json()) .then(data => setSidebarItems(data)) .catch(err => console.error('获取侧边栏数据失败:', err)); }, []); return ( <Router> <div style={{ display: 'flex' }}> <Sidebar items={sidebarItems} /> <div style={{ flex: 1, padding: '20px' }}> <Routes> <Route path="/" element={sidebarItems.length > 0 ? <Navigate to={`/${sidebarItems[0].slug}`} /> : <div>暂无内容</div>} /> {sidebarItems.map(item => ( <Route key={item._id} path={`/${item.slug}`} element={<ContentPage content={item.contentId.htmlContent} />} /> ))} <Route path="/admin" element={<AdminPanel />} /> </Routes> </div> </div> </Router> ); } export default App;
2. 侧边栏组件
渲染动态菜单并处理跳转:
// components/Sidebar.js import { Link } from 'react-router-dom'; function Sidebar({ items }) { return ( <div style={{ width: '250px', backgroundColor: '#f1f1f1', padding: '20px' }}> <h2>导航菜单</h2> <ul style={{ listStyle: 'none', padding: 0 }}> {items.map(item => ( <li key={item._id} style={{ margin: '10px 0' }}> <Link to={`/${item.slug}`} style={{ textDecoration: 'none', color: '#333' }}> {item.title} </Link> </li> ))} <li style={{ margin: '10px 0' }}> <Link to="/admin" style={{ textDecoration: 'none', color: '#333' }}> 后台管理 </Link> </li> </ul> </div> ); } export default Sidebar;
3. 内容渲染组件
渲染对应侧边栏项的HTML内容:
// components/ContentPage.js function ContentPage({ content }) { return ( <div dangerouslySetInnerHTML={{ __html: content }} /> ); } export default ContentPage;
4. 后台管理面板(简化版)
实现创建侧边栏项的功能:
// components/AdminPanel.js import { useState } from 'react'; function AdminPanel() { const [title, setTitle] = useState(''); const [slug, setSlug] = useState(''); const [order, setOrder] = useState(0); const [htmlContent, setHtmlContent] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); try { await fetch('/api/sidebar', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, slug, order, htmlContent }) }); alert('创建成功'); setTitle(''); setSlug(''); setOrder(0); setHtmlContent(''); } catch (err) { console.error('创建失败:', err); alert('创建失败'); } }; return ( <div> <h2>后台管理 - 创建侧边栏项</h2> <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}> <input type="text" placeholder="标题" value={title} onChange={(e) => setTitle(e.target.value)} required /> <input type="text" placeholder="路由别名(slug)" value={slug} onChange={(e) => setSlug(e.target.value)} required /> <input type="number" placeholder="排序序号" value={order} onChange={(e) => setOrder(Number(e.target.value))} /> <textarea placeholder="内容(支持HTML)" value={htmlContent} onChange={(e) => setHtmlContent(e.target.value)} rows={10} required /> <button type="submit">创建</button> </form> </div> ); } export default AdminPanel;
三、关键注意事项
- 路由唯一性:后端需保证
slug字段唯一,避免路由冲突。 - 内容安全:若允许用户输入HTML,需用
dompurify等库做XSS防护后再存储。 - 状态同步:后台修改数据后,前端需重新拉取数据(可在AdminPanel操作成功后触发App组件的重新请求)。
- 排序控制:通过
order字段调整侧边栏菜单的显示顺序。
内容的提问来源于stack exchange,提问作者kartik Krishna sharma
相关产品推荐
相关产品推荐

