物品清单工具的实现笔记


搬到招待所住了,大包小包的,乱乱的,这周日按箱子整理一下,舒服了,顺带记录下这些物品

新建一个页面 box-inventory

  • 搬到招待所住了,大包小包的,乱乱的,这周日按箱子整理一下,舒服了,顺带记录下这些物品。
  • PHP实现的,做这些小工具小页面还是挺好用的。

链接

http://boxes.mxzfun.xyz

项目代码

https://github.com/yosoroQ/box-inventory/

页面展示

桌面端

移动端

物品清单工具核心架构与功能实现

采用 PHP + JSON + 原生 JavaScript 的极简三层结构:

  • 数据层data.json 存储所有物品(数组对象,字段:id, box, location, item, item2, count, unit
  • 服务层index.php 读取 JSON,通过 json_encode 注入到前端全局变量 window.itemsData
  • 表现层style.css + app.js 完成界面渲染与全部交互逻辑

核心优势:无数据库,无框架,数据与逻辑分离,增删改只需编辑 data.json


一、整体架构

1. 数据层(data.json)

[
  { "id": 1, "box": "1箱", "location": "(柜子一层,左侧)", "item": "冬装", "item2": "", "count": 12, "unit": "件" },
  ...
]

2. 服务层(index.php)

<?php
$jsonFile = __DIR__ . '/data.json';
$data = json_decode(file_get_contents($jsonFile), true);
?>
<script>
    window.itemsData = <?php echo json_encode($data, JSON_UNESCAPED_UNICODE); ?>;
</script>

3. 表现层(app.js + style.css)

  • app.js:分页、筛选、搜索、导出、渲染全部逻辑
  • style.css:深色表头、响应式、交互反馈样式

二、数据注入(PHP → JS)

<script>
    window.itemsData = <?php echo json_encode($data, JSON_UNESCAPED_UNICODE); ?>;
</script>
  • 避免 AJAX 请求造成的 CORS 问题
  • 页面加载完成即可直接使用 window.itemsData
  • 数据在服务端完成序列化,前端零等待

三、分页机制(按箱号)

核心变量currentBox(当前选中的箱号,'all' 表示全部)

实现步骤

  1. 提取箱号列表
    getUniqueValues(allData, 'box'),使用 Set 去重并按中文排序。
  2. 渲染导航按钮
    生成 <button class="page-btn" data-box="...">,默认“全部”激活。
  3. 切换逻辑

    • 点击按钮 → setCurrentBox(box)
    • 更新所有按钮的 .active
    • 重置位置下拉为 'all'
    • 调用 updateLocationOptions() 动态更新位置选项
    • 触发 applyFiltersAndRender() 重新渲染
function setCurrentBox(box) {
    currentBox = box;
    document.querySelectorAll('.page-btn').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.box === box);
    });
    filterLocation.value = 'all';
    updateLocationOptions();
}

四、双重筛选(位置 + 搜索)

采用链式过滤,顺序:箱号 → 位置 → 搜索关键词。

function applyFiltersAndRender() {
    let base = allData;
    if (currentBox !== 'all') base = base.filter(d => d.box === currentBox);

    const locVal = filterLocation.value;
    if (locVal !== 'all') base = base.filter(d => d.location === locVal);

    const keyword = searchInput.value.trim().toLowerCase();
    if (keyword) {
        base = base.filter(d => {
            const searchStr = [
                d.box, d.location, d.item, d.item2, d.unit, String(d.count)
            ].join(' ').toLowerCase();
            return searchStr.includes(keyword);
        });
    }

    filteredData = base;
    renderTable(filteredData);
}

关键点

  • 位置下拉动态更新:每次切换箱号时,根据当前箱号的数据重新生成位置列表,保证选项不冗余。
  • 搜索实时响应:通过 input 事件触发过滤。

五、导出 CSV

点击“📥 CSV”按钮执行 exportCsv(),流程如下:

  1. 数据检查
    filteredData 为空则弹窗提示。
  2. 构建内容

    • 表头:['序号','箱号','位置','物品','物品2','个数','单位']
    • 每行映射为数组,空 item2 处理为 ''
  3. 字段转义(处理逗号、双引号、换行)

    function escapeCsvField(field) {
        if (field == null) return '';
        const str = String(field);
        if (str.includes(',') || str.includes('"') || str.includes('\n')) {
            return '"' + str.replace(/"/g, '""') + '"';
        }
        return str;
    }
  4. 生成文件

    • 使用 Blob 添加 BOM(\uFEFF)确保 Excel 识别 UTF-8
    • 动态创建 <a> 标签,设置 download物品清单_YYYY-MM-DD.csv
    • 触发点击下载,释放 URL.createObjectURL
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `物品清单_${dateStr}.csv`;
link.click();
URL.revokeObjectURL(link.href);

六、表格渲染优化

renderTable(data) 采用一次构建整体 HTML的方式,避免多次 DOM 操作:

let html = '';
for (const d of data) {
    const item2Display = d.item2 ? escapeHtml(d.item2) : `<span class="empty-item2">—</span>`;
    html += `<tr>...</tr>`;
}
tbody.innerHTML = html;
  • 空状态展示友好提示(📭)。
  • 所有文本使用 escapeHtml() 防止 XSS。

七、响应式与样式关键点

  • 表头固定position: sticky; top: 0; z-index: 5; 配合深色背景 #2c3e50,白色文字。
  • 行悬停background: #ecf3fa; 提升交互反馈。
  • 导出按钮:独立绿色 #2c7a5f,区别于其他控件。
  • 移动端适配:通过 @media 查询在 768px 和 480px 断点调整字体、内边距、按钮尺寸。

八、事件绑定与初始化

事件绑定

filterLocation.addEventListener('change', applyFiltersAndRender);
searchInput.addEventListener('input', applyFiltersAndRender);
clearSearch.addEventListener('click', () => {
    searchInput.value = '';
    applyFiltersAndRender();
    searchInput.focus();
});
exportBtn.addEventListener('click', exportCsv);

初始化流程

  1. 设置 totalCountEl.textContent
  2. 渲染分页导航 renderPagination()
  3. 填充位置下拉 updateLocationOptions()
  4. 绑定事件
  5. 执行首次渲染 applyFiltersAndRender()

总结

整个工具的核心在于 服务端只负责数据注入,所有交互逻辑由原生 JS 完成,通过链式过滤实现多维度筛选,CSV 导出基于 Blob 实现零依赖下载,代码量小且性能良好。

封面

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 物品清单工具的实现笔记


三二一的一的二