在日常工作中,经常需要借用各种办公或实验设备、工具及耗材。为了方便管理借用记录、防止物品丢失,同时提高办公效率,我设计了一个前端小程序——物品借用登记系统。该系统支持记录借用人、物品名称、数量、借用说明和日期,并可实时显示借用状态,同时提供归还和删除操作。整个程序基于 HTML、CSS 和 JavaScript 实现,无需复杂环境配置,即可在内部网页中展示使用效果,非常适合研发团队内部管理和演示。
页面结构与样式
HTML 文档声明与语言设置
使用 HTML5,并设置 lang="zh",保证中文显示正常。
头部信息 ()
字符编码 UTF-8,设置页面标题。
内嵌 CSS 样式:
页面整体居中、最大宽度 1080px、字体为 Arial。
表单和表格设计:背景色、圆角、间距、边框及文字对齐。
表格列宽固定、文字换行处理,日期输入框和按钮样式优化。
页面主体内容
标题
居中显示“研发部物品借用登记表”,突出页面用途。
导航按钮
提供“查看未归还记录”和“查看已归还记录”的快速跳转,便于数据筛选。
借用表单
输入项包括:借用人、物品名称、数量、借用说明、借用日期。
借用日期默认显示当前日期。
提交按钮居中,点击提交触发表单逻辑。
记录展示表格
表头列:借用人、物品、数量、借用说明、借用日期、归还日期、状态、操作。
表格主体动态生成,每条记录可执行操作:
归还:未归还记录可选择归还日期并标记已归还。
删除:需要管理员密码确认后才能删除。
前端 JavaScript 功能
初始化
页面加载时自动将借用日期设置为当前日期。
加载记录 (loadRecords)
向后端请求记录数据。
根据状态显示不同操作控件:归还按钮或只显示归还日期。
数据倒序显示,最新记录在前。
删除记录 (deleteRecord)
弹出密码提示,验证后再确认删除。
删除操作通过 Fetch API 请求后端接口。
归还操作 (returnItem)
选择归还日期,发送请求更新记录状态为“已归还”。
表单提交
验证必填信息完整性。
构建记录对象,初始状态为“未归还”,发送到后端。
提交成功后清空表单并刷新记录列表。
数据与状态管理
记录对象结构
name:借用人
item:物品名称
quantity:数量
remark:借用说明
borrowDate:借用日期
returnDate:归还日期
status:借用状态(未归还/已归还)
前端索引
每条记录会添加 _originalIndex 属性,用于操作对应后端记录。
交互逻辑
所有操作(提交、归还、删除)均通过 Fetch API 与后端通信。
页面实时更新,保证数据一致性与操作安全性。
总结
这个程序实现了借用登记、归还管理和数据展示的全流程功能,界面简洁、操作直观,具有以下特点:
易用性:表单简洁,操作逻辑清晰。
实时性:操作后自动刷新列表,无需手动刷新页面。
安全性:删除记录需要管理员密码确认,避免误操作。
前端实现:完全基于 HTML、CSS 和 JavaScript,无需额外依赖,适合内部网页或博客展示。
总结:该系统可作为研发部门日常物品借用管理的轻量级解决方案,可以部署在Windows系统内,进行局域网管理统计登记。
主页面代码如下:
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8" /><title>物品借用登记</title><style>body { font-family: Arial, sans-serif; padding: 20px; max-width: 1080px; margin: auto; }h2 { text-align: center; margin-bottom: 20px; }form { margin-bottom: 20px; background: #f5f5f5; padding: 15px; border-radius: 8px; }.form-group { display: flex; flex-direction: column; margin-bottom: 12px; }label { margin-bottom: 6px; font-weight: bold; }input, button { padding: 8px; font-size: 14px; }button { margin-top: 10px; cursor: pointer; }table { border-collapse: collapse; width: 100%; table-layout: fixed;}th, td { border: 1px solid #ccc;padding: 8px;text-align: center;white-space: normal; word-break: break-word; }th { background-color: #f0f0f0;}th:nth-child(1), td:nth-child(1) { width: 60px; }th:nth-child(2), td:nth-child(2) { width: 200px; }th:nth-child(3), td:nth-child(3) { width: 40px; } th:nth-child(4), td:nth-child(4) { width: 300px; text-align:left; } input[type="date"] { width: 65%; }.action-btn { margin: 2px; }</style>
</head>
<body><h2>研发部物品借用登记表</h2><!-- 查看未归还/已归还按钮 --><div style="text-align:center; margin-bottom: 20px;"><button onclick="window.location.href='not-returned.html'">查看未归还记录</button><button onclick="window.location.href='returned.html'">查看已归还记录</button></div><form id="borrowForm"><div class="form-group"><label for="name">借用人</label><input type="text" id="name" required /></div><div class="form-group"><label for="item">物品名称</label><input type="text" id="item" required /></div><div class="form-group"><label for="quantity">数量</label><input type="number" id="quantity" min="1" required /></div><div class="form-group"><label for="remark">借用说明</label><input type="text" id="remark" /></div><div class="form-group"><label for="borrowDate">借用日期</label><input type="date" id="borrowDate" style="display:block;margin:0 auto;width:20%; text-align:center;" required /></div><button type="submit" style="display:block;margin:0 auto">提交</button></form><table id="recordTable"><thead><tr><th>借用人</th><th>物品</th><th>数量</th><th>借用说明</th><th>借用日期</th><th>归还日期</th><th>状态</th><th>操作</th></tr></thead><tbody></tbody></table><script>const form = document.getElementById('borrowForm');const tableBody = document.querySelector('#recordTable tbody');document.getElementById('borrowDate').valueAsDate = new Date();function loadRecords() {fetch('/api/records').then(res => res.json()).then(data => {tableBody.innerHTML = '';// 给每条记录添加原数组索引data.forEach((record, idx) => { record._originalIndex = idx; });// 倒序显示data.slice().reverse().forEach((record, index) => {const row = document.createElement('tr');let returnDateInput = '';let returnButton = '';if (record.status === '已归还') {returnDateInput = record.returnDate || '';} else {returnDateInput = `<input type="date" id="returnDate-${index}" />`;returnButton = `<button class="action-btn" onclick="returnItem(${record._originalIndex}, this)">归还</button>`;}row.innerHTML = `<td>${record.name}</td><td>${record.item}</td><td>${record.quantity}</td><td>${record.remark || ''}</td><td>${record.borrowDate}</td><td>${returnDateInput}</td><td>${record.status || '未归还'}</td><td>${returnButton}<button class="action-btn" onclick="deleteRecord(${record._originalIndex})">删除</button></td>`;tableBody.appendChild(row);});}).catch(err => alert('加载数据失败: ' + err.message));}function deleteRecord(index) {const password = prompt('请输入管理员密码进行删除:');if (password === null) return;const correctPassword = '19980216';if (password !== correctPassword) { alert('密码错误'); return; }if (!confirm('确定删除这条记录吗?')) return;fetch(`/api/records/${index}`, { method: 'DELETE' }).then(() => loadRecords()).catch(() => alert('删除失败'));}function returnItem(index, button) {const dateInput = document.getElementById(`returnDate-${index}`);const returnDate = dateInput.value;if (!returnDate) { alert('请选择归还日期'); return; }button.disabled = true;fetch(`/api/records/${index}/return`, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ returnDate })}).then(() => loadRecords()).catch(() => { alert('归还失败'); button.disabled = false; });}form.addEventListener('submit', e => {e.preventDefault();const name = document.getElementById('name').value.trim();const item = document.getElementById('item').value.trim();const remark = document.getElementById('remark').value.trim();const quantity = parseInt(document.getElementById('quantity').value);const borrowDate = document.getElementById('borrowDate').value;if (!name || !item || !quantity || !borrowDate) {alert('请填写完整信息');return;}const record = { name, item, quantity, remark, borrowDate, returnDate:'', status:'未归还' };fetch('/api/records', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(record) }).then(() => { form.reset(); document.getElementById('borrowDate').valueAsDate = new Date(); loadRecords(); }).catch(() => alert('提交失败'));});loadRecords();</script>
</body>
</html>
未归还记录页面如下
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>未归还记录</title><style>body { font-family: Arial, sans-serif; padding: 20px; max-width: 1080px; margin: auto; }h2 { text-align: center; margin-bottom: 20px; }table { border-collapse: collapse; width: 100%; table-layout: fixed; background: #fff;}th, td { border: 1px solid #ccc;padding: 8px;text-align: center;white-space: normal;word-break: break-word; }th { background-color: #f0f0f0; }th:nth-child(1), td:nth-child(1) { width: 60px; }th:nth-child(2), td:nth-child(2) { width: 200px; }th:nth-child(3), td:nth-child(3) { width: 40px; }th:nth-child(4), td:nth-child(4) { width: 300px; text-align:left; }th:nth-child(5), td:nth-child(5) { width: 100px; }th:nth-child(6), td:nth-child(6) { width: 100px; }button { padding: 4px 8px; cursor: pointer; }.action-btn { margin: 2px; }.center-btn { display:block; margin:0 auto; }</style>
</head>
<body><h2>未归还记录</h2><div style="text-align:center; margin-bottom: 20px;"><button onclick="window.location.href='index.html'">返回主页面</button></div><table id="recordTable"><thead><tr><th>借用人</th><th>物品</th><th>数量</th><th>借用说明</th><th>借用日期</th><th>归还日期</th><th>状态</th><th>操作</th></tr></thead><tbody></tbody></table><script>const tableBody = document.querySelector('#recordTable tbody');function loadRecords() {fetch('/api/records').then(res => res.json()).then(data => {tableBody.innerHTML = '';const notReturned = data.filter(record => record.status === '未归还');notReturned.forEach((record, index) => {const row = document.createElement('tr');row.innerHTML = `<td>${record.name}</td><td>${record.item}</td><td>${record.quantity}</td><td>${record.remark || ''}</td><td>${record.borrowDate}</td><td><input type="date" id="returnDate-${index}" /></td><td>${record.status}</td><td><button class="action-btn" onclick="returnItem(${index}, '${record.name}', '${record.item}')">归还</button></td>`;tableBody.appendChild(row);});}).catch(err => alert('加载数据失败: ' + err.message));}function returnItem(index, name, item) {const dateInput = document.getElementById(`returnDate-${index}`);const returnDate = dateInput.value;if (!returnDate) { alert('请选择归还日期'); return; }// 获取完整记录索引fetch('/api/records').then(res => res.json()).then(data => {const recordIndex = data.findIndex(r => r.name === name && r.item === item && r.status === '未归还');if (recordIndex === -1) { alert('未找到记录'); return; }fetch(`/api/records/${recordIndex}/return`, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ returnDate })}).then(res => res.json()).then(() => loadRecords()).catch(() => { alert('归还失败'); });});}loadRecords();</script>
</body>
</html>
已归还页面代码如下:
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8" /><title>已归还记录</title><style>body { font-family: Arial, sans-serif; padding: 20px; max-width: 1080px; margin: auto; }h2 { text-align: center; margin-bottom: 20px; }table { border-collapse: collapse; width: 100%; table-layout: fixed;}th, td { border: 1px solid #ccc;padding: 8px;text-align: center;white-space: normal;word-break: break-word; }th { background-color: #f0f0f0;}th:nth-child(1), td:nth-child(1) {width: 60px;}th:nth-child(2), td:nth-child(2) {width: 200px;}th:nth-child(3), td:nth-child(3) {width: 40px;} th:nth-child(4), td:nth-child(4) {width: 300px;text-align:left;} </style>
</head>
<body><h2>已归还记录</h2><div style="text-align:center; margin-bottom: 20px;"><button onclick="window.location.href='index.html'">返回主页面</button></div><table id="recordTable"><thead><tr><th>借用人</th><th>物品</th><th>数量</th><th>借用说明</th><th>借用日期</th><th>归还日期</th><th>状态</th></tr></thead><tbody></tbody></table><script>const tableBody = document.querySelector('#recordTable tbody');function loadRecords() {fetch('/api/records').then(res => res.json()).then(data => {tableBody.innerHTML = '';const returned = data.filter(record => record.status === '已归还');returned.forEach(record => {const row = document.createElement('tr');row.innerHTML = `<td>${record.name}</td><td>${record.item}</td><td>${record.quantity}</td><td>${record.remark || ''}</td><td>${record.borrowDate}</td><td>${record.returnDate || ''}</td><td>${record.status}</td>`;tableBody.appendChild(row);});}).catch(err => alert('加载数据失败: ' + err.message));}loadRecords();</script>
</body>
</html>
Express 框架搭建nodejs服务后端代码
const express = require('express');
const fs = require('fs');
const path = require('path');const app = express();
const PORT = 3000;
const DATA_FILE = path.join(__dirname, 'data.json');app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));// 读取数据
function readDataFile() {if (!fs.existsSync(DATA_FILE)) return [];try {const data = fs.readFileSync(DATA_FILE, 'utf-8').trim();if (!data) return []; // 如果文件为空,返回空数组return JSON.parse(data);} catch (err) {console.error('读取文件出错:', err);return [];}
}// 写入数据
function writeDataFile(data) {fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2), 'utf-8');
}// 获取所有记录
app.get('/api/records', (req, res) => {res.json(readDataFile());
});// 添加记录
app.post('/api/records', (req, res) => {const { name, item, quantity, borrowDate, remark } = req.body;if (!name || !item || !borrowDate) {return res.status(400).json({ error: '缺少字段' });}const records = readDataFile();records.push({name,item,quantity: quantity || 1,borrowDate,remark: remark || '',returnDate: '',status: '未归还'});writeDataFile(records);res.json({ status: 'success' });
});// 删除记录
app.delete('/api/records/:index', (req, res) => {const index = parseInt(req.params.index);const records = readDataFile();if (isNaN(index) || index < 0 || index >= records.length) {return res.status(400).json({ error: '索引无效' });}records.splice(index, 1);writeDataFile(records);res.json({ status: 'deleted' });
});// 归还记录
app.post('/api/records/:index/return', (req, res) => {const index = parseInt(req.params.index);const { returnDate } = req.body;const records = readDataFile();if (!returnDate || isNaN(index) || index < 0 || index >= records.length) {return res.status(400).json({ error: '无效请求' });}records[index].returnDate = returnDate;records[index].status = '已归还';writeDataFile(records);res.json({ status: 'updated' });
});app.listen(PORT, () => {console.log(`Server running at http://localhost:${PORT}`);
});