|
|
连接数据库持久化存储
- 添加邮件或消息通知
- 实现撤回功能
- 添加附件上传功能
这些怎么做,请给个例子
我来为你扩展之前的流程审批示例,添加这些实用功能。这是一个完整的全栈示例,使用Node.js + Express + SQLite:
- // 1. 安装依赖:npm install express sqlite3 multer nodemailer body-parser
- // ---------- server.js ----------
- const express = require('express');
- const sqlite3 = require('sqlite3').verbose();
- const multer = require('multer');
- const nodemailer = require('nodemailer');
- const path = require('path');
- const fs = require('fs');
- const app = express();
- const port = 3000;
- // 数据库初始化
- const db = new sqlite3.Database('./approval.db');
- db.serialize(() => {
- // 流程表
- db.run(`CREATE TABLE IF NOT EXISTS processes (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- title TEXT NOT NULL,
- content TEXT,
- applicant TEXT NOT NULL,
- status TEXT DEFAULT 'pending',
- level1_approver TEXT,
- level2_approver TEXT,
- level1_result TEXT,
- level2_result TEXT,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- can_withdraw BOOLEAN DEFAULT 1
- )`);
- // 附件表
- db.run(`CREATE TABLE IF NOT EXISTS attachments (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- process_id INTEGER,
- filename TEXT,
- path TEXT,
- uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (process_id) REFERENCES processes(id)
- )`);
- // 消息通知表
- db.run(`CREATE TABLE IF NOT EXISTS notifications (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- process_id INTEGER,
- user TEXT,
- message TEXT,
- is_read BOOLEAN DEFAULT 0,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (process_id) REFERENCES processes(id)
- )`);
- });
- // 文件上传配置
- const storage = multer.diskStorage({
- destination: './uploads/',
- filename: (req, file, cb) => {
- cb(null, Date.now() + path.extname(file.originalname));
- }
- });
- const upload = multer({ storage });
- // 邮件配置(示例使用Ethereal测试邮件服务)
- const transporter = nodemailer.createTransport({
- host: 'smtp.ethereal.email',
- port: 587,
- auth: {
- user: 'your-email@ethereal.email',
- pass: 'your-password'
- }
- });
- // 中间件
- app.use(express.json());
- app.use(express.urlencoded({ extended: true }));
- app.use('/uploads', express.static('uploads'));
- // ---------- API接口 ----------
- // 1. 发起申请(带附件上传)
- app.post('/api/process', upload.array('attachments', 5), async (req, res) => {
- const { title, content, applicant, level1_approver, level2_approver } = req.body;
- const files = req.files || [];
- db.run(
- `INSERT INTO processes (title, content, applicant, level1_approver, level2_approver)
- VALUES (?, ?, ?, ?, ?)`,
- [title, content, applicant, level1_approver, level2_approver],
- function(err) {
- if (err) {
- return res.status(500).json({ error: err.message });
- }
- const processId = this.lastID;
- // 保存附件信息
- files.forEach(file => {
- db.run(
- `INSERT INTO attachments (process_id, filename, path) VALUES (?, ?, ?)`,
- [processId, file.originalname, file.path]
- );
- });
- // 发送邮件通知一级审批人
- sendEmailNotification(
- level1_approver,
- '新的审批申请',
- `您有一个新的审批申请需要处理:${title}`
- );
- // 添加消息通知
- addNotification(processId, level1_approver, '您有一个新的待审批申请');
- res.json({
- success: true,
- processId,
- message: '申请提交成功'
- });
- }
- );
- });
- // 2. 一级审批
- app.post('/api/process/:id/level1-approve', (req, res) => {
- const { result, comment } = req.body;
- const processId = req.params.id;
- db.run(
- `UPDATE processes SET level1_result = ?, status = ?, updated_at = CURRENT_TIMESTAMP
- WHERE id = ? AND status = 'pending'`,
- [result, result === 'approved' ? 'level1_approved' : 'rejected', processId],
- function(err) {
- if (err || this.changes === 0) {
- return res.status(400).json({ error: '审批失败' });
- }
- // 通知申请人
- db.get(`SELECT applicant FROM processes WHERE id = ?`, [processId], (err, row) => {
- if (row) {
- addNotification(processId, row.applicant, `一级审批结果:${result}`);
- }
- });
- // 如果通过,通知二级审批人
- if (result === 'approved') {
- db.get(
- `SELECT level2_approver FROM processes WHERE id = ?`,
- [processId],
- (err, row) => {
- if (row) {
- addNotification(processId, row.level2_approver, '请进行二级审批');
- sendEmailNotification(
- row.level2_approver,
- '二级审批通知',
- '请处理待审批的申请'
- );
- }
- }
- );
- }
- res.json({ success: true, message: '审批完成' });
- }
- );
- });
- // 3. 撤回申请(只有pending状态且未超过5分钟可撤回)
- app.post('/api/process/:id/withdraw', (req, res) => {
- const processId = req.params.id;
- db.run(
- `UPDATE processes SET status = 'withdrawn', can_withdraw = 0
- WHERE id = ? AND status = 'pending'
- AND datetime(created_at, '+5 minutes') > datetime('now')`,
- [processId],
- function(err) {
- if (err || this.changes === 0) {
- return res.status(400).json({
- error: '撤回失败,可能已超时或状态不符'
- });
- }
- // 通知审批人
- db.get(
- `SELECT level1_approver FROM processes WHERE id = ?`,
- [processId],
- (err, row) => {
- if (row) {
- addNotification(processId, row.level1_approver, '申请已撤回');
- }
- }
- );
- res.json({ success: true, message: '撤回成功' });
- }
- );
- });
- // 4. 获取用户通知
- app.get('/api/notifications/:user', (req, res) => {
- const user = req.params.user;
- db.all(
- `SELECT n.*, p.title as process_title
- FROM notifications n
- LEFT JOIN processes p ON n.process_id = p.id
- WHERE n.user = ? AND n.is_read = 0
- ORDER BY n.created_at DESC`,
- [user],
- (err, rows) => {
- if (err) {
- return res.status(500).json({ error: err.message });
- }
- res.json(rows);
- }
- );
- });
- // 5. 获取申请详情(含附件)
- app.get('/api/process/:id', (req, res) => {
- const processId = req.params.id;
- db.get(
- `SELECT p.*,
- GROUP_CONCAT(a.filename) as attachments
- FROM processes p
- LEFT JOIN attachments a ON p.id = a.process_id
- WHERE p.id = ?
- GROUP BY p.id`,
- [processId],
- (err, process) => {
- if (err) {
- return res.status(500).json({ error: err.message });
- }
- // 获取附件详情
- db.all(
- `SELECT * FROM attachments WHERE process_id = ?`,
- [processId],
- (err, attachments) => {
- process.attachments = attachments;
- res.json(process);
- }
- );
- }
- );
- });
- // ---------- 辅助函数 ----------
- // 发送邮件通知
- async function sendEmailNotification(to, subject, text) {
- try {
- const info = await transporter.sendMail({
- from: '"审批系统" <system@example.com>',
- to: to,
- subject: subject,
- text: text
- });
- console.log('邮件已发送:', info.messageId);
- } catch (error) {
- console.error('邮件发送失败:', error);
- }
- }
- // 添加消息通知
- function addNotification(processId, user, message) {
- db.run(
- `INSERT INTO notifications (process_id, user, message) VALUES (?, ?, ?)`,
- [processId, user, message]
- );
- }
- app.listen(port, () => {
- console.log(`服务器运行在 [url=http://localhost:${port}`);]http://localhost:${port}`);[/url]
- });
- // ---------- 前端HTML示例 ----------
- /*
- <!DOCTYPE html>
- <html>
- <head>
- <title>流程审批系统</title>
- <style>
- .container { max-width: 800px; margin: 20px auto; padding: 20px; }
- .form-group { margin-bottom: 15px; }
- .form-group label { display: block; margin-bottom: 5px; }
- .form-group input, .form-group textarea {
- width: 100%; padding: 8px;
- }
- .btn { padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; }
- .btn:hover { background: #0056b3; }
- .notification { background: #f8f9fa; padding: 10px; margin: 10px 0; border-left: 4px solid #007bff; }
- .attachment-list { margin-top: 10px; }
- .attachment-item {
- display: inline-block;
- background: #e9ecef;
- padding: 5px 10px;
- margin: 5px;
- border-radius: 3px;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>发起审批申请</h1>
- <form id="processForm" enctype="multipart/form-data">
- <div class="form-group">
- <label>标题:</label>
- <input type="text" name="title" required>
- </div>
- <div class="form-group">
- <label>内容:</label>
- <textarea name="content" rows="4" required></textarea>
- </div>
- <div class="form-group">
- <label>申请人:</label>
- <input type="text" name="applicant" required>
- </div>
- <div class="form-group">
- <label>一级审批人邮箱:</label>
- <input type="email" name="level1_approver" required>
- </div>
- <div class="form-group">
- <label>二级审批人邮箱:</label>
- <input type="email" name="level2_approver" required>
- </div>
- <div class="form-group">
- <label>附件上传(最多5个):</label>
- <input type="file" name="attachments" multiple>
- <div class="attachment-list" id="attachmentPreview"></div>
- </div>
- <button type="submit" class="btn">提交申请</button>
- <button type="button" class="btn" onclick="checkNotifications()">检查通知</button>
- </form>
- <div id="notifications"></div>
- <div id="processList"></div>
- </div>
- <script>
- // 提交申请(带附件)
- document.getElementById('processForm').addEventListener('submit', async (e) => {
- e.preventDefault();
- const formData = new FormData(e.target);
-
- const response = await fetch('/api/process', {
- method: 'POST',
- body: formData
- });
-
- const result = await response.json();
- alert(result.message || result.error);
- });
- // 检查通知
- async function checkNotifications() {
- const user = document.querySelector('[name="applicant"]').value;
- const response = await fetch(`/api/notifications/${user}`);
- const notifications = await response.json();
-
- const container = document.getElementById('notifications');
- container.innerHTML = '<h2>通知</h2>';
-
- notifications.forEach(notify => {
- const div = document.createElement('div');
- div.className = 'notification';
- div.innerHTML = `
- <strong>${notify.process_title || '系统通知'}</strong>
- <p>${notify.message}</p>
- <small>${new Date(notify.created_at).toLocaleString()}</small>
- `;
- container.appendChild(div);
- });
- }
- // 附件预览
- document.querySelector('[name="attachments"]').addEventListener('change', (e) => {
- const preview = document.getElementById('attachmentPreview');
- preview.innerHTML = '';
-
- Array.from(e.target.files).forEach(file => {
- const span = document.createElement('span');
- span.className = 'attachment-item';
- span.textContent = file.name;
- preview.appendChild(span);
- });
- });
- // 撤回申请
- async function withdrawProcess(processId) {
- if (!confirm('确定要撤回申请吗?')) return;
-
- const response = await fetch(`/api/process/${processId}/withdraw`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
-
- const result = await response.json();
- alert(result.message || result.error);
- }
- </script>
- </body>
- </html>
- */
复制代码
功能说明:
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 小助理,如未能正确解答您的问题,请继续追问。 |
|