鱼C论坛

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

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

  [复制链接]
发表于 2026-3-9 22:24:25 | 显示全部楼层
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
回复 支持 反对

使用道具 举报

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

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

使用道具 举报

发表于 2026-3-9 22:38:33 | 显示全部楼层
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
回复 支持 反对

使用道具 举报

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

使用道具 举报

发表于 2026-3-9 23:11:15 | 显示全部楼层
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
回复 支持 反对

使用道具 举报

 楼主| 发表于 2026-3-9 23:43:34 | 显示全部楼层
@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
回复 支持 反对

使用道具 举报

发表于 2026-3-9 23:47:12 | 显示全部楼层
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
回复 支持 反对

使用道具 举报

 楼主| 发表于 2026-3-11 21:45:46 | 显示全部楼层
@FishC plotlyjs设置了长度和宽度以后,怎么让图表尽可能的大,贴近边框。现在图表和边框的距离太远了,显得很小
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2026-3-11 21:46:20 | 显示全部楼层
kerln888 发表于 2026-03-11 21:45
@FishC plotlyjs设置了长度和宽度以后,怎么让图表尽 ...


在 Plotly.js 中,图表与边框距离过远通常是由于默认的边距(margin)较大导致的。你可以通过调整 layout.margin 属性来减小边距,使图表尽可能贴近边框。

以下是一个示例代码,展示如何设置边距为最小值:


  1. var data = [{
  2.   x: [1, 2, 3, 4],
  3.   y: [10, 11, 12, 13],
  4.   type: 'scatter'
  5. }];

  6. var layout = {
  7.   width: 800,   // 你设置的宽度
  8.   height: 600,  // 你设置的高度
  9.   margin: {
  10.     l: 0,       // 左边距设为0
  11.     r: 0,       // 右边距设为0
  12.     t: 0,       // 上边距设为0
  13.     b: 0        // 下边距设为0
  14.   },
  15.   autosize: false // 禁用自动调整大小,以便使用固定宽度和高度
  16. };

  17. Plotly.newPlot('myDiv', data, layout);
复制代码


如果你需要保留坐标轴标签或标题,可以适当调整边距值(例如设为 10 或 20),而不是全部为 0。

此外,请检查图表容器的 CSS 样式,确保没有内边距(padding)或边框(border)影响显示。例如,可以在 CSS 中设置:


  1. #myDiv {
  2.   padding: 0;
  3.   margin: 0;
  4.   border: none;
  5. }
复制代码


这样,图表就能更充分地利用容器空间,贴近边框显示了。

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

使用道具 举报

 楼主| 发表于 2026-3-11 22:06:46 | 显示全部楼层
@FishC plotlyjs悬停显示信息有几种模式,请一一举例列举
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2026-3-11 22:07:56 | 显示全部楼层
kerln888 发表于 2026-03-11 22:06
@FishC plotlyjs悬停显示信息有几种模式,请一一举例 ...


在 plotly.js 中,悬停显示信息通过 hoverinfo 属性控制,常见的模式有以下几种:

  • 'x':仅显示 x 坐标值。
  • 'y':仅显示 y 坐标值。
  • 'x+y':同时显示 x 和 y 坐标值。
  • 'text':显示文本信息(需在 trace 中设置 text 属性)。
  • 'name':显示图例名称。
  • 'all':显示所有可用信息(包括 x、y、text、name 等)。
  • 'none':不显示任何悬停信息。
  • 组合模式:例如 'x+y+text',可同时显示 x、y 和文本。


