技术培训档案软件:从零搭建到部署的完整实操指南

一、核心需求分析与技术选型

技术培训档案软件需要解决三个核心问题:培训记录结构化存储、多维度检索统计、权限分级管理。基于这些需求,我们选择以下技术栈:

  • 后端:Node.js + Express,轻量快速,适合中小型应用
  • 数据库:MySQL 8.0,关系型数据更适合结构化档案
  • 前端:Vue 3 + Element Plus,组件化开发效率高
  • 文件存储:本地文件系统(初期),后期可扩展至云存储

二、开发环境准备

2.1 基础环境安装

首先安装Node.js 18.x LTS版本:

``` Windows用户从官网下载安装包 https://nodejs.org/dist/v18.17.0/node-v18.17.0-x64.msi macOS用户使用Homebrew安装 brew install node@18 Linux用户(Ubuntu/Debian) curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs ```

安装完成后验证版本:

``` node --version 应显示v18.x.x npm --version 应显示9.x.x或更高 ```

2.2 数据库安装与配置

安装MySQL 8.0:

``` Windows下载安装包 https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.33-winx64.msi macOS使用Homebrew brew install mysql@8.0 Ubuntu/Debian sudo apt install mysql-server-8.0 ```

安装后启动服务并创建数据库:

``` 启动MySQL服务 sudo systemctl start mysql Linux/macOS 或通过服务管理器启动(Windows) 登录MySQL mysql -u root -p 创建数据库和用户 CREATE DATABASE tech_training_archive DEFAULT CHARACTER SET utf8mb4; CREATE USER 'training_admin'@'localhost' IDENTIFIED BY 'YourSecurePassword123!'; GRANT ALL PRIVILEGES ON tech_training_archive. TO 'training_admin'@'localhost'; FLUSH PRIVILEGES; EXIT; ```

三、数据库表结构设计

3.1 核心表创建

执行以下SQL语句创建必要的数据表:

``` USE tech_training_archive; -- 培训课程表 CREATE TABLE training_courses ( id INT PRIMARY KEY AUTO_INCREMENT, course_code VARCHAR(20) UNIQUE NOT NULL, course_name VARCHAR(100) NOT NULL, category ENUM('技术', '管理', '软技能', '安全') NOT NULL, duration_hours DECIMAL(5,2) NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_category (category), INDEX idx_created (created_at) ); -- 员工培训记录表 CREATE TABLE employee_training ( id INT PRIMARY KEY AUTO_INCREMENT, employee_id VARCHAR(20) NOT NULL, employee_name VARCHAR(50) NOT NULL, department VARCHAR(50) NOT NULL, course_id INT NOT NULL, training_date DATE NOT NULL, trainer_name VARCHAR(50), score DECIMAL(5,2), certificate_path VARCHAR(255), status ENUM('已完成', '进行中', '未通过') DEFAULT '已完成', notes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (course_id) REFERENCES training_courses(id), INDEX idx_employee (employee_id), INDEX idx_department (department), INDEX idx_training_date (training_date), INDEX idx_status (status) ); -- 培训材料表 CREATE TABLE training_materials ( id INT PRIMARY KEY AUTO_INCREMENT, course_id INT NOT NULL, material_name VARCHAR(200) NOT NULL, file_type VARCHAR(20), file_size INT, storage_path VARCHAR(500) NOT NULL, uploader VARCHAR(50), uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (course_id) REFERENCES training_courses(id), INDEX idx_course (course_id) ); ```

四、后端服务搭建

4.1 项目初始化

创建项目目录并初始化:

``` mkdir tech-training-archive cd tech-training-archive npm init -y ```

4.2 安装依赖包

安装必要的依赖:

``` npm install express mysql2 cors dotenv multer npm install -D nodemon ```

4.3 项目结构创建

创建以下目录结构:

``` tech-training-archive/ ├── src/ │ ├── config/ │ │ └── database.js │ ├── controllers/ │ │ ├── courseController.js │ │ ├── trainingController.js │ │ └── materialController.js │ ├── routes/ │ │ ├── courseRoutes.js │ │ ├── trainingRoutes.js │ │ └── materialRoutes.js │ ├── middleware/ │ │ └── upload.js │ ├── utils/ │ │ └── logger.js │ └── app.js ├── uploads/ ├── .env ├── .gitignore └── package.json ```

4.4 配置文件设置

技术培训档案软件:从零搭建到部署的完整实操指南

创建.env文件并配置数据库连接:

``` DB_HOST=localhost DB_PORT=3306 DB_USER=training_admin DB_PASSWORD=YourSecurePassword123! DB_NAME=tech_training_archive SERVER_PORT=3000 UPLOAD_PATH=./uploads ```

创建config/database.js:

``` const mysql = require('mysql2/promise'); require('dotenv').config(); const pool = mysql.createPool({ host: process.env.DB_HOST, port: process.env.DB_PORT, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); module.exports = pool; ```

4.5 文件上传中间件

