You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 22:00:57