鱼C论坛

 找回密码
 立即注册
楼主: kerln888

[已解决]html5 左边菜单栏保持展开或者关闭的状态

  [复制链接]
发表于 昨天 22:24 | 显示全部楼层
kerln888 发表于 2026-03-09 22:23
@FishC 鱼C,想用js写个流程审批,能给个例子吗?就是 ...


下面是一个简单的JavaScript流程审批示例,包含发起、两级审批和归档功能:


  1. // 审批状态常量
  2. const STATUS = {
  3.   DRAFT: '草稿',
  4.   PENDING_FIRST: '待一级审批',
  5.   PENDING_SECOND: '待二级审批',
  6.   APPROVED: '已批准',
  7.   REJECTED: '已驳回',
  8.   ARCHIVED: '已归档'
  9. };

  10. // 审批流程类
  11. class ApprovalFlow {
  12.   constructor(initiator) {
  13.     this.initiator = initiator;      // 发起人
  14.     this.firstApprover = null;       // 第一审批人
  15.     this.secondApprover = null;      // 第二审批人
  16.     this.currentStatus = STATUS.DRAFT;
  17.     this.history = [];               // 审批历史记录
  18.   }

  19.   // 发起流程
  20.   submit(firstApprover, secondApprover) {
  21.     if (this.currentStatus !== STATUS.DRAFT) {
  22.       console.log('流程已提交,无法重复发起');
  23.       return false;
  24.     }
  25.    
  26.     this.firstApprover = firstApprover;
  27.     this.secondApprover = secondApprover;
  28.     this.currentStatus = STATUS.PENDING_FIRST;
  29.    
  30.     this.history.push({
  31.       operator: this.initiator,
  32.       action: '提交流程',
  33.       time: new Date().toLocaleString(),
  34.       status: this.currentStatus
  35.     });
  36.    
  37.     console.log(`流程已提交,等待 ${firstApprover} 审批`);
  38.     return true;
  39.   }

  40.   // 第一级审批
  41.   firstApprove(approver, isApproved, remark = '') {
  42.     if (this.currentStatus !== STATUS.PENDING_FIRST) {
  43.       console.log('当前不是第一级审批阶段');
  44.       return false;
  45.     }
  46.    
  47.     if (approver !== this.firstApprover) {
  48.       console.log('审批人权限不足');
  49.       return false;
  50.     }
  51.    
  52.     if (isApproved) {
  53.       this.currentStatus = STATUS.PENDING_SECOND;
  54.       console.log(`第一级审批通过,等待 ${this.secondApprover} 审批`);
  55.     } else {
  56.       this.currentStatus = STATUS.REJECTED;
  57.       console.log('第一级审批驳回,流程结束');
  58.     }
  59.    
  60.     this.history.push({
  61.       operator: approver,
  62.       action: isApproved ? '一级审批通过' : '一级审批驳回',
  63.       remark: remark,
  64.       time: new Date().toLocaleString(),
  65.       status: this.currentStatus
  66.     });
  67.    
  68.     return true;
  69.   }

  70.   // 第二级审批
  71.   secondApprove(approver, isApproved, remark = '') {
  72.     if (this.currentStatus !== STATUS.PENDING_SECOND) {
  73.       console.log('当前不是第二级审批阶段');
  74.       return false;
  75.     }
  76.    
  77.     if (approver !== this.secondApprover) {
  78.       console.log('审批人权限不足');
  79.       return false;
  80.     }
  81.    
  82.     if (isApproved) {
  83.       this.currentStatus = STATUS.APPROVED;
  84.       console.log('第二级审批通过,流程完成');
  85.     } else {
  86.       this.currentStatus = STATUS.REJECTED;
  87.       console.log('第二级审批驳回,流程结束');
  88.     }
  89.    
  90.     this.history.push({
  91.       operator: approver,
  92.       action: isApproved ? '二级审批通过' : '二级审批驳回',
  93.       remark: remark,
  94.       time: new Date().toLocaleString(),
  95.       status: this.currentStatus
  96.     });
  97.    
  98.     return true;
  99.   }

  100.   // 归档
  101.   archive() {
  102.     if (this.currentStatus !== STATUS.APPROVED) {
  103.       console.log('只有已批准的流程才能归档');
  104.       return false;
  105.     }
  106.    
  107.     this.currentStatus = STATUS.ARCHIVED;
  108.    
  109.     this.history.push({
  110.       operator: '系统',
  111.       action: '流程归档',
  112.       time: new Date().toLocaleString(),
  113.       status: this.currentStatus
  114.     });
  115.    
  116.     console.log('流程已归档');
  117.     return true;
  118.   }

  119.   // 查看审批历史
  120.   showHistory() {
  121.     console.log('===== 审批历史记录 =====');
  122.     this.history.forEach(record => {
  123.       console.log(`[${record.time}] ${record.operator} - ${record.action}`);
  124.       if (record.remark) console.log(`备注:${record.remark}`);
  125.     });
  126.   }
  127. }

  128. // 使用示例
  129. const flow = new ApprovalFlow('张三');

  130. // 发起流程
  131. flow.submit('李四', '王五');

  132. // 第一级审批
  133. flow.firstApprove('李四', true, '同意,请下一级审批');

  134. // 第二级审批
  135. flow.secondApprove('王五', true, '审批通过');

  136. // 归档
  137. flow.archive();

  138. // 查看历史
  139. flow.showHistory();