以下是一些示例代码,展示如何在 trace 中设置不同的悬停模式:


  1. // 模式1: 显示 x 和 y 值
  2. var trace1 = {
  3.   x: [1, 2, 3],
  4.   y: [4, 5, 6],
  5.   hoverinfo: 'x+y',
  6.   type: 'scatter'
  7. };

  8. // 模式2: 只显示文本
  9. var trace2 = {
  10.   x: [1, 2, 3],
  11.   y: [7, 8, 9],
  12.   text: ['点A', '点B', '点C'],
  13.   hoverinfo: 'text',
  14.   type: 'scatter'
  15. };

  16. // 模式3: 显示所有信息
  17. var trace3 = {
  18.   x: [1, 2, 3],
  19.   y: [10, 11, 12],
  20.   text: ['信息1', '信息2', '信息3'],
  21.   name: '数据系列',
  22.   hoverinfo: 'all',
  23.   type: 'scatter'
  24. };

  25. // 模式4: 不显示悬停信息
  26. var trace4 = {
  27.   x: [1, 2, 3],
  28.   y: [13, 14, 15],
  29.   hoverinfo: 'none',
  30.   type: 'scatter'
  31. };

  32. // 组合模式示例:显示 x、y 和文本
  33. var trace5 = {
  34.   x: [1, 2, 3],
  35.   y: [16, 17, 18],
  36.   text: ['组合1', '组合2', '组合3'],
  37.   hoverinfo: 'x+y+text',
  38.   type: 'scatter'
  39. };
复制代码


这些模式可以根据需求灵活组合使用,以实现不同的悬停信息展示效果。

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

使用道具 举报

 楼主| 发表于 2026-3-11 22:57:17 | 显示全部楼层
@FishC matplotlib柱状图x坐标是2021, 2022, 2023,2024。怎么样能让x坐标变成2021年, 2022年, 2023年, 2024年
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2026-3-11 22:57:49 | 显示全部楼层
kerln888 发表于 2026-03-11 22:57
@FishC matplotlib柱状图x坐标是2021, 2022, 2023, ...


可以使用 matplotlib 的 plt.xticks()ax.set_xticklabels() 来设置 x 轴标签为带“年”的字符串。以下是示例代码:


  1. import matplotlib.pyplot as plt

  2. # 示例数据
  3. years = [2021, 2022, 2023, 2024]
  4. values = [10, 20, 15, 25]

  5. # 绘制柱状图
  6. plt.bar(years, values)

  7. # 设置 x 轴刻度标签:将数字年份转换为“2021年”等形式
  8. plt.xticks(years, [f"{year}年" for year in years])

  9. # 显示图形
  10. plt.show()
复制代码


如果使用面向对象的风格,可以这样写:


  1. fig, ax = plt.subplots()
  2. ax.bar(years, values)
  3. ax.set_xticks(years)
  4. ax.set_xticklabels([f"{year}年" for year in years])
  5. plt.show()
复制代码


解释:通过 plt.xticks()ax.set_xticklabels() 将原始年份数字替换为自定义标签,即可显示“年”字。

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

使用道具 举报

 楼主| 发表于 2026-3-14 22:08:24 | 显示全部楼层
@FishC PyCharm Community Edition 2020.3.2 x64 报错pycharm python package tools not found,点击install packaging tools后,提示Packages installed successfully: Installed Python packaging tools。但是回到File | Settings | Project: pythonProject | Python Interpreter,还是提示pycharm python package tools not found。这要怎么解决
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2026-3-14 22:09:29 | 显示全部楼层
kerln888 发表于 2026-03-14 22:08
@FishC PyCharm Community Edition 2020.3.2 x64 报错 ...


亲爱的鱼油,你好!遇到 PyCharm 提示包工具未找到的问题,别担心,我们可以一步步来解决。

