315 lines
8.0 KiB
JavaScript
315 lines
8.0 KiB
JavaScript
/**
|
|
* 点名系统后端API服务器
|
|
* 使用Node.js原生HTTP模块实现
|
|
*/
|
|
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const url = require('url');
|
|
|
|
// 数据存储(实际应用中应使用数据库)
|
|
let rollCallLists = [];
|
|
let rollCallRecords = [];
|
|
|
|
// 从文件加载数据
|
|
function loadData() {
|
|
try {
|
|
const listsData = fs.readFileSync('data_lists.json', 'utf8');
|
|
rollCallLists = JSON.parse(listsData);
|
|
} catch (e) {
|
|
rollCallLists = [];
|
|
}
|
|
|
|
try {
|
|
const recordsData = fs.readFileSync('data_records.json', 'utf8');
|
|
rollCallRecords = JSON.parse(recordsData);
|
|
} catch (e) {
|
|
rollCallRecords = [];
|
|
}
|
|
}
|
|
|
|
// 保存数据到文件
|
|
function saveData() {
|
|
fs.writeFileSync('data_lists.json', JSON.stringify(rollCallLists, null, 2));
|
|
fs.writeFileSync('data_records.json', JSON.stringify(rollCallRecords, null, 2));
|
|
}
|
|
|
|
// 生成随机编号
|
|
function generateCode() {
|
|
const length = Math.floor(Math.random() * 7) + 6; // 6-12位
|
|
let code = '';
|
|
for (let i = 0; i < length; i++) {
|
|
code += Math.floor(Math.random() * 10);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
// 创建HTTP服务器
|
|
const server = http.createServer((req, res) => {
|
|
// 设置CORS头
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
|
|
// 处理OPTIONS请求
|
|
if (req.method === 'OPTIONS') {
|
|
res.writeHead(200);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
const parsedUrl = url.parse(req.url, true);
|
|
const pathname = parsedUrl.pathname;
|
|
|
|
// API路由
|
|
if (pathname.startsWith('/api/')) {
|
|
handleAPI(req, res, pathname, parsedUrl.query);
|
|
return;
|
|
}
|
|
|
|
// 静态文件服务
|
|
serveStatic(req, res, pathname);
|
|
});
|
|
|
|
// 处理API请求
|
|
function handleAPI(req, res, pathname, query) {
|
|
let body = '';
|
|
|
|
req.on('data', chunk => {
|
|
body += chunk.toString();
|
|
});
|
|
|
|
req.on('end', () => {
|
|
let data = {};
|
|
if (body) {
|
|
try {
|
|
data = JSON.parse(body);
|
|
} catch (e) {
|
|
sendError(res, '无效的JSON数据');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 路由处理
|
|
switch (pathname) {
|
|
case '/api/login':
|
|
handleLogin(res, data);
|
|
break;
|
|
case '/api/create-list':
|
|
handleCreateList(res, data);
|
|
break;
|
|
case '/api/get-lists':
|
|
handleGetLists(res);
|
|
break;
|
|
case '/api/get-list':
|
|
handleGetList(res, query);
|
|
break;
|
|
case '/api/update-list':
|
|
handleUpdateList(res, data);
|
|
break;
|
|
case '/api/delete-list':
|
|
handleDeleteList(res, data);
|
|
break;
|
|
case '/api/roll-call':
|
|
handleRollCall(res, data);
|
|
break;
|
|
case '/api/get-records':
|
|
handleGetRecords(res);
|
|
break;
|
|
default:
|
|
sendError(res, '未知的API路径', 404);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 登录处理
|
|
function handleLogin(res, data) {
|
|
const { username, password } = data;
|
|
|
|
if (username === 'admin' && password === 'admin123') {
|
|
sendSuccess(res, { token: 'admin-token-' + Date.now() });
|
|
} else {
|
|
sendError(res, '用户名或密码错误', 401);
|
|
}
|
|
}
|
|
|
|
// 创建点名名单
|
|
function handleCreateList(res, data) {
|
|
const { name, members } = data;
|
|
|
|
if (!name || !members || !Array.isArray(members)) {
|
|
sendError(res, '参数错误');
|
|
return;
|
|
}
|
|
|
|
const newList = {
|
|
id: Date.now(),
|
|
name: name,
|
|
code: generateCode(),
|
|
members: members.map(memberName => ({
|
|
name: memberName,
|
|
completed: false,
|
|
timestamp: null
|
|
})),
|
|
createdAt: new Date().toLocaleString()
|
|
};
|
|
|
|
rollCallLists.push(newList);
|
|
saveData();
|
|
|
|
sendSuccess(res, newList);
|
|
}
|
|
|
|
// 获取所有名单
|
|
function handleGetLists(res) {
|
|
sendSuccess(res, rollCallLists);
|
|
}
|
|
|
|
// 获取单个名单
|
|
function handleGetList(res, query) {
|
|
const code = query.code;
|
|
const list = rollCallLists.find(l => l.code === code);
|
|
|
|
if (list) {
|
|
sendSuccess(res, list);
|
|
} else {
|
|
sendError(res, '名单不存在', 404);
|
|
}
|
|
}
|
|
|
|
// 更新名单
|
|
function handleUpdateList(res, data) {
|
|
const { id, name, members } = data;
|
|
const list = rollCallLists.find(l => l.id === id);
|
|
|
|
if (!list) {
|
|
sendError(res, '名单不存在', 404);
|
|
return;
|
|
}
|
|
|
|
if (name) list.name = name;
|
|
if (members && Array.isArray(members)) {
|
|
list.members = members.map(memberName => {
|
|
const existing = list.members.find(m => m.name === memberName);
|
|
return existing || { name: memberName, completed: false, timestamp: null };
|
|
});
|
|
}
|
|
|
|
saveData();
|
|
sendSuccess(res, list);
|
|
}
|
|
|
|
// 删除名单
|
|
function handleDeleteList(res, data) {
|
|
const { id } = data;
|
|
const index = rollCallLists.findIndex(l => l.id === id);
|
|
|
|
if (index === -1) {
|
|
sendError(res, '名单不存在', 404);
|
|
return;
|
|
}
|
|
|
|
rollCallLists.splice(index, 1);
|
|
saveData();
|
|
sendSuccess(res, { message: '删除成功' });
|
|
}
|
|
|
|
// 点名
|
|
function handleRollCall(res, data) {
|
|
const { code, name } = data;
|
|
const list = rollCallLists.find(l => l.code === code);
|
|
|
|
if (!list) {
|
|
sendError(res, '名单不存在', 404);
|
|
return;
|
|
}
|
|
|
|
const member = list.members.find(m => m.name === name);
|
|
if (!member) {
|
|
sendError(res, '成员不存在', 404);
|
|
return;
|
|
}
|
|
|
|
if (member.completed) {
|
|
sendError(res, '该成员已点名');
|
|
return;
|
|
}
|
|
|
|
member.completed = true;
|
|
member.timestamp = new Date().toLocaleString();
|
|
|
|
// 添加记录
|
|
rollCallRecords.push({
|
|
name: name,
|
|
listName: list.name,
|
|
code: code,
|
|
timestamp: member.timestamp
|
|
});
|
|
|
|
saveData();
|
|
sendSuccess(res, { message: '点名成功', member: member });
|
|
}
|
|
|
|
// 获取点名记录
|
|
function handleGetRecords(res) {
|
|
sendSuccess(res, rollCallRecords);
|
|
}
|
|
|
|
// 静态文件服务
|
|
function serveStatic(req, res, pathname) {
|
|
let filePath = pathname === '/' ? '/index.html' : pathname;
|
|
filePath = path.join(__dirname, filePath);
|
|
|
|
const extname = path.extname(filePath);
|
|
const contentTypes = {
|
|
'.html': 'text/html',
|
|
'.js': 'text/javascript',
|
|
'.css': 'text/css',
|
|
'.json': 'application/json',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.gif': 'image/gif'
|
|
};
|
|
|
|
const contentType = contentTypes[extname] || 'text/plain';
|
|
|
|
fs.readFile(filePath, (err, content) => {
|
|
if (err) {
|
|
if (err.code === 'ENOENT') {
|
|
res.writeHead(404);
|
|
res.end('文件未找到');
|
|
} else {
|
|
res.writeHead(500);
|
|
res.end('服务器错误');
|
|
}
|
|
} else {
|
|
res.writeHead(200, { 'Content-Type': contentType });
|
|
res.end(content);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 发送成功响应
|
|
function sendSuccess(res, data) {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ success: true, data: data }));
|
|
}
|
|
|
|
// 发送错误响应
|
|
function sendError(res, message, code = 400) {
|
|
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ success: false, error: message }));
|
|
}
|
|
|
|
// 启动服务器
|
|
const PORT = 3000;
|
|
loadData();
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`点名系统服务器已启动`);
|
|
console.log(`访问地址: http://localhost:${PORT}`);
|
|
console.log(`后台管理: http://localhost:${PORT}/admin.html`);
|
|
console.log(`用户点名: http://localhost:${PORT}/index.html`);
|
|
});
|