复制代码


使用方法:

1. 创建流程实例:new ApprovalFlow('发起人姓名')

2. 提交流程:submit('第一审批人', '第二审批人')

3. 第一级审批:firstApprove('审批人', 是否通过, '备注')

4. 第二级审批:secondApprove('审批人', 是否通过, '备注')

5. 归档:archive()

6. 查看历史:showHistory()

这个示例包含了基本的流程控制、状态验证和审批历史记录。实际使用时,你可以根据需求扩展以下功能:
- 添加更多审批层级
- 连接数据库持久化存储
- 添加邮件或消息通知
- 实现撤回功能
- 添加附件上传功能

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 22:35 | 显示全部楼层
FishC 发表于 2026-3-9 22:24
下面是一个简单的JavaScript流程审批示例,包含发起、两级审批和归档功能:

@FishC
连接数据库持久化存储
- 添加邮件或消息通知
- 实现撤回功能
- 添加附件上传功能
这些怎么做,请给个例子
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 昨天 22:38 | 显示全部楼层
kerln888 发表于 2026-03-09 22:35
FishC 发表于 2026-3-9 22:24
下面是一个简单的JavaS ...

连接数据库持久化存储
- 添加邮件或消息通知
- 实现撤回功能
- 添加附件上传功能
这些怎么做,请给个例子


