简介:这份供应链系统页面原型资源面向产品经理、UI设计师、前端开发者及供应链业务人员,用于在开发前期直观呈现采购、库存、生产计划、物流配送、销售分销、财务与报表分析等核心模块的界面布局与交互流程,帮助团队在需求沟通与方案评审阶段快速对齐认知。压缩包共299个文件,约661KB,以87个html页面为主体,辅以126个gif与43个png界面素材、14张jpg配图,以及少量js、css、scc样式脚本和3个xlsx数据表,覆盖登录、菜单、表单、图表等典型页面元素。目前已有1175人学习下载。通过逐页浏览原型,读者可梳理各模块的字段、按钮与导航关系,理解供应链流程如何串联,为后续视觉设计与功能开发提供可复用的参考蓝本,降低沟通与返工成本。
1. 供应链系统页面原型:从一堆静态 HTML 到能跑的业务骨架
很多做后台管理系统的开发者都遇到过这种场景:产品经理丢过来一句“先出个供应链系统的页面原型”,然后就没有然后了。没有设计稿,没有接口文档,甚至连字段定义都没对齐。这时候如果直接上框架写组件,大概率是白干——需求一变,组件全废。我一般会先用纯 HTML + CSS 把核心页面搭出来,不依赖任何构建工具,双击就能在浏览器里看效果。这份供应链系统页面原型就是干这个的:它把供应商管理、采购订单、库存看板、物流跟踪这几个高频模块的页面结构全部用静态 HTML 铺开,适合需要快速对齐需求的前端、需要给客户演示的产品,以及想学后台布局的新手。拿到手就能改,改完就能给业务方看,省掉大量来回扯皮的时间。
2. 技术选型与目录结构:为什么不用框架反而更合适
2.1 纯 HTML 方案的取舍逻辑
供应链系统的页面原型和普通官网不一样,它的核心诉求是“结构清晰、字段密集、表格多”。用 Vue 或 React 写当然可以,但引入构建工具之后,改一个字段名要等热更新,给非技术同事看还要教他们装环境。纯 HTML 方案的好处是零门槛:浏览器打开就能看,记事本就能改,发给谁都能直接跑。
常见做法是用一套轻量 CSS 框架打底,比如 Bootstrap 或者 Tailwind 的 CDN 版本,再配合少量自定义样式。这样既保证了表格、表单、卡片的视觉一致性,又不用自己从零写栅格。我一般会在<head>里直接引 CDN,不搞本地构建,保证文件拷到任何地方都能用。
<!-- 页面头部:引入 CDN 样式与图标库 --> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>供应链管理系统 - 采购订单</title> <!-- Bootstrap 负责栅格与基础组件样式 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> <!-- 图标库用于侧边栏与操作按钮 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> <!-- 自定义样式覆盖,放在最后保证优先级 --> <link rel="stylesheet" href="../css/custom.css"> </head>这段代码的关键点在于加载顺序:Bootstrap 先加载,自定义样式后加载,这样custom.css里的规则才能覆盖框架默认值。图标库单独引入是因为供应链系统里大量用到“编辑”“删除”“查看详情”这类操作按钮,纯文字太占地方。参数上,viewport那行不能省,否则在平板上看表格会挤成一团。
2.2 目录结构怎么划分才不乱
原型项目最怕的就是文件堆在一起,改了一个页面找不到对应的样式。我一般按“页面 + 资源”两个维度来分:
supply-chain-prototype/ ├── index.html # 登录页,入口 ├── pages/ │ ├── supplier-list.html # 供应商列表 │ ├── purchase-order.html # 采购订单管理 │ ├── inventory.html # 库存看板 │ └── logistics.html # 物流跟踪 ├── css/ │ ├── bootstrap.min.css # 框架样式(本地备份) │ └── custom.css # 项目自定义样式 ├── js/ │ ├── mock-data.js # 模拟数据,方便演示 │ └── common.js # 公共交互逻辑 └── assets/ └── logo.png # 系统 Logo这个结构的好处是页面和资源分离,pages/下每个文件对应一个业务模块,css/和js/放公共依赖。mock-data.js是我强烈建议保留的,因为原型阶段没有后端,表格里的数据全靠它撑场面。改数据不用动 HTML 结构,演示的时候直接改 JS 数组就行。
2.3 页面之间的跳转关系
原型不是孤立的页面,供应链系统的核心链路是“供应商 → 采购订单 → 库存 → 物流”。在supplier-list.html里点击某个供应商的“查看订单”,应该跳到purchase-order.html并带上供应商 ID。纯 HTML 方案里用 URL 参数传递:
// common.js:统一的页面跳转方法,携带业务参数 function navigateTo(page, params) { // 将参数对象拼接成 URL 查询字符串 const query = new URLSearchParams(params).toString(); // 跳转到目标页面,例如 purchase-order.html?supplierId=1001 window.location.href = `${page}?${query}`; } // 在供应商列表页调用 document.querySelectorAll('.btn-view-order').forEach(btn => { btn.addEventListener('click', () => { // 从按钮的 data 属性读取供应商 ID const supplierId = btn.dataset.supplierId; navigateTo('purchase-order.html', { supplierId }); }); });URLSearchParams会自动处理编码问题,比手动拼字符串靠谱。><!-- 供应商列表表格:支持横向滚动,避免字段过多时挤压 --> <div class="table-responsive"> <table class="table table-hover table-striped align-middle"> <thead class="table-dark"> <tr> <th scope="col">供应商编码</th> <th scope="col">供应商名称</th> <th scope="col">联系人</th> <th scope="col">联系电话</th> <th scope="col">合作状态</th> <th scope="col">最近订单</th> <th scope="col" class="text-center">操作</th> </tr> </thead> <tbody id="supplierTableBody"> <!-- 数据由 mock-data.js 动态渲染,保持 HTML 干净 --> </tbody> </table> </div>
table-responsive是必须加的,供应链系统的表格字段通常超过 8 列,不加这个在小屏幕上会溢出。align-middle让单元格内容垂直居中,视觉上更整齐。thead用table-dark深色背景,和内容区形成对比,方便快速定位表头。tbody留空,由 JS 动态填充,这样改数据不用动 HTML。
3.2 用 mock 数据渲染表格行
原型阶段没有后端,但表格不能空着。我一般会在mock-data.js里定义一个数组,字段名和表头一一对应,然后用map生成 HTML 字符串插入tbody。
// mock-data.js:供应商模拟数据,字段与表格列对应 const suppliers = [ { code: 'SUP-1001', name: '华东电子元件厂', contact: '张工', phone: '13800001111', status: '合作中', lastOrder: '2025-03-12' }, { code: 'SUP-1002', name: '南方物流包装有限公司', contact: '李经理', phone: '13900002222', status: '待审核', lastOrder: '—' }, { code: 'SUP-1003', name: '西部原材料供应站', contact: '王主管', phone: '13700003333', status: '已暂停', lastOrder: '2024-11-05' } ]; // 渲染供应商表格 function renderSupplierTable() { const tbody = document.getElementById('supplierTableBody'); if (!tbody) return; // 根据合作状态生成不同颜色的徽章 const statusBadge = (status) => { const map = { '合作中': 'success', '待审核': 'warning', '已暂停': 'secondary' }; return `<span class="badge bg-${map[status] || 'secondary'}">${status}</span>`; }; tbody.innerHTML = suppliers.map(s => ` <tr> <td>${s.code}</td> <td>${s.name}</td> <td>${s.contact}</td> <td>${s.phone}</td> <td>${statusBadge(s.status)}</td> <td>${s.lastOrder}</td> <td class="text-center"> <button class="btn btn-sm btn-outline-primary btn-view-order"><!-- 采购订单筛选栏:供应商下拉 + 状态筛选 + 日期范围 --> <div class="row g-3 mb-4"> <div class="col-md-3"> <label class="form-label">供应商</label> <select class="form-select" id="filterSupplier"> <option value="">全部供应商</option> <!-- 选项由 JS 从 mock 数据生成 --> </select> </div> <div class="col-md-3"> <label class="form-label">订单状态</label> <select class="form-select" id="filterStatus"> <option value="">全部状态</option> <option value="pending">待确认</option> <option value="shipped">已发货</option> <option value="received">已入库</option> </select> </div> <div class="col-md-4"> <label class="form-label">下单日期</label> <input type="date" class="form-control" id="filterDate"> </div> <div class="col-md-2 d-flex align-items-end"> <button class="btn btn-primary w-100" id="btnFilter">筛选</button> </div> </div>筛选栏用 Bootstrap 的栅格系统分成四列,g-3控制列间距。供应商下拉的选项不是写死的,而是从suppliers数组动态生成,这样新增供应商时不用改 HTML。筛选按钮绑定点击事件后,根据当前选中的条件过滤orders数组,重新渲染表格。原型阶段不需要真实请求,但交互逻辑要完整,否则演示时点按钮没反应会很尴尬。
// 根据筛选条件过滤订单并重新渲染 function filterOrders() { const supplierCode = document.getElementById('filterSupplier').value; const status = document.getElementById('filterStatus').value; const date = document.getElementById('filterDate').value; // 逐条件过滤,条件为空时跳过 const filtered = orders.filter(o => { if (supplierCode && o.supplierCode !== supplierCode) return false; if (status && o.status !== status) return false; if (date && o.orderDate !== date) return false; return true; }); renderOrderTable(filtered); } // 绑定筛选按钮事件 document.getElementById('btnFilter').addEventListener('click', filterOrders);filter里的三个if是典型的链式过滤写法,每个条件独立判断,互不影响。renderOrderTable接收过滤后的数组,内部逻辑和供应商表格类似,只是列不同。这种写法在原型阶段足够用,后期接真实接口时只需要把orders换成fetch返回的数据即可。
4. 避坑与常见问题:原型阶段最容易翻车的几个点
4.1 表格字段太多导致横向溢出
现象:在 1366px 宽度的笔记本上打开采购订单页,表格右侧的“操作”列被截断,按钮点不到。原因:字段超过 10 列时,即使加了table-responsive,如果容器宽度不够,仍然会出现滚动条不明显或者布局错乱。解决:给表格设置min-width,强制触发横向滚动,同时把不重要的列在原型阶段隐藏,只保留核心字段。
/* custom.css:表格最小宽度,保证字段不被挤压 */ .table-responsive table { min-width: 1200px; /* 根据实际字段数量调整 */ }4.2 动态渲染后事件绑定失效
现象:用innerHTML渲染表格后,之前用querySelectorAll绑定的按钮点击事件全部失效。原因:innerHTML会销毁原有 DOM 节点,重新创建新节点,旧的事件监听器自然不存在了。解决:要么在渲染完成后再绑定事件,要么用事件委托把监听器挂在tbody上。
// 事件委托:监听 tbody 上的点击,通过 target 判断来源 document.getElementById('supplierTableBody').addEventListener('click', (e) => { if (e.target.classList.contains('btn-view-order')) { const supplierId = e.target.dataset.supplierId; navigateTo('purchase-order.html', { supplierId }); } });4.3 日期格式不统一导致筛选失败
现象:筛选日期时选了2025-03-12,但订单数据里的日期是2025/03/12,导致过滤结果为空。原因:input type="date"返回的格式固定是YYYY-MM-DD,而 mock 数据里手写的日期格式不统一。解决:在mock-data.js里统一用YYYY-MM-DD格式,或者在过滤前做一次格式转换。
4.4 页面跳转后参数丢失
现象:从供应商列表跳到采购订单页,URL 里带了supplierId,但页面加载后筛选下拉没有选中对应供应商。原因:目标页面没有在DOMContentLoaded里读取 URL 参数并设置下拉框的值。解决:在目标页面的初始化逻辑里加一段参数回填。
// 采购订单页初始化:读取 URL 参数并回填筛选条件 document.addEventListener('DOMContentLoaded', () => { const params = new URLSearchParams(location.search); const supplierId = params.get('supplierId'); if (supplierId) { document.getElementById('filterSupplier').value = supplierId; filterOrders(); // 立即执行一次筛选 } });4.5 CDN 资源加载失败导致样式全丢
现象:把原型发给同事,对方打开后页面没有任何样式,表格变成纯文本堆叠。原因:对方网络环境访问不了 CDN,Bootstrap 和图标库加载失败。解决:把 CDN 文件下载到本地css/和js/目录,引用相对路径。原型项目体积不大,本地化之后拷 U 盘都能跑。
5. 二次开发与改造:从静态原型到可交互 Demo 的升级路径
5.1 把 mock 数据抽成独立 JSON 文件
原型跑通之后,下一步通常是让数据可配置。我一般会把mock-data.js里的数组抽成独立的.json文件,用fetch加载。这样做的好处是产品和业务方可以直接改 JSON 文件,不用碰 JS 代码。
// 从 JSON 文件加载供应商数据 async function loadSuppliers() { const res = await fetch('../data/suppliers.json'); if (!res.ok) throw new Error('数据加载失败'); return res.json(); } // 使用示例 loadSuppliers().then(data => { suppliers = data; // 替换全局变量 renderSupplierTable(); }).catch(err => { console.error(err); // 降级:使用内置的默认数据 });fetch返回的是 Promise,用async/await写更直观。res.ok判断 HTTP 状态码是否在 200-299 之间,失败时抛出异常。.catch里做降级处理,保证 JSON 文件缺失时页面仍然能显示默认数据。这个改造点适合已经确定字段结构的项目,如果字段还在频繁变动,建议先保持 JS 内联。
5.2 引入轻量路由实现页面无刷新切换
纯 HTML 多页面方案在页面跳转时会白屏一下,体验不够流畅。如果想让原型更接近真实系统,可以引入一个轻量路由库,比如page.js或navigo,把多个页面合并成单页应用。改造步骤不复杂:把每个页面的内容抽成<template>或 JS 模板字符串,路由根据 URL 切换渲染内容。
// 使用 page.js 定义路由规则 page('/', () => renderPage('login')); page('/supplier', () => renderPage('supplierList')); page('/order', () => renderPage('purchaseOrder')); page('/inventory', () => renderPage('inventory')); page.start(); // 渲染函数:根据页面名称加载对应模板 function renderPage(name) { const app = document.getElementById('app'); const templates = { supplierList: document.getElementById('tpl-supplier').innerHTML, purchaseOrder: document.getElementById('tpl-order').innerHTML, inventory: document.getElementById('tpl-inventory').innerHTML }; app.innerHTML = templates[name] || '<p>页面不存在</p>'; // 渲染后重新绑定事件 bindEvents(name); }page.js的 API 很简洁,page('/path', callback)定义路由,page.start()启动监听。renderPage从<template>标签里读取 HTML 片段,插入主容器。注意每次渲染后要重新绑定事件,因为 DOM 被替换了。这个改造适合原型已经稳定、需要给客户做演示的场景,如果只是内部对齐需求,多页面方案更省事。
5.3 用 CSS 变量统一主题色
供应链系统的页面多,如果每个页面都写死颜色值,后期换主题会改到崩溃。我一般会在custom.css里定义一组 CSS 变量,所有页面引用变量而不是具体色值。
/* custom.css:主题变量定义 */ :root { --primary-color: #0d6efd; /* 主色调,用于按钮和链接 */ --sidebar-bg: #1a1d21; /* 侧边栏背景 */ --table-header-bg: #343a40; /* 表头背景 */ --success-color: #198754; /* 成功状态 */ --warning-color: #ffc107; /* 警告状态 */ } /* 使用变量 */ .sidebar { background-color: var(--sidebar-bg); } .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); }改主题时只需要改:root里的几个值,全站生效。这个技巧在原型阶段可能觉得多余,但一旦项目进入迭代期,省下的时间非常可观。我一般会在项目启动时就加上,后面不管怎么改需求,颜色始终统一。
5.4 验证改造是否成功的三个检查点
改完之后怎么确认没改坏?我习惯走一遍这三个检查点:第一,打开浏览器控制台,看有没有 404 或 JS 报错;第二,把每个页面的核心交互点一遍,筛选、跳转、按钮点击是否正常;第三,把窗口缩到 768px 宽,看表格和筛选栏有没有错位。这三个检查点花不了五分钟,但能挡住大部分低级问题。
提示:改造过程中如果遇到
fetch跨域问题,不要急着搭后端,先把 JSON 文件放在和 HTML 同目录下,用相对路径加载。原型阶段不需要处理跨域,那是接真实接口时才需要考虑的事。
从那以后我每次交付原型之前,都会强制走一遍“控制台无报错、核心交互全通、窄屏不错位”这三步,踩过的坑多了,就变成了肌肉记忆。希望帮到你。
本文还有配套的精品资源,点击获取