首先,请确保你的 Python 解释器配置正确。以下是几个可能的解决方案:

  • 检查 Python 解释器路径
    打开 File | Settings | Project: pythonProject | Python Interpreter,确认选择的解释器路径是有效的。你可以点击下拉菜单,选择或添加一个正确的 Python 解释器(例如,系统安装的 Python 或虚拟环境)。
  • 手动安装包管理工具
    如果自动安装失败,可以尝试手动安装。打开终端或命令提示符,运行以下命令:

    1. python -m ensurepip --upgrade
    2. python -m pip install --upgrade pip setuptools wheel
    复制代码

    如果你使用 Python 3,可能需要用 python3 代替 python
  • 重启 PyCharm
    安装完成后,完全关闭 PyCharm 并重新启动。有时 IDE 需要刷新才能识别新安装的工具。
  • 创建新的虚拟环境
    如果问题依旧,可以尝试创建一个新的虚拟环境。在 PyCharm 中:
    - 进入 File | Settings | Project: pythonProject | Python Interpreter
    - 点击齿轮图标,选择 Add...
    - 在弹出窗口中,选择 Virtualenv EnvironmentConda Environment,然后指定位置。
    - 点击 OK 创建新环境,然后将其设为项目解释器。
  • 清除 PyCharm 缓存
    有时缓存可能导致问题。尝试清除缓存:
    - 在 PyCharm 中,点击 File | Invalidate Caches / Restart...
    - 选择 Invalidate and Restart


如果以上步骤都无法解决,请检查你的网络连接或代理设置,确保 PyCharm 能够正常下载包。另外,确保你的 PyCharm 版本与 Python 版本兼容(2020.3.2 支持 Python 3.5+)。

希望这些方法能帮助你解决问题!如果还有其他疑问,随时再来提问。

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

使用道具 举报

 楼主| 发表于 2026-3-15 09:17:40 | 显示全部楼层
@FishC pandas读取excel后,有4列分别是年、月、日、时间。怎么把这四列合并成一列,格式为xxxx-xx-xx xx:xx:xx,并把原来的四列drop掉。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2026-3-15 09:19:04 | 显示全部楼层
kerln888 发表于 2026-03-15 09:17
@FishC pandas读取excel后,有4列分别是年、月、日、 ...


要使用pandas将年、月、日、时间四列合并成一列,并格式化为"xxxx-xx-xx xx:xx:xx",然后删除原列,可以参考以下步骤:

首先,确保数据列类型正确,然后合并并格式化。

方法一:直接拼接字符串(假设时间列已是"HH:MM:SS"格式)


  1. import pandas as pd

  2. # 读取Excel文件,假设DataFrame为df,列名为'年', '月', '日', '时间'
  3. # df = pd.read_excel('your_file.xlsx')

  4. # 将年、月、日转换为整数(如果已是整数可跳过)
  5. df['年'] = df['年'].astype(int)
  6. df['月'] = df['月'].astype(int)
  7. df['日'] = df['日'].astype(int)

  8. # 将时间列转换为字符串(如果已是字符串可跳过)
  9. df['时间'] = df['时间'].astype(str)

  10. # 合并为字符串列,使用zfill确保月、日两位
  11. df['合并时间'] = df['年'].astype(str) + '-' + df['月'].astype(str).str.zfill(2) + '-' + df['日'].astype(str).str.zfill(2) + ' ' + df['时间']

  12. # 删除原来的四列
  13. df.drop(['年', '月', '日', '时间'], axis=1, inplace=True)
复制代码


如果时间列不是完整格式(例如缺少秒部分),需先处理。例如,若时间列是"HH:MM",可添加秒部分:


  1. # 如果时间列是"HH:MM"格式,添加秒部分
  2. df['时间'] = df['时间'] + ':00'
复制代码


方法二:使用pandas的to_datetime函数(更稳健)


  1. import pandas as pd

  2. # 读取数据后,将各列转换为字符串并格式化
  3. date_str = df['年'].astype(str) + '-' + df['月'].astype(str).str.zfill(2) + '-' + df['日'].astype(str).str.zfill(2)
  4. time_str = df['时间'].astype(str)

  5. # 合并日期和时间字符串
  6. datetime_str = date_str + ' ' + time_str

  7. # 转换为datetime对象
  8. df['datetime'] = pd.to_datetime(datetime_str)

  9. # 格式化为指定字符串
  10. df['格式化时间'] = df['datetime'].dt.strftime('%Y-%m-%d %H:%M:%S')

  11. # 删除原列及中间列
  12. df.drop(['年', '月', '日', '时间', 'datetime'], axis=1, inplace=True)