我来为你扩展之前的流程审批示例,添加这些实用功能。这是一个完整的全栈示例,使用Node.js + Express + SQLite:

  1. // 1. 安装依赖:npm install express sqlite3 multer nodemailer body-parser

  2. // ---------- server.js ----------
  3. const express = require('express');
  4. const sqlite3 = require('sqlite3').verbose();
  5. const multer = require('multer');
  6. const nodemailer = require('nodemailer');
  7. const path = require('path');
  8. const fs = require('fs');

  9. const app = express();
  10. const port = 3000;

  11. // 数据库初始化
  12. const db = new sqlite3.Database('./approval.db');

  13. db.serialize(() => {
  14.   // 流程表
  15.   db.run(`CREATE TABLE IF NOT EXISTS processes (
  16.     id INTEGER PRIMARY KEY AUTOINCREMENT,
  17.     title TEXT NOT NULL,
  18.     content TEXT,
  19.     applicant TEXT NOT NULL,
  20.     status TEXT DEFAULT 'pending',
  21.     level1_approver TEXT,
  22.     level2_approver TEXT,
  23.     level1_result TEXT,
  24.     level2_result TEXT,
  25.     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  26.     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  27.     can_withdraw BOOLEAN DEFAULT 1
  28.   )`);

  29.   // 附件表
  30.   db.run(`CREATE TABLE IF NOT EXISTS attachments (
  31.     id INTEGER PRIMARY KEY AUTOINCREMENT,
  32.     process_id INTEGER,
  33.     filename TEXT,
  34.     path TEXT,
  35.     uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  36.     FOREIGN KEY (process_id) REFERENCES processes(id)
  37.   )`);

  38.   // 消息通知表
  39.   db.run(`CREATE TABLE IF NOT EXISTS notifications (
  40.     id INTEGER PRIMARY KEY AUTOINCREMENT,
  41.     process_id INTEGER,
  42.     user TEXT,
  43.     message TEXT,
  44.     is_read BOOLEAN DEFAULT 0,
  45.     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  46.     FOREIGN KEY (process_id) REFERENCES processes(id)
  47.   )`);
  48. });

  49. // 文件上传配置
  50. const storage = multer.diskStorage({
  51.   destination: './uploads/',
  52.   filename: (req, file, cb) => {
  53.     cb(null, Date.now() + path.extname(file.originalname));
  54.   }
  55. });
  56. const upload = multer({ storage });

  57. // 邮件配置(示例使用Ethereal测试邮件服务)
  58. const transporter = nodemailer.createTransport({
  59.   host: 'smtp.ethereal.email',
  60.   port: 587,
  61.   auth: {
  62.     user: 'your-email@ethereal.email',
  63.     pass: 'your-password'
  64.   }
  65. });

  66. // 中间件
  67. app.use(express.json());
  68. app.use(express.urlencoded({ extended: true }));
  69. app.use('/uploads', express.static('uploads'));

  70. // ---------- API接口 ----------

  71. // 1. 发起申请(带附件上传)
  72. app.post('/api/process', upload.array('attachments', 5), async (req, res) => {
  73.   const { title, content, applicant, level1_approver, level2_approver } = req.body;
  74.   const files = req.files || [];

  75.   db.run(
  76.     `INSERT INTO processes (title, content, applicant, level1_approver, level2_approver)
  77.      VALUES (?, ?, ?, ?, ?)`,
  78.     [title, content, applicant, level1_approver, level2_approver],
  79.     function(err) {
  80.       if (err) {
  81.         return res.status(500).json({ error: err.message });
  82.       }

  83.       const processId = this.lastID;

  84.       // 保存附件信息
  85.       files.forEach(file => {
  86.         db.run(
  87.           `INSERT INTO attachments (process_id, filename, path) VALUES (?, ?, ?)`,
  88.           [processId, file.originalname, file.path]
  89.         );
  90.       });

  91.       // 发送邮件通知一级审批人
  92.       sendEmailNotification(
  93.         level1_approver,
  94.         '新的审批申请',
  95.         `您有一个新的审批申请需要处理:${title}`
  96.       );

  97.       // 添加消息通知
  98.       addNotification(processId, level1_approver, '您有一个新的待审批申请');

  99.       res.json({
  100.         success: true,
  101.         processId,
  102.         message: '申请提交成功'
  103.       });
  104.     }
  105.   );
  106. });

  107. // 2. 一级审批
  108. app.post('/api/process/:id/level1-approve', (req, res) => {
  109.   const { result, comment } = req.body;
  110.   const processId = req.params.id;

  111.   db.run(
  112.     `UPDATE processes SET level1_result = ?, status = ?, updated_at = CURRENT_TIMESTAMP
  113.      WHERE id = ? AND status = 'pending'`,
  114.     [result, result === 'approved' ? 'level1_approved' : 'rejected', processId],
  115.     function(err) {
  116.       if (err || this.changes === 0) {
  117.         return res.status(400).json({ error: '审批失败' });
  118.       }

  119.       // 通知申请人
  120.       db.get(`SELECT applicant FROM processes WHERE id = ?`, [processId], (err, row) => {
  121.         if (row) {
  122.           addNotification(processId, row.applicant, `一级审批结果:${result}`);
  123.         }
  124.       });

  125.       // 如果通过,通知二级审批人
  126.       if (result === 'approved') {
  127.         db.get(
  128.           `SELECT level2_approver FROM processes WHERE id = ?`,
  129.           [processId],
  130.           (err, row) => {
  131.             if (row) {
  132.               addNotification(processId, row.level2_approver, '请进行二级审批');
  133.               sendEmailNotification(
  134.                 row.level2_approver,
  135.                 '二级审批通知',
  136.                 '请处理待审批的申请'
  137.               );
  138.             }
  139.           }
  140.         );
  141.       }

  142.       res.json({ success: true, message: '审批完成' });
  143.     }
  144.   );
  145. });

  146. // 3. 撤回申请(只有pending状态且未超过5分钟可撤回)
  147. app.post('/api/process/:id/withdraw', (req, res) => {
  148.   const processId = req.params.id;

  149.   db.run(
  150.     `UPDATE processes SET status = 'withdrawn', can_withdraw = 0
  151.      WHERE id = ? AND status = 'pending'
  152.      AND datetime(created_at, '+5 minutes') > datetime('now')`,
  153.     [processId],
  154.     function(err) {
  155.       if (err || this.changes === 0) {
  156.         return res.status(400).json({
  157.           error: '撤回失败,可能已超时或状态不符'
  158.         });
  159.       }

  160.       // 通知审批人
  161.       db.get(
  162.         `SELECT level1_approver FROM processes WHERE id = ?`,
  163.         [processId],
  164.         (err, row) => {
  165.           if (row) {
  166.             addNotification(processId, row.level1_approver, '申请已撤回');
  167.           }
  168.         }
  169.       );

  170.       res.json({ success: true, message: '撤回成功' });
  171.     }
  172.   );
  173. });

  174. // 4. 获取用户通知
  175. app.get('/api/notifications/:user', (req, res) => {
  176.   const user = req.params.user;

  177.   db.all(
  178.     `SELECT n.*, p.title as process_title
  179.      FROM notifications n
  180.      LEFT JOIN processes p ON n.process_id = p.id
  181.      WHERE n.user = ? AND n.is_read = 0
  182.      ORDER BY n.created_at DESC`,
  183.     [user],
  184.     (err, rows) => {
  185.       if (err) {
  186.         return res.status(500).json({ error: err.message });
  187.       }
  188.       res.json(rows);
  189.     }
  190.   );
  191. });

  192. // 5. 获取申请详情(含附件)
  193. app.get('/api/process/:id', (req, res) => {
  194.   const processId = req.params.id;

  195.   db.get(
  196.     `SELECT p.*,
  197.      GROUP_CONCAT(a.filename) as attachments
  198.      FROM processes p
  199.      LEFT JOIN attachments a ON p.id = a.process_id
  200.      WHERE p.id = ?
  201.      GROUP BY p.id`,
  202.     [processId],
  203.     (err, process) => {
  204.       if (err) {
  205.         return res.status(500).json({ error: err.message });
  206.       }

  207.       // 获取附件详情
  208.       db.all(
  209.         `SELECT * FROM attachments WHERE process_id = ?`,
  210.         [processId],
  211.         (err, attachments) => {
  212.           process.attachments = attachments;
  213.           res.json(process);
  214.         }
  215.       );
  216.     }
  217.   );
  218. });

  219. // ---------- 辅助函数 ----------

  220. // 发送邮件通知
  221. async function sendEmailNotification(to, subject, text) {
  222.   try {
  223.     const info = await transporter.sendMail({
  224.       from: '"审批系统" <system@example.com>',
  225.       to: to,
  226.       subject: subject,
  227.       text: text
  228.     });
  229.     console.log('邮件已发送:', info.messageId);
  230.   } catch (error) {
  231.     console.error('邮件发送失败:', error);
  232.   }
  233. }

  234. // 添加消息通知
  235. function addNotification(processId, user, message) {
  236.   db.run(
  237.     `INSERT INTO notifications (process_id, user, message) VALUES (?, ?, ?)`,
  238.     [processId, user, message]
  239.   );
  240. }

  241. app.listen(port, () => {
  242.   console.log(`服务器运行在 [url=http://localhost:${port}`);]http://localhost:${port}`);[/url]
  243. });

  244. // ---------- 前端HTML示例 ----------
  245. /*
  246. <!DOCTYPE html>
  247. <html>
  248. <head>
  249.   <title>流程审批系统</title>
  250.   <style>
  251.     .container { max-width: 800px; margin: 20px auto; padding: 20px; }
  252.     .form-group { margin-bottom: 15px; }
  253.     .form-group label { display: block; margin-bottom: 5px; }
  254.     .form-group input, .form-group textarea {
  255.       width: 100%; padding: 8px;
  256.     }
  257.     .btn { padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; }
  258.     .btn:hover { background: #0056b3; }
  259.     .notification { background: #f8f9fa; padding: 10px; margin: 10px 0; border-left: 4px solid #007bff; }
  260.     .attachment-list { margin-top: 10px; }
  261.     .attachment-item {
  262.       display: inline-block;
  263.       background: #e9ecef;
  264.       padding: 5px 10px;
  265.       margin: 5px;
  266.       border-radius: 3px;
  267.     }
  268.   </style>
  269. </head>
  270. <body>
  271.   <div class="container">
  272.     <h1>发起审批申请</h1>
  273.     <form id="processForm" enctype="multipart/form-data">
  274.       <div class="form-group">
  275.         <label>标题:</label>
  276.         <input type="text" name="title" required>
  277.       </div>
  278.       <div class="form-group">
  279.         <label>内容:</label>
  280.         <textarea name="content" rows="4" required></textarea>
  281.       </div>
  282.       <div class="form-group">
  283.         <label>申请人:</label>
  284.         <input type="text" name="applicant" required>
  285.       </div>
  286.       <div class="form-group">
  287.         <label>一级审批人邮箱:</label>
  288.         <input type="email" name="level1_approver" required>
  289.       </div>
  290.       <div class="form-group">
  291.         <label>二级审批人邮箱:</label>
  292.         <input type="email" name="level2_approver" required>
  293.       </div>
  294.       <div class="form-group">
  295.         <label>附件上传(最多5个):</label>
  296.         <input type="file" name="attachments" multiple>
  297.         <div class="attachment-list" id="attachmentPreview"></div>
  298.       </div>
  299.       <button type="submit" class="btn">提交申请</button>
  300.       <button type="button" class="btn" onclick="checkNotifications()">检查通知</button>
  301.     </form>

  302.     <div id="notifications"></div>
  303.     <div id="processList"></div>
  304.   </div>

  305.   <script>
  306.     // 提交申请(带附件)
  307.     document.getElementById('processForm').addEventListener('submit', async (e) => {
  308.       e.preventDefault();
  309.       const formData = new FormData(e.target);
  310.       
  311.       const response = await fetch('/api/process', {
  312.         method: 'POST',
  313.         body: formData
  314.       });
  315.       
  316.       const result = await response.json();
  317.       alert(result.message || result.error);
  318.     });

  319.     // 检查通知
  320.     async function checkNotifications() {
  321.       const user = document.querySelector('[name="applicant"]').value;
  322.       const response = await fetch(`/api/notifications/${user}`);
  323.       const notifications = await response.json();
  324.       
  325.       const container = document.getElementById('notifications');
  326.       container.innerHTML = '<h2>通知</h2>';
  327.       
  328.       notifications.forEach(notify => {
  329.         const div = document.createElement('div');
  330.         div.className = 'notification';
  331.         div.innerHTML = `
  332.           <strong>${notify.process_title || '系统通知'}</strong>
  333.           <p>${notify.message}</p>
  334.           <small>${new Date(notify.created_at).toLocaleString()}</small>
  335.         `;
  336.         container.appendChild(div);
  337.       });
  338.     }

  339.     // 附件预览
  340.     document.querySelector('[name="attachments"]').addEventListener('change', (e) => {
  341.       const preview = document.getElementById('attachmentPreview');
  342.       preview.innerHTML = '';
  343.       
  344.       Array.from(e.target.files).forEach(file => {
  345.         const span = document.createElement('span');
  346.         span.className = 'attachment-item';
  347.         span.textContent = file.name;
  348.         preview.appendChild(span);
  349.       });
  350.     });

  351.     // 撤回申请
  352.     async function withdrawProcess(processId) {
  353.       if (!confirm('确定要撤回申请吗?')) return;
  354.       
  355.       const response = await fetch(`/api/process/${processId}/withdraw`, {
  356.         method: 'POST',
  357.         headers: { 'Content-Type': 'application/json' }
  358.       });
  359.       
  360.       const result = await response.json();
  361.       alert(result.message || result.error);
  362.     }
  363.   </script>
  364. </body>
  365. </html>
  366. */