创建middleware/upload.js:

``` const multer = require('multer'); const path = require('path'); const fs = require('fs'); // 确保上传目录存在 const uploadDir = process.env.UPLOAD_PATH || './uploads'; if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } // 配置存储 const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, uploadDir); }, filename: function (req, file, cb) { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() 1E9); cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); } }); // 文件过滤 const fileFilter = (req, file, cb) => { const allowedTypes = ['.pdf', '.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx', '.jpg', '.png']; const ext = path.extname(file.originalname).toLowerCase(); if (allowedTypes.includes(ext)) { cb(null, true); } else { cb(new Error('不支持的文件类型'), false); } }; const upload = multer({ storage: storage, fileFilter: fileFilter, limits: { fileSize: 50 1024 1024 // 50MB限制 } }); module.exports = upload; ```

4.6 核心控制器实现

创建controllers/trainingController.js:

``` const pool = require('../config/database'); exports.addTrainingRecord = async (req, res) => { try { const { employee_id, employee_name, department, course_id, training_date, trainer_name, score, notes } = req.body; const [result] = await pool.execute( `INSERT INTO employee_training (employee_id, employee_name, department, course_id, training_date, trainer_name, score, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [employee_id, employee_name, department, course_id, training_date, trainer_name, score, notes] ); res.status(201).json({ success: true, data: { id: result.insertId } }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }; exports.getTrainingRecords = async (req, res) => { try { const { department, start_date, end_date, status } = req.query; let query = `SELECT et., tc.course_name, tc.category FROM employee_training et JOIN training_courses tc ON et.course_id = tc.id WHERE 1=1`; const params = []; if (department) { query += ' AND et.department = ?'; params.push(department); } if (start_date) { query += ' AND et.training_date >= ?'; params.push(start_date); } if (end_date) { query += ' AND et.training_date <= ?'; params.push(end_date); } if (status) { query += ' AND et.status = ?'; params.push(status); } query += ' ORDER BY et.training_date DESC'; const [rows] = await pool.execute(query, params); res.json({ success: true, data: rows }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }; exports.getStatistics = async (req, res) => { try { const [departmentStats] = await pool.execute( `SELECT department, COUNT() as training_count, AVG(score) as avg_score FROM employee_training WHERE status = '已完成' GROUP BY department` ); const [monthlyStats] = await pool.execute( `SELECT DATE_FORMAT(training_date, '%Y-%m') as month, COUNT() as training_count FROM employee_training WHERE training_date >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) GROUP BY DATE_FORMAT(training_date, '%Y-%m') ORDER BY month DESC` ); res.json({ success: true, data: { by_department: departmentStats, by_month: monthlyStats } }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }; ```

4.7 路由配置

创建routes/trainingRoutes.js:

``` const express = require('express'); const router = express.Router(); const trainingController = require('../controllers/trainingController'); // 培训记录管理 router.post('/records', trainingController.addTrainingRecord); router.get('/records', trainingController.getTrainingRecords); router.get('/records/:id', trainingController.getTrainingRecordById); router.put('/records/:id', trainingController.updateTrainingRecord); // 统计分析 router.get('/statistics/department', trainingController.getDepartmentStats); router.get('/statistics/monthly', trainingController.getMonthlyStats); router.get('/statistics/course', trainingController.getCourseStats); module.exports = router; ```

4.8 主应用文件

创建src/app.js:

``` const express = require('express'); const cors = require('cors'); require('dotenv').config(); const trainingRoutes = require('./routes/trainingRoutes'); const courseRoutes = require('./routes/courseRoutes'); const materialRoutes = require('./routes/materialRoutes'); const app = express(); // 中间件 app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 静态文件服务 app.use('/uploads', express.static('uploads')); // 路由 app.use('/api/training', trainingRoutes); app.use('/api/courses', courseRoutes); app.use('/api/materials', materialRoutes); // 健康检查 app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // 错误处理中间件 app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ success: false, error: process.env.NODE_ENV === 'development' ? err.message : '内部服务器错误' }); }); const PORT = process.env.SERVER_PORT || 3000; app.listen(PORT, () => { console.log(`服务器运行在 http://localhost:${PORT}`); }); ```

五、前端界面快速搭建

5.1 Vue项目初始化

在项目根目录同级创建前端项目:

``` 回到tech-training-archive的上级目录 cd .. vue create training-frontend 选择Vue 3 + TypeScript + Router cd training-frontend ```

5.2 安装UI库和依赖

安装Element Plus和Axios:

``` npm install element-plus @element-plus/icons-vue axios ```

5.3 配置Element Plus

修改src/main.ts:

``` import { createApp } from 'vue' import App from './App.vue' import router from './router' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import as ElementPlusIconsVue from '@element-plus/icons-vue' const app = createApp(App) // 注册所有图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(router) app.use(ElementPlus) app.mount('app') ```

5.4 创建培训记录管理组件

创建src/views/TrainingRecords.vue:

```