用前端(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}`);
});
posted @ 2025-09-23 15:19  小神龙_007  阅读(15)  评论(0)    收藏  举报