复制代码


功能说明:

1. 数据库持久化
   - 使用SQLite存储流程数据
   - 三个核心表:processes(流程)、attachments(附件)、notifications(通知)
   - 支持状态跟踪和时间戳

2. 邮件/消息通知
   - 使用nodemailer发送邮件(示例使用Ethereal测试服务)
   - 数据库存储消息通知
   - 重要节点自动发送通知

3. 撤回功能
   - 只有pending状态且5分钟内可撤回
   - 撤回后状态变更为withdrawn
   - 自动通知相关审批人

4. 附件上传
   - 使用multer中间件处理文件上传
   - 限制最多5个附件
   - 文件存储在uploads目录
   - 数据库记录文件信息

使用步骤:
1. 安装依赖:运行 npm install express sqlite3 multer nodemailer
2. 创建uploads目录:mkdir uploads
3. 启动服务器:node server.js
4. 访问前端页面进行操作

注意:这只是一个教学示例,生产环境需要添加更多安全措施(如身份验证、输入验证、错误处理等)。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 23:07 | 显示全部楼层
@FishC 请举一个RBAC实例。以js和mysql数据库为例,创建了导航栏和侧边栏,不同权限显现的导航栏和侧边栏。比如导航栏有4个标签ABCD,侧边栏6个标签efghijk。管理员全部显示,员工显示AB和efgh,游客只显示C和k。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 昨天 23:11 | 显示全部楼层
kerln888 发表于 2026-03-09 23:07
@FishC 请举一个RBAC实例。以js和mysql数据库为例,创 ...


