当前位置: 首页 > news >正文

用前端(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}`);
});
http://www.hskmm.com/?act=detail&tid=14723

相关文章:

  • Google智能体Jules小试牛刀
  • 搞笑椅子机房语录
  • 在AI技术快速实现创意的时代,挖掘渗透测试框架新需求成为关键挑战
  • 基于区域的空间域图像融合MATLAB实现
  • Qt - 音视频采集
  • 梳理 | 脑神经科学原理学习资料整理
  • 如何做有效的Bug管理?
  • 2025年9月16日纸质证书 - 高同学PostgreSQL管理员(中级)认证
  • 智能体重电子秤解决方案:开发时注意事项
  • 一套开源、美观、高性能的跨平台 .NET MAUI 控件库,助力轻松构建美观且功能丰富的应用程序!
  • 2025年9月16日纸质证书 - 张同学PostgreSQL管理员(中级)认证
  • SQL统计:统计TEMP表空间的脚本
  • 读书笔记:Oracle索引必知必会:避开这些坑,让你的数据库飞起来
  • 三维CT图像重建算法
  • ROS2之话题
  • App 代上架全流程解析 iOS 应用代上架服务、苹果应用发布步骤、ipa 文件上传与 App Store 审核经验
  • 详细介绍:Transformer学习记录与CNN思考
  • 华清远见携STM32全矩阵产品及创新机器狗亮相2025 STM32研讨会,共启嵌入式技术探索新程
  • MySQL与Redis面试问题详解 - 详解
  • 代数几何: 1. 结构,2. “函子”观点 , 3. 测试
  • AT_agc023_f [AGC023F] 01 on Tree
  • 智慧医疗的新基建:视频融合平台EasyCVR在医疗场景中的深度应用解析
  • 书虫私藏的免费阅读渠道大公开!
  • 智能工厂革命:Gitee PPM如何重塑企业级软件开发新范式
  • PyTorch图神经网络(三)
  • 2025年9月16日纸质证书 - 宋同学PostgreSQL管理员(中级)认证
  • C# 18天 029 依赖注入
  • ruoyi-vue列表显示关联
  • 自定义网关选择后端的微服务实例实现
  • VUE3切换页面时,页面没有加载