复制代码


选择合适的方法后,DataFrame将新增一列格式为"xxxx-xx-xx xx:xx:xx"的字符串列,原四列已被删除。注意根据实际数据调整列名和格式。

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

使用道具 举报

 楼主| 发表于 2026-3-15 09:23:47 | 显示全部楼层
FishC 发表于 2026-3-15 09:19
要使用pandas将年、月、日、时间四列合并成一列,并格式化为"xxxx-xx-xx xx:xx:xx",然后删除原列,可 ...

@FishC 如果excel中年月日或者时间输入错误,比如时间xx::xx:xx多打了一个冒号,如何能修复这些错误,保证数据准确性
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2026-3-15 09:24:39 | 显示全部楼层
kerln888 发表于 2026-03-15 09:23
FishC 发表于 2026-3-15 09:19
要使用pandas将年、月 ...

@FishC 如果excel中年月日或者时间输入错误,比如时间xx::xx:xx多打了一个冒号,如何能修复这些错误,保证数据准确性


你好!要修复Excel中数据输入错误(如时间多冒号),可以使用pandas加载数据后,通过字符串处理进行校正。以下步骤可帮助保证数据准确性:

  • 加载Excel文件到DataFrame。
  • 检查并修复时间列的多余冒号(或其他格式问题)。
  • 验证年月日列的有效性。
  • 合并列并转换为标准日期时间格式。
  • 删除原列。


示例代码:


  1. import pandas as pd

  2. # 1. 加载Excel数据
  3. df = pd.read_excel('your_file.xlsx')  # 替换为你的文件名

  4. # 2. 修复时间列:将多个冒号替换为单个冒号
  5. # 假设时间列名为 'time',使用字符串替换
  6. df['time'] = df['time'].astype(str).str.replace(r':+', ':', regex=True)

  7. # 3. 可选:检查年月日列,确保为有效整数(例如,去除空格或非数字字符)
  8. # 假设列名为 'year', 'month', 'day'
  9. for col in ['year', 'month', 'day']:
  10.     df[col] = pd.to_numeric(df[col], errors='coerce')  # 转换错误值会变为NaN

  11. # 4. 合并年月日和时间,创建日期时间列
  12. # 先组合成字符串,再转换为日期时间格式
  13. df['datetime'] = df['year'].astype(str) + '-' + df['month'].astype(str) + '-' + df['day'].astype(str) + ' ' + df['time']
  14. df['datetime'] = pd.to_datetime(df['datetime'], format='%Y-%m-%d %H:%M:%S', errors='coerce')  # 无效值变为NaN

  15. # 5. 删除原列
  16. df.drop(columns=['year', 'month', 'day', 'time'], inplace=True)

  17. # 6. 检查修复结果:查看是否有NaN(表示转换失败的数据)
  18. print(df['datetime'].isnull().sum())  # 输出转换失败的数量
  19. print(df.head())

  20. # 保存到新Excel文件(如果需要)
  21. df.to_excel('fixed_file.xlsx', index=False)
复制代码


解释:
- 使用 str.replace(r':+', ':', regex=True) 修复时间列中多余冒号,例如将 xx::xx:xx 改为 xx:xx:xx
- pd.to_numericpd.to_datetimeerrors='coerce' 参数会将无效数据转换为NaN,便于后续处理。
- 最后合并列时,建议先创建字符串再转换,这样可控制格式。

注意事项:
- 如果数据错误复杂(如年月日格式混乱),可能需要更细致的清洗(如使用正则表达式)。
- 转换后检查NaN值,以识别未被修复的数据,并手动处理。

这样就能在合并前修复错误,确保数据准确性。如果有其他具体错误类型,可以进一步调整清洗逻辑。

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

使用道具 举报

 楼主| 发表于 2026-3-15 09:31:48 | 显示全部楼层
@FishC pandas读取excel后,如何把数据存储到mysql数据库
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-4-7 22:12

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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