RBAC实例:基于JavaScript和MySQL的导航栏与侧边栏权限控制

本实例展示如何使用RBAC管理用户对导航栏和侧边栏的访问。假设导航栏有4个标签A、B、C、D,侧边栏有7个标签e、f、g、h、i、j、k(注:用户提及6个,但示例使用常见字母,您可按需调整)。权限分配如下:
- 管理员:显示所有标签。
- 员工:显示导航栏A、B和侧边栏e、f、g、h。
- 游客:显示导航栏C和侧边栏k。

1. 数据库设计
创建MySQL数据库和表,存储用户、角色、权限信息。


  1. -- 创建数据库
  2. CREATE DATABASE rbac_example;
  3. USE rbac_example;

  4. -- 用户表
  5. CREATE TABLE users (
  6.     id INT AUTO_INCREMENT PRIMARY KEY,
  7.     username VARCHAR(50) NOT NULL,
  8.     password VARCHAR(255) NOT NULL
  9. );

  10. -- 角色表
  11. CREATE TABLE roles (
  12.     id INT AUTO_INCREMENT PRIMARY KEY,
  13.     role_name VARCHAR(50) NOT NULL
  14. );

  15. -- 权限表
  16. CREATE TABLE permissions (
  17.     id INT AUTO_INCREMENT PRIMARY KEY,
  18.     permission_name VARCHAR(50) NOT NULL,
  19.     type ENUM('nav', 'side') NOT NULL
  20. );

  21. -- 用户角色关联表
  22. CREATE TABLE user_roles (
  23.     user_id INT,
  24.     role_id INT,
  25.     FOREIGN KEY (user_id) REFERENCES users(id),
  26.     FOREIGN KEY (role_id) REFERENCES roles(id),
  27.     PRIMARY KEY (user_id, role_id)
  28. );

  29. -- 角色权限关联表
  30. CREATE TABLE role_permissions (
  31.     role_id INT,
  32.     permission_id INT,
  33.     FOREIGN KEY (role_id) REFERENCES roles(id),
  34.     FOREIGN KEY (permission_id) REFERENCES permissions(id),
  35.     PRIMARY KEY (role_id, permission_id)
  36. );

  37. -- 插入示例角色
  38. INSERT INTO roles (role_name) VALUES ('admin'), ('employee'), ('guest');

  39. -- 插入权限
  40. INSERT INTO permissions (permission_name, type) VALUES
  41. ('nav_a', 'nav'),
  42. ('nav_b', 'nav'),
  43. ('nav_c', 'nav'),
  44. ('nav_d', 'nav'),
  45. ('side_e', 'side'),
  46. ('side_f', 'side'),
  47. ('side_g', 'side'),
  48. ('side_h', 'side'),
  49. ('side_i', 'side'),
  50. ('side_j', 'side'),
  51. ('side_k', 'side');

  52. -- 分配权限给管理员(所有权限)
  53. INSERT INTO role_permissions (role_id, permission_id)
  54. SELECT r.id, p.id FROM roles r, permissions p WHERE r.role_name = 'admin';

  55. -- 分配权限给员工
  56. INSERT INTO role_permissions (role_id, permission_id)
  57. SELECT r.id, p.id FROM roles r, permissions p
  58. WHERE r.role_name = 'employee'
  59. AND p.permission_name IN ('nav_a', 'nav_b', 'side_e', 'side_f', 'side_g', 'side_h');

  60. -- 分配权限给游客
  61. INSERT INTO role_permissions (role_id, permission_id)
  62. SELECT r.id, p.id FROM roles r, permissions p
  63. WHERE r.role_name = 'guest'
  64. AND p.permission_name IN ('nav_c', 'side_k');

  65. -- 插入示例用户(密码应使用哈希,此处为简化使用明文占位)
  66. INSERT INTO users (username, password) VALUES
  67. ('admin_user', 'hashed_pass'),
  68. ('employee_user', 'hashed_pass'),
  69. ('guest_user', 'hashed_pass');

  70. -- 分配角色给用户
  71. INSERT INTO user_roles (user_id, role_id) VALUES
  72. (1, (SELECT id FROM roles WHERE role_name = 'admin')),
  73. (2, (SELECT id FROM roles WHERE role_name = 'employee')),
  74. (3, (SELECT id FROM roles WHERE role_name = 'guest'));
