用前端(HTML+Node.js)实现物品借用登记:完整代码示例
在日常工作中,经常需要借用各种办公或实验设备、工具及耗材。为了方便管理借用记录、防止物品丢失,同时提高办公效率,我设计了一个前端小程序——物品借用登记系统。该系统支持记录借用人、物品名称、数量、借用说明和日期,并可实时显示借用状态,同时提供归还和删除操作。整个程序基于 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}`);
});

浙公网安备 33010602011771号