复制代码


2. 后端API(使用Node.js和Express)
创建后端服务,提供权限查询接口。


  1. const express = require('express');
  2. const mysql = require('mysql2');
  3. const app = express();
  4. const port = 3000;

  5. // 数据库连接
  6. const db = mysql.createConnection({
  7.     host: 'localhost',
  8.     user: 'root',
  9.     password: 'password',
  10.     database: 'rbac_example'
  11. });

  12. db.connect(err => {
  13.     if (err) throw err;
  14.     console.log('Database connected');
  15. });

  16. app.use(express.json());

  17. // 获取用户权限的API端点
  18. app.get('/permissions/:userId', (req, res) => {
  19.     const userId = req.params.userId;
  20.     const query = `
  21.         SELECT p.permission_name, p.type
  22.         FROM users u
  23.         JOIN user_roles ur ON u.id = ur.user_id
  24.         JOIN roles r ON ur.role_id = r.id
  25.         JOIN role_permissions rp ON r.id = rp.role_id
  26.         JOIN permissions p ON rp.permission_id = p.id
  27.         WHERE u.id = ?
  28.     `;
  29.     db.query(query, [userId], (err, results) => {
  30.         if (err) {
  31.             res.status(500).json({ error: err.message });
  32.         } else {
  33.             res.json(results);
  34.         }
  35.     });
  36. });

  37. app.listen(port, () => {
  38.     console.log(`Server running on port ${port}`);
  39. });
复制代码


3. 前端JavaScript
在HTML页面中,使用JavaScript动态生成导航栏和侧边栏。


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>RBAC Example</title>
  7.     <style>
  8.         .navbar, .sidebar {
  9.             display: flex;
  10.             list-style: none;
  11.             padding: 0;
  12.         }
  13.         .navbar li, .sidebar li {
  14.             margin: 0 10px;
  15.         }
  16.     </style>
  17. </head>
  18. <body>
  19.     <nav>
  20.         <ul id="navbar" class="navbar"></ul>
  21.     </nav>
  22.     <aside>
  23.         <ul id="sidebar" class="sidebar"></ul>
  24.     </aside>

  25.     <script>
  26.         // 假设当前用户ID为1(管理员),实际应从登录会话获取
  27.         const userId = 1;

  28.         fetch(`[url=http://localhost:3000/permissions/${userId}`)]http://localhost:3000/permissions/${userId}`)[/url]
  29.             .then(response => response.json())
  30.             .then(permissions => {
  31.                 const navbar = document.getElementById('navbar');
  32.                 const sidebar = document.getElementById('sidebar');
  33.                 navbar.innerHTML = '';
  34.                 sidebar.innerHTML = '';

  35.                 // 标签映射配置
  36.                 const tagMap = {
  37.                     'nav_a': { text: 'A', href: '#' },
  38.                     'nav_b': { text: 'B', href: '#' },
  39.                     'nav_c': { text: 'C', href: '#' },
  40.                     'nav_d': { text: 'D', href: '#' },
  41.                     'side_e': { text: 'e', href: '#' },
  42.                     'side_f': { text: 'f', href: '#' },
  43.                     'side_g': { text: 'g', href: '#' },
  44.                     'side_h': { text: 'h', href: '#' },
  45.                     'side_i': { text: 'i', href: '#' },
  46.                     'side_j': { text: 'j', href: '#' },
  47.                     'side_k': { text: 'k', href: '#' }
  48.                 };

  49.                 // 生成导航栏
  50.                 permissions.filter(p => p.type === 'nav').forEach(perm => {
  51.                     const tag = tagMap[perm.permission_name];
  52.                     if (tag) {
  53.                         const li = document.createElement('li');
  54.                         const a = document.createElement('a');
  55.                         a.href = tag.href;
  56.                         a.textContent = tag.text;
  57.                         li.appendChild(a);
  58.                         navbar.appendChild(li);
  59.                     }
  60.                 });

  61.                 // 生成侧边栏
  62.                 permissions.filter(p => p.type === 'side').forEach(perm => {
  63.                     const tag = tagMap[perm.permission_name];
  64.                     if (tag) {
  65.                         const li = document.createElement('li');
  66.                         const a = document.createElement('a');
  67.                         a.href = tag.href;
  68.                         a.textContent = tag.text;
  69.                         li.appendChild(a);
  70.                         sidebar.appendChild(li);
  71.                     }
  72.                 });
  73.             })
  74.             .catch(error => console.error('Error:', error));
  75.     </script>
  76. </body>
  77. </html>
复制代码


总结
以上实例实现了RBAC控制的前端菜单显示。通过数据库分配角色权限,后端API提供权限数据,前端JavaScript动态渲染界面。您可以根据实际需求调整角色和权限分配。注意:实际应用中需加强安全性,如使用密码哈希、会话管理和错误处理。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 23:43 | 显示全部楼层
@FishC 更改一下条件。请举一个RBAC实例。以js和mysql数据库为例,创建了导航栏和侧边栏,导航栏有4个标签ABCD,且导航栏和侧边栏关联。就是点击A显示侧边栏2个标签A1 A2,点击B显示B1 B2 B3,点击C显示C1 C2,点击D显示D D2 D3 D4。现在要求管理员全部显示,员工显示ABD和A1 B1 B2 D1 D2 D4,游客只显示C和C1。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 昨天 23:47 | 显示全部楼层
kerln888 发表于 2026-03-09 23:43
@FishC 更改一下条件。请举一个RBAC实例。以js和mysql ...

更改一下条件。请举一个RBAC实例。以js和mysql数据库为例,创建了导航栏和侧边栏,导航栏有4个标签ABCD,且导航栏和侧边栏关联。就是点击A显示侧边栏2个标签A1 A2,点击B显示B1 B2 B3,点击C显示C1 C2,点击D显示D D2 D3 D4。现在要求管理员全部显示,员工显示ABD和A1 B1 B2 D1 D2 D4,游客只显示C和C1。


下面是一个基于RBAC的实例,使用MySQL数据库和JavaScript实现。数据库存储角色和权限,前端根据用户角色动态生成导航栏和侧边栏。

1. 数据库设计

首先,创建角色表、权限表和角色权限关联表,并插入示例数据。


  1. -- 创建角色表
  2. CREATE TABLE roles (
  3.     id INT PRIMARY KEY AUTO_INCREMENT,
  4.     role_name VARCHAR(50) NOT NULL UNIQUE
  5. );

  6. -- 插入角色数据
  7. INSERT INTO roles (role_name) VALUES ('admin'), ('staff'), ('guest');

  8. -- 创建权限表
  9. CREATE TABLE permissions (
  10.     id INT PRIMARY KEY AUTO_INCREMENT,
  11.     permission_name VARCHAR(100) NOT NULL UNIQUE
  12. );

  13. -- 插入权限数据:导航栏和侧边栏标签的访问权限
  14. INSERT INTO permissions (permission_name) VALUES
  15. ('view_nav_A'), ('view_nav_B'), ('view_nav_C'), ('view_nav_D'),
  16. ('view_sidebar_A1'), ('view_sidebar_A2'),
  17. ('view_sidebar_B1'), ('view_sidebar_B2'), ('view_sidebar_B3'),
  18. ('view_sidebar_C1'), ('view_sidebar_C2'),
  19. ('view_sidebar_D1'), ('view_sidebar_D2'), ('view_sidebar_D3'), ('view_sidebar_D4');

  20. -- 创建角色权限关联表
  21. CREATE TABLE role_permissions (
  22.     role_id INT,
  23.     permission_id INT,
  24.     FOREIGN KEY (role_id) REFERENCES roles(id),
  25.     FOREIGN KEY (permission_id) REFERENCES permissions(id),
  26.     PRIMARY KEY (role_id, permission_id)
  27. );

  28. -- 为管理员分配所有权限
  29. INSERT INTO role_permissions (role_id, permission_id)
  30. SELECT r.id, p.id
  31. FROM roles r, permissions p
  32. WHERE r.role_name = 'admin';

  33. -- 为员工分配权限:导航栏A、B、D,侧边栏A1、B1、B2、D1、D2、D4
  34. INSERT INTO role_permissions (role_id, permission_id)
  35. SELECT r.id, p.id
  36. FROM roles r, permissions p
  37. WHERE r.role_name = 'staff'
  38. AND p.permission_name IN (
  39.     'view_nav_A', 'view_nav_B', 'view_nav_D',
  40.     'view_sidebar_A1', 'view_sidebar_B1', 'view_sidebar_B2',
  41.     'view_sidebar_D1', 'view_sidebar_D2', 'view_sidebar_D4'
  42. );

  43. -- 为游客分配权限:导航栏C,侧边栏C1
  44. INSERT INTO role_permissions (role_id, permission_id)
  45. SELECT r.id, p.id
  46. FROM roles r, permissions p
  47. WHERE r.role_name = 'guest'
  48. AND p.permission_name IN ('view_nav_C', 'view_sidebar_C1');
复制代码


2. 前端HTML结构

创建一个简单的HTML页面,包含导航栏和侧边栏容器。


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>RBAC导航示例</title>
  5.     <style>
  6.         .navbar {
  7.             display: flex;
  8.             background-color: #333;
  9.             padding: 10px;
  10.         }
  11.         .nav-item {
  12.             padding: 10px 15px;
  13.             color: white;
  14.             cursor: pointer;
  15.             margin-right: 5px;
  16.         }
  17.         .nav-item:hover {
  18.             background-color: #555;
  19.         }
  20.         .sidebar {
  21.             margin-top: 20px;
  22.             padding: 10px;
  23.             border: 1px solid #ccc;
  24.             min-height: 100px;
  25.         }
  26.         .sidebar-item {
  27.             padding: 5px;
  28.             border-bottom: 1px dashed #ddd;
  29.         }
  30.     </style>
  31. </head>
  32. <body>
  33.     <div id="navbar" class="navbar"></div>
  34.     <div id="sidebar" class="sidebar"></div>
  35.     <script src="menu.js"></script>
  36. </body>
  37. </html>
复制代码


3. JavaScript逻辑

menu.js 中,假设从后端获取用户权限,并根据权限动态生成导航栏和侧边栏。这里模拟一个API调用返回权限列表。


  1. // 假设当前用户角色,实际应从登录状态获取
  2. let userRole = 'staff'; // 可改为 'admin' 或 'guest' 测试

  3. // 模拟权限数据,实际应从后端API获取
  4. const permissionMap = {
  5.     admin: [
  6.         'view_nav_A', 'view_nav_B', 'view_nav_C', 'view_nav_D',
  7.         'view_sidebar_A1', 'view_sidebar_A2',
  8.         'view_sidebar_B1', 'view_sidebar_B2', 'view_sidebar_B3',
  9.         'view_sidebar_C1', 'view_sidebar_C2',
  10.         'view_sidebar_D1', 'view_sidebar_D2', 'view_sidebar_D3', 'view_sidebar_D4'
  11.     ],
  12.     staff: [
  13.         'view_nav_A', 'view_nav_B', 'view_nav_D',
  14.         'view_sidebar_A1', 'view_sidebar_B1', 'view_sidebar_B2',
  15.         'view_sidebar_D1', 'view_sidebar_D2', 'view_sidebar_D4'
  16.     ],
  17.     guest: [
  18.         'view_nav_C', 'view_sidebar_C1'
  19.     ]
  20. };

  21. // 模拟从后端获取权限
  22. function fetchPermissions(role) {
  23.     return new Promise(resolve => {
  24.         setTimeout(() => resolve(permissionMap[role] || []), 100);
  25.     });
  26. }

  27. // 导航栏和侧边栏配置
  28. const navConfig = [
  29.     {
  30.         id: 'A',
  31.         navPermission: 'view_nav_A',
  32.         sidebarItems: [
  33.             { id: 'A1', permission: 'view_sidebar_A1' },
  34.             { id: 'A2', permission: 'view_sidebar_A2' }
  35.         ]
  36.     },
  37.     {
  38.         id: 'B',
  39.         navPermission: 'view_nav_B',
  40.         sidebarItems: [
  41.             { id: 'B1', permission: 'view_sidebar_B1' },
  42.             { id: 'B2', permission: 'view_sidebar_B2' },
  43.             { id: 'B3', permission: 'view_sidebar_B3' }
  44.         ]
  45.     },
  46.     {
  47.         id: 'C',
  48.         navPermission: 'view_nav_C',
  49.         sidebarItems: [
  50.             { id: 'C1', permission: 'view_sidebar_C1' },
  51.             { id: 'C2', permission: 'view_sidebar_C2' }
  52.         ]
  53.     },
  54.     {
  55.         id: 'D',
  56.         navPermission: 'view_nav_D',
  57.         sidebarItems: [
  58.             { id: 'D1', permission: 'view_sidebar_D1' },
  59.             { id: 'D2', permission: 'view_sidebar_D2' },
  60.             { id: 'D3', permission: 'view_sidebar_D3' },
  61.             { id: 'D4', permission: 'view_sidebar_D4' }
  62.         ]
  63.     }
  64. ];

  65. // 生成导航栏和侧边栏
  66. async function generateMenu() {
  67.     const permissions = await fetchPermissions(userRole);
  68.     const navBar = document.getElementById('navbar');
  69.     const sidebar = document.getElementById('sidebar');
  70.     navBar.innerHTML = '';
  71.     sidebar.innerHTML = '';

  72.     // 生成导航栏
  73.     navConfig.forEach(item => {
  74.         if (permissions.includes(item.navPermission)) {
  75.             const navElement = document.createElement('div');
  76.             navElement.className = 'nav-item';
  77.             navElement.textContent = item.id;
  78.             navElement.addEventListener('click', () => {
  79.                 generateSidebar(item.sidebarItems, permissions);
  80.             });
  81.             navBar.appendChild(navElement);
  82.         }
  83.     });

  84.     // 默认显示第一个有权限的导航栏对应的侧边栏
  85.     const firstNav = navConfig.find(item => permissions.includes(item.navPermission));
  86.     if (firstNav) {
  87.         generateSidebar(firstNav.sidebarItems, permissions);
  88.     }
  89. }

  90. // 生成侧边栏
  91. function generateSidebar(sidebarItems, permissions) {
  92.     const sidebar = document.getElementById('sidebar');
  93.     sidebar.innerHTML = '';
  94.     sidebarItems.forEach(item => {
  95.         if (permissions.includes(item.permission)) {
  96.             const sidebarElement = document.createElement('div');
  97.             sidebarElement.className = 'sidebar-item';
  98.             sidebarElement.textContent = item.id;
  99.             sidebar.appendChild(sidebarElement);
  100.         }
  101.     });
  102. }

  103. // 页面加载时初始化
  104. document.addEventListener('DOMContentLoaded', generateMenu);
复制代码


4. 使用说明

- 修改 userRole 变量为 'admin''staff''guest' 测试不同角色。
- 实际应用中,后端需提供API(如 /api/permissions)根据用户角色查询数据库返回权限列表,替换 fetchPermissions 函数中的模拟数据。
- 导航栏点击时,侧边栏会动态显示对应项,且只显示当前角色有权限的标签。

这个实例实现了RBAC的基本逻辑,通过数据库管理权限,前端根据权限动态渲染界面。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2026-3-10 01:03

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表