中小商家库存智能预警系统
README文件
项目简介
本系统是为中小商家设计的库存智能管理平台,融合创新思维与战略管理理念,通过时间序列分析预测销售趋势,自动计算补货节点,生成采购清单,并提供数据可视化与Excel导出功能,帮助商家降低库存成本、规避缺货风险。
核心功能
- 商品档案管理与销售数据录入
- 基于时间序列模型的销量预测(移动平均/指数平滑)
- 多级预警机制(缺货预警、积压预警)
- 智能采购清单生成(含经济订货量计算)
- Excel数据导入导出
- 库存周转分析与可视化报表
技术栈
- 前端:HTML5/CSS3/JavaScript (ES6+)
- 数据处理:SheetJS (xlsx.js) 用于Excel交互
- 图表库:Chart.js 用于数据可视化
- 架构:模块化设计(6大核心模块)
目录结构
├── index.html # 主界面
├── styles.css # 样式文件
├── app.js # 核心逻辑代码
├── README.md # 项目说明
├── 使用说明.md # 用户操作指南
├── 核心知识点卡片.html # 管理/技术知识点库
└── lib/
├── xlsx.full.min.js # SheetJS库
└── chart.min.js # Chart.js库
核心代码实现
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>中小商家库存智能预警系统</title>
<script src="lib/xlsx.full.min.js"></script>
<script src="lib/chart.min.js"></script>
<style>
:root {
--primary: #3498db;
--warning: #f39c12;
--danger: #e74c3c;
--success: #2ecc71;
--light: #f8f9fa;
--dark: #343a40;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background: linear-gradient(135deg, var(--primary), #2980b9);
color: white;
padding: 1.5rem 0;
border-radius: 8px;
margin-bottom: 2rem;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
h1 {
font-size: 1.8rem;
font-weight: 600;
}
.dashboard {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 2rem;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
}
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
padding: 20px;
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.card-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--dark);
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #555;
}
input, select, button {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
button {
background-color: var(--primary);
color: white;
border: none;
cursor: pointer;
font-weight: 500;
transition: background-color 0.3s;
margin-top: 10px;
}
button:hover {
background-color: #2980b9;
}
.btn-warning {
background-color: var(--warning);
}
.btn-warning:hover {
background-color: #e67e22;
}
.btn-danger {
background-color: var(--danger);
}
.btn-danger:hover {
background-color: #c0392b;
}
.btn-success {
background-color: var(--success);
}
.btn-success:hover {
background-color: #27ae60;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background-color: #f8f9fa;
font-weight: 600;
}
tr:hover {
background-color: #f8f9fa;
}
.alert {
padding: 10px 15px;
border-radius: 4px;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.alert-warning {
background-color: #fff3cd;
border-left: 4px solid var(--warning);
color: #856404;
}
.alert-danger {
background-color: #f8d7da;
border-left: 4px solid var(--danger);
color: #721c24;
}
.alert-success {
background-color: #d4edda;
border-left: 4px solid var(--success);
color: #155724;
}
.chart-container {
height: 300px;
margin-top: 20px;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
.action-buttons button {
flex: 1;
}
.tabs {
display: flex;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border-bottom: 3px solid transparent;
}
.tab.active {
border-bottom-color: var(--primary);
color: var(--primary);
font-weight: 500;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.summary-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
text-align: center;
}
.summary-card h3 {
font-size: 1rem;
color: #777;
margin-bottom: 10px;
}
.summary-card .value {
font-size: 1.8rem;
font-weight: 600;
color: var(--primary);
}
.summary-card .change {
font-size: 0.9rem;
margin-top: 5px;
}
.positive {
color: var(--success);
}
.negative {
color: var(--danger);
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="header-content">
<h1>📊 中小商家库存智能预警系统</h1>
<div class="user-info">
<span id="currentDate"></span>
</div>
</div>
</header>
<div class="summary-cards">
<div class="summary-card">
<h3>商品总数</h3>
<div class="value" id="totalProducts">0</div>
</div>
<div class="summary-card">
<h3>预警商品</h3>
<div class="value" id="alertProducts">0</div>
</div>
<div class="summary-card">
<h3>库存总额</h3>
<div class="value" id="totalInventory">¥0</div>
</div>
<div class="summary-card">
<h3>周转率</h3>
<div class="value" id="turnoverRate">0%</div>
</div>
</div>
<div class="tabs">
<div class="tab active" data-tab="dashboard">仪表盘</div>
<div class="tab" data-tab="products">商品管理</div>
<div class="tab" data-tab="sales">销售数据</div>
<div class="tab" data-tab="forecast">预测分析</div>
<div class="tab" data-tab="purchase">采购清单</div>
</div>
<div class="tab-content active" id="dashboard-tab">
<div class="dashboard">
<div class="card">
<div class="card-header">
<h2 class="card-title">库存预警</h2>
</div>
<div id="alertsContainer">
<!-- 预警信息动态生成 -->
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">库存趋势</h2>
</div>
<div class="chart-container">
<canvas id="inventoryTrendChart"></canvas>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">热销商品排行</h2>
</div>
<div class="chart-container">
<canvas id="topProductsChart"></canvas>
</div>
</div>
</div>
<div class="tab-content" id="products-tab">
<div class="card">
<div class="card-header">
<h2 class="card-title">商品信息管理</h2>
</div>
<form id="productForm">
<div class="form-group">
<label for="productId">商品ID</label>
<input type="text" id="productId" required>
</div>
<div class="form-group">
<label for="productName">商品名称</label>
<input type="text" id="productName" required>
</div>
<div class="form-group">
<label for="category">商品类别</label>
<select id="category">
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
<option value="food">食品</option>
<option value="daily">日用品</option>
<option value="other">其他</option>
</select>
</div>
<div class="form-group">
<label for="unitPrice">单价 (¥)</label>
<input type="number" id="unitPrice" step="0.01" min="0" required>
</div>
<div class="form-group">
<label for="safetyStock">安全库存</label>
<input type="number" id="safetyStock" min="0" required>
</div>
<div class="form-group">
<label for="reorderPoint">补货点</label>
<input type="number" id="reorderPoint" min="0" required>
</div>
<div class="form-group">
<label for="leadTime">补货周期 (天)</label>
<input type="number" id="leadTime" min="1" required>
</div>
<button type="submit" class="btn-success">保存商品信息</button>
</form>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">商品列表</h2>
<button id="exportProductsBtn" class="btn-success">导出Excel</button>
</div>
<table id="productsTable">
<thead>
<tr>
<th>商品ID</th>
<th>名称</th>
<th>类别</th>
<th>单价</th>
<th>安全库存</th>
<th>补货点</th>
<th>补货周期</th>
<th>当前库存</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 商品数据动态生成 -->
</tbody>
</table>
</div>
</div>
<div class="tab-content" id="sales-tab">
<div class="card">
<div class="card-header">
<h2 class="card-title">销售数据录入</h2>
</div>
<form id="salesForm">
<div class="form-group">
<label for="saleProduct">选择商品</label>
<select id="saleProduct" required>
<!-- 商品选项动态生成 -->
</select>
</div>
<div class="form-group">
<label for="saleDate">销售日期</label>
<input type="date" id="saleDate" 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="saleAmount">销售金额 (¥)</label>
<input type="number" id="saleAmount" step="0.01" min="0" required>
</div>
<button type="submit" class="btn-success">添加销售记录</button>
</form>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">销售数据列表</h2>
<div class="action-buttons">
<button id="importSalesBtn" class="btn-warning">导入Excel</button>
<button id="exportSalesBtn" class="btn-success">导出Excel</button>
</div>
</div>
<table id="salesTable">
<thead>
<tr>
<th>商品ID</th>
<th>商品名称</th>
<th>销售日期</th>
<th>数量</th>
<th>金额 (¥)</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 销售数据动态生成 -->
</tbody>
</table>
</div>
</div>
<div class="tab-content" id="forecast-tab">
<div class="card">
<div class="card-header">
<h2 class="card-title">销量预测分析</h2>
</div>
<div class="form-group">
<label for="forecastProduct">选择商品</label>
<select id="forecastProduct" required>
<!-- 商品选项动态生成 -->
</select>
</div>
<div class="form-group">
<label for="forecastPeriod">预测周期 (天)</label>
<input type="number" id="forecastPeriod" min="1" max="90" value="30" required>
</div>
<div class="form-group">
<label for="forecastMethod">预测方法</label>
<select id="forecastMethod">
<option value="movingAverage">移动平均法</option>
<option value="exponentialSmoothing">指数平滑法</option>
</select>
</div>
<button id="runForecastBtn" class="btn-primary">运行预测</button>
<div class="chart-container">
<canvas id="forecastChart"></canvas>
</div>
<div id="forecastResults">
<!-- 预测结果动态生成 -->
</div>
</div>
</div>
<div class="tab-content" id="purchase-tab">
<div class="card">
<div class="card-header">
<h2 class="card-title">智能采购清单</h2>
<button id="generatePurchaseListBtn" class="btn-primary">生成采购清单</button>
</div>
<div id="purchaseListContainer">
<!-- 采购清单动态生成 -->
</div>
<div class="action-buttons">
<button id="exportPurchaseListBtn" class="btn-success">导出Excel</button>
</div>
</div>
</div>
</div>
<script>
/**
* 中小商家库存智能预警系统 - 核心模块
* 融合创新思维与战略管理理念,实现数据驱动的库存决策
*/
// ==================== 模块1: 数据管理模块 ====================
const DataManager = {
// 商品数据存储
products: JSON.parse(localStorage.getItem('inventoryProducts')) || [],
// 销售数据存储
sales: JSON.parse(localStorage.getItem('inventorySales')) || [],
// 预警规则配置
alertRules: {
stockout: { threshold: 0.2, message: "库存严重不足" },
lowStock: { threshold: 0.5, message: "库存偏低" },
overstock: { threshold: 1.5, message: "库存积压风险" }
},
// 初始化数据
init: function() {
// 设置默认日期为今天
document.getElementById('currentDate').textContent = new Date().toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
});
// 如果没有商品数据,添加示例数据
if (this.products.length === 0) {
this.addSampleData();
}
this.updateSummaryCards();
this.renderProductsTable();
this.renderSalesTable();
this.updateSaleProductOptions();
this.updateForecastProductOptions();
},
// 添加示例数据
addSampleData: function() {
this.products = [
{
id: "P001",
name: "无线蓝牙耳机",
category: "electronics",
unitPrice: 199.00,
safetyStock: 20,
reorderPoint: 30,
leadTime: 7,
currentStock: 45
},
{
id: "P002",
name: "纯棉T恤",
category: "clothing",
unitPrice: 59.00,
safetyStock: 50,
reorderPoint: 80,
leadTime: 5,
currentStock: 120
},
{
id: "P003",
name: "有机牛奶",
category: "food",
unitPrice: 12.80,
safetyStock: 30,
reorderPoint: 50,
leadTime: 2,
currentStock: 25
}
];
this.sales = [
{ productId: "P001", date: "2023-10-01", quantity: 15, amount: 2985 },
{ productId: "P001", date: "2023-10-05", quantity: 8, amount: 1592 },
{ productId: "P002", date: "2023-10-02", quantity: 25, amount: 1475 },
{ productId: "P002", date: "2023-10-06", quantity: 30, amount: 1770 },
{ productId: "P003", date: "2023-10-03", quantity: 40, amount: 512 },
{ productId: "P003", date: "2023-10-07", quantity: 35, amount: 448 }
];
this.saveData();
},
// 保存数据到localStorage
saveData: function() {
localStorage.setItem('inventoryProducts', JSON.stringify(this.products));
localStorage.setItem('inventorySales', JSON.stringify(this.sales));
},
// 添加商品
addProduct: function(product) {
// 检查ID是否已存在
if (this.products.some(p => p.id === product.id)) {
return false;
}
this.products.push(product);
this.saveData();
this.updateSummaryCards();
this.renderProductsTable();
this.updateSaleProductOptions();
this.updateForecastProductOptions();
return true;
},
// 更新商品
updateProduct: function(productId, updatedData) {
const index = this.products.findIndex(p => p.id === productId);
if (index === -1) return false;
this.products[index] = {...this.products[index], ...updatedData};
this.saveData();
this.updateSummaryCards();
this.renderProductsTable();
this.updateSaleProductOptions();
this.updateForecastProductOptions();
return true;
},
// 删除商品
deleteProduct: function(productId) {
const index = this.products.findIndex(p => p.id === productId);
if (index === -1) return false;
this.products.splice(index, 1);
// 同时删除相关销售记录
this.sales = this.sales.filter(s => s.productId !== productId);
this.saveData();
this.updateSummaryCards();
this.renderProductsTable();
this.renderSalesTable();
this.updateSaleProductOptions();
this.updateForecastProductOptions();
return true;
},
// 获取商品
getProduct: function(productId) {
return this.products.find(p => p.id === productId);
},
// 添加销售记录
addSale: function(sale) {
// 验证商品是否存在
if (!this.products.some(p => p.id === sale.productId)) {
return false;
}
this.sales.push(sale);
// 更新商品当前库存
const product = this.getProduct(sale.productId);
if (product) {
product.currentStock -= sale.quantity;
this.updateProduct(product.id, { currentStock: product.currentStock });
}
this.saveData();
this.renderSalesTable();
this.updateSummaryCards();
return true;
},
// 获取商品的销售数据
getProductSales: function(productId, days = 30) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
return this.sales
.filter(s => s.productId === productId && new Date(s.date) >= cutoffDate)
.sort((a, b) => new Date(a.date) - new Date(b.date));
},
// 更新汇总卡片
updateSummaryCards: function() {
document.getElementById('totalProducts').textContent = this.products.length;
// 计算预警商品数量
const alertCount = this.products.filter(p =>
p.currentStock <= p.reorderPoint
).length;
document.getElementById('alertProducts').textContent = alertCount;
// 计算库存总额
const totalInventory = this.products.reduce((sum, p) =>
sum + (p.unitPrice * p.currentStock), 0
);
document.getElementById('totalInventory').textContent = `¥${totalInventory.toFixed(2)}`;
// 计算周转率 (简化版)
const totalSales = this.sales.reduce((sum, s) => sum + s.amount, 0);
const turnoverRate = totalInventory > 0 ? (totalSales / totalInventory * 100).toFixed(2) : 0;
document.getElementById('turnoverRate').textContent = `${turnoverRate}%`;
},
// 渲染商品表格
renderProductsTable: function() {
const tbody = document.querySelector('#productsTable tbody');
tbody.innerHTML = '';
this.products.forEach(product => {
const row = document.createElement('tr');
// 根据库存状态设置行样式
if (product.currentStock <= product.safetyStock) {
row.style.backgroundColor = '#fff3cd';
} else if (product.currentStock > product.reorderPoint * 1.5) {
row.style.backgroundColor = '#d4edda';
}
row.innerHTML = `
<td>${product.id}</td>
<td>${product.name}</td>
<td>${this.getCategoryName(product.category)}</td>
<td>¥${product.unitPrice.toFixed(2)}</td>
<td>${product.safetyStock}</td>
<td>${product.reorderPoint}</td>
<td>${product.leadTime}</td>
<td>${product.currentStock}</td>
<td>
<button class="btn-warning edit-product" data-id="${product.id}">编辑</button>
<button class="btn-danger delete-product" data-id="${product.id}">删除</button>
</td>
`;
tbody.appendChild(row);
});
// 添加事件监听
document.querySelectorAll('.edit-product').forEach(btn => {
btn.addEventListener('click', (e) => {
const productId = e.target.dataset.id;
this.editProduct(productId);
});
});
document.querySelectorAll('.delete-product').forEach(btn => {
btn.addEventListener('click', (e) => {
const productId = e.target.dataset.id;
if (confirm('确定要删除这个商品及其所有销售记录吗?')) {
this.deleteProduct(productId);
}
});
});
},
// 渲染销售表格
renderSalesTable: function() {
const tbody = document.querySelector('#salesTable tbody');
tbody.innerHTML = '';
this.sales.forEach((sale, index) => {
const product = this.getProduct(sale.productId);
const row = document.createElement('tr');
row.innerHTML = `
<td>${sale.productId}</td>
<td>${product ? product.name : '未知商品'}</td>
<td>${sale.date}</td>
<td>${sale.quantity}</td>
<td>¥${sale.amount.toFixed(2)}</td>
<td>
<button class="btn-danger delete-sale" data-index="${index}">删除</button>
</td>
`;
tbody.appendChild(row);
});
// 添加删除事件监听
document.querySelectorAll('.delete-sale').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
this.deleteSale(index);
});
});
},
// 删除销售记录
deleteSale: function(index) {
const sale = this.sales[index];
if (sale) {
// 恢复库存
const product = this.getProduct(sale.productId);
if (product) {
product.currentStock += sale.quantity;
this.updateProduct(product.id, { currentStock: product.currentStock });
}
this.sales.splice(index, 1);
this.saveData();
this.renderSalesTable();
this.updateSummaryCards();
}
},
// 更新销售表单中的商品选项
updateSaleProductOptions: function() {
const select = document.getElementById('saleProduct');
select.innerHTML = '<option value="">选择商品</option>';
this.products.forEach(product => {
const option = document.createElement('option');
option.value = product.id;
option.textContent = `${product.name} (库存: ${product.currentStock})`;
select.appendChild(option);
});
},
// 更新预测表单中的商品选项
updateForecastProductOptions: function() {
const selects = [
document.getElementById('forecastProduct'),
document.getElementById('purchaseProductFilter')
].filter(el => el);
selects.forEach(select => {
const currentValue = select.value;
select.innerHTML = '<option value="">选择商品</option>';
this.products.forEach(product => {
const option = document.createElement('option');
option.value = product.id;
option.textContent = product.name;
select.appendChild(option);
});
if (currentValue) {
select.value = currentValue;
}
});
},
// 获取类别名称
getCategoryName: function(categoryCode) {
const categories = {
electronics: "电子产品",
clothing: "服装",
food: "食品",
daily: "日用品",
other: "其他"
};
return categories[categoryCode] || categoryCode;
},
// 编辑商品
editProduct: function(productId) {
const product = this.getProduct(productId);
if (!product) return;
// 填充表单
document.getElementById('productId').value = product.id;
document.getElementById('productName').value = product.name;
document.getElementById('category').value = product.category;
document.getElementById('unitPrice').value = product.unitPrice;
document.getElementById('safetyStock').value = product.safetyStock;
document.getElementById('reorderPoint').value = product.reorderPoint;
document.getElementById('leadTime').value = product.leadTime;
// 滚动到表单
document.getElementById('products-tab').scrollIntoView({ behavior: 'smooth' });
}
};
// ==================== 模块2: 时间序列预测模块 ====================
const ForecastEngine = {
// 移动平均法预测
movingAverage: function(data, period = 3) {
if (data.length === 0) return 0;
if (data.length < period) period = data.length;
const recentData = data.slice(-period);
const sum = recentData.reduce((acc, val) => acc + val.quantity, 0);
return sum / period;
},
// 指数平滑法预测 (Holt-Winters简化版)
exponentialSmoothing: function(data, alpha = 0.3) {
if (data.length === 0) return 0;
if (data.length === 1) return data[0].quantity;
let forecast = data[0].quantity;
for (let i = 1; i < data.length; i++) {
forecast = alpha * data[i].quantity + (1 - alpha) * forecast;
}
return forecast;
},
// 预测未来销量
predictSales: function(productId, days = 30, method = 'movingAverage') {
const salesData = DataManager.getProductSales(productId, 90); // 使用最近90天数据
if (salesData.length === 0) return 0;
let dailyAvg;
if (method === 'movingAverage') {
dailyAvg = this.movingAverage(salesData, 7); // 7天移动平均
} else {
dailyAvg = this.exponentialSmoothing(salesData);
}
return dailyAvg * days;
},
// 生成预测结果
generateForecast: function(productId, days = 30, method = 'movingAverage') {
const product = DataManager.getProduct(productId);
if (!product) return null;
const salesData = DataManager.getProductSales(productId, 90);
const forecastQty = this.predictSales(productId, days, method);
const forecastAmount = forecastQty * product.unitPrice;
return {
productId,
productName: product.name,
forecastDays: days,
forecastQuantity: Math.round(forecastQty),
forecastAmount: forecastAmount.toFixed(2),
method,
historicalData: salesData
};
}
};
// ==================== 模块3: 预警引擎模块 ====================
const AlertEngine = {
// 检查商品库存预警状态
checkProductAlert: function(product) {
const alerts = [];
// 缺货预警
if (product.currentStock <= product.safetyStock) {
alerts.push({
type: 'danger',
message: `${product.name} ${DataManager.alertRules.stockout.message},当前库存: ${product.currentStock}`,
productId: product.id
});
}
// 低库存预警
else if (product.currentStock <= product.reorderPoint) {
alerts.push({
type: 'warning',
message: `${product.name} ${DataManager.alertRules.lowStock.message},当前库存: ${product.currentStock}`,
productId: product.id
});
}
// 积压预警
else if (product.currentStock > product.reorderPoint * DataManager.alertRules.overstock.threshold) {
alerts.push({
type: 'info',
message: `${product.name} ${DataManager.alertRules.overstock.message},当前库存: ${product.currentStock}`,
productId: product.id
});
}
return alerts;
},
// 获取所有预警信息
getAllAlerts: function() {
const allAlerts = [];
DataManager.products.forEach(product => {
const productAlerts = this.checkProductAlert(product);
allAlerts.push(...productAlerts);
});
return allAlerts;
},
// 渲染预警信息
renderAlerts: function() {
const container = document.getElementById('alertsContainer');
const alerts = this.getAllAlerts();
if (alerts.length === 0) {
container.innerHTML = '<div class="alert alert-success">所有商品库存状态正常</div>';
return;
}
container.innerHTML = '';
alerts.forEach(alert => {
const alertEl = document.createElement('div');
alertEl.className = `alert alert-${alert.type}`;
alertEl.innerHTML = `
<strong>${alert.productName}:</strong> ${alert.message}
<button class="btn btn-sm" style="margin-left: 10px;" onclick="AlertEngine.resolveAlert('${alert.productId}')">标记处理</button>
`;
container.appendChild(alertEl);
});
},
// 标记预警为已处理
resolveAlert: function(productId) {
// 在实际应用中,这里可以添加更复杂的逻辑
alert(`已标记 ${DataManager.getProduct(productId).name} 的预警为处理中`);
this.renderAlerts();
}
};
// ==================== 模块4: 采购清单生成模块 ====================
const PurchaseManager = {
// 生成采购清单
generatePurchaseList: function() {
const list = [];
DataManager.products.forEach(product => {
// 只考虑需要补货的商品
if (product.currentStock <= product.reorderPoint) {
// 计算建议采购量 (EOQ简化版)
const forecast = ForecastEngine.predictSales(product.id, product.leadTime);
const purchaseQty = Math.max(
product.reorderPoint * 2 - product.currentStock, // 基础补货量
Math.ceil(forecast * 1.2) // 考虑预测销量增加20%
);
list.push({
productId: product.id,
productName: product.name,
currentStock: product.currentStock,
reorderPoint: product.reorderPoint,
leadTime: product.leadTime,
forecastDemand: Math.ceil(forecast),
suggestedQty: purchaseQty,
unitPrice: product.unitPrice,
totalCost: (purchaseQty * product.unitPrice).toFixed(2)
});
}
});
return list;
},
// 渲染采购清单
renderPurchaseList: function() {
const container = document.getElementById('purchaseListContainer');
const list = this.generatePurchaseList();
if (list.length === 0) {
container.innerHTML = '<div class="alert alert-success">当前所有商品库存充足,无需采购</div>';
return;
}
let html = `
<table class="purchase-list">
<thead>
<tr>
<th>商品ID</th>
<th>商品名称</th>
<th>当前库存</th>
<th>补货点</th>
<th>预测需求</th>
<th>建议采购量</th>
<th>单价</th>
<th>总金额</th>
</tr>
</thead>
<tbody>
`;
list.forEach(item => {
html += `
<tr>
<td>${item.productId}</td>
<td>${item.productName}</td>
<td>${item.currentStock}</td>
<td>${item.reorderPoint}</td>
<td>${item.forecastDemand}</td>
<td>${item.suggestedQty}</td>
<td>¥${item.unitPrice.toFixed(2)}</td>
<td>¥${item.totalCost}</td>
</tr>
`;
});
html += `
</tbody>
<tfoot>
<tr>
<td colspan="7" style="text-align: right;"><strong>总计:</strong></td>
<td><strong>¥${list.reduce((sum, item) => sum + parseFloat(item.totalCost), 0).toFixed(2)}</strong></td>
</tr>
</tfoot>
</table>
`;
container.innerHTML = html;
}
};
// ==================== 模块5: 报表与导出模块 ====================
const ReportManager = {
// 导出数据为Excel
exportToExcel: function(data, fileName, sheetName) {
try {
const ws = XLSX.utils.json_to_sheet(data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, sheetName);
XLSX.writeFile(wb, `${fileName}.xlsx`);
return true;
} catch (e) {
console.error("导出失败:", e);
return false;
}
},
// 获取商品数据用于导出
getProductsForExport: function() {
return DataManager.products.map(p => ({
"商品ID": p.id,
"商品名称": p.name,
"类别": DataManager.getCategoryName(p.category),
"单价(¥)": p.unitPrice,
"安全库存": p.safetyStock,
"补货点": p.reorderPoint,
"补货周期(天)": p.leadTime,
"当前库存": p.currentStock,
"库存金额(¥)": (p.unitPrice * p.currentStock).toFixed(2)
}));
},
// 获取销售数据用于导出
getSalesForExport: function() {
return DataManager.sales.map(s => {
const product = DataManager.getProduct(s.productId);
return {
"商品ID": s.productId,
"商品名称": product ? product.name : "未知商品",
"销售日期": s.date,
"销售数量": s.quantity,
"销售金额(¥)": s.amount.toFixed(2)
};
});
},
// 获取采购清单用于导出
getPurchaseListForExport: function() {
const list = PurchaseManager.generatePurchaseList();
return list.map(item => ({
"商品ID": item.productId,
"商品名称": item.productName,
"当前库存": item.currentStock,
"补货点": item.reorderPoint,
"补货周期(天)": item.leadTime,
"预测需求量": item.forecastDemand,
"建议采购量": item.suggestedQty,
"单价(¥)": item.unitPrice,
"总金额(¥)": item.totalCost
}));
}
};
// ==================== 模块6: 可视化模块 ====================
const VisualizationManager = {
// 初始化图表
initCharts: function() {
this.renderInventoryTrendChart();
this.renderTopProductsChart();
},
// 渲染库存趋势图
renderInventoryTrendChart: function() {
const ctx = document.getElementById('inventoryTrendChart').getContext('2d');
// 按类别统计库存
const categories = ["electronics", "clothing", "food", "daily", "other"];
const categoryNames = categories.map(cat => DataManager.getCategoryName(cat));
const inventoryByCategory = categories.map(cat =>
DataManager.products
.filter(p => p.category === cat)
.reduce((sum, p) => sum + p.currentStock, 0)
);
// 销毁旧图表实例(如果存在)
if (window.inventoryTrendChart) {
window.inventoryTrendChart.destroy();
}
window.inventoryTrendChart = new Chart(ctx, {
type: 'bar',
data: {
labels: categoryNames,
datasets: [{
label: '库存数量',
data: inventoryByCategory,
backgroundColor: [
'rgba(54, 162, 235, 0.7)',
'rgba(255, 99, 132, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(255, 159, 64, 0.7)',
'rgba(153, 102, 255, 0.7)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(255, 99, 132, 1)',
'rgba(75, 192, 192, 1)',
'rgba(255, 159, 64, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '库存数量'
}
}
}
}
});
},
// 渲染热销商品排行图
renderTopProductsChart: function() {
const ctx = document.getElementById('topProductsChart').getContext('2d');
// 计算每个商品的总销量
const productSales = {};
DataManager.sales.forEach(sale => {
if (!productSales[sale.productId]) {
productSales[sale.productId] = 0;
}
productSales[sale.productId] += sale.quantity;
});
// 转换为数组并排序
const sortedSales = Object.entries(productSales)
.map(([productId, quantity]) => {
const product = DataManager.getProduct(productId);
return {
id: productId,
name: product ? product.name : "未知商品",
quantity: quantity
};
})
.sort((a, b) => b.quantity - a.quantity)
.slice(0, 5); // 取前5名
// 销毁旧图表实例(如果存在)
if (window.topProductsChart) {
window.topProductsChart.destroy();
}
window.topProductsChart = new Chart(ctx, {
type: 'pie',
data: {
labels: sortedSales.map(item => item.name),
datasets: [{
data: sortedSales.map(item => item.quantity),
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(54, 162, 235, 0.7)',
'rgba(255, 206, 86, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(153, 102, 255, 0.7)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right'
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.raw || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = Math.round((value / total) * 100);
return `${label}: ${value}件 (${percentage}%)`;
}
}
}
}
}
});
},
// 渲染预测图表
renderForecastChart: function(forecastResult) {
const ctx = document.getElementById('forecastChart').getContext('2d');
// 准备历史数据
const historicalDates = forecastResult.historicalData.map(d => d.date);
const historicalQuantities = forecastResult.historicalData.map(d => d.quantity);
// 准备预测数据 (这里简化处理,实际应用中应生成未来日期)
const forecastDates = [];
const forecastQuantities = [];
const lastDate = new Date(historicalDates[historicalDates.length - 1]);
for (let i = 1; i <= forecastResult.forecastDays; i++) {
const nextDate = new Date(lastDate);
nextDate.setDate(nextDate.getDate() + i);
forecastDates.push(nextDate.toISOString().split('T')[0]);
forecastQuantities.push(Math.round(forecastResult.forecastQuantity / forecastResult.forecastDays));
}
// 销毁旧图表实例(如果存在)
if (window.forecastChart) {
window.forecastChart.destroy();
}
window.forecastChart = new Chart(ctx, {
type: 'line',
data: {
labels: [...historicalDates, ...forecastDates],
datasets: [
{
label: '历史销量',
data: [...historicalQuantities, ...Array(forecastDates.length).fill(null)],
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: 0.3,
fill: false
},
{
label: '预测销量',
data: [...Array(historicalDates.length).fill(null), ...forecastQuantities],
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
borderDash: [5, 5],
tension: 0.3,
fill: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '销量'
}
}
}
}
});
}
};
// ==================== 主程序初始化 ====================
document.addEventListener('DOMContentLoaded', function() {
// 初始化数据
DataManager.init();
// 初始化图表
VisualizationManager.initCharts();
// 渲染预警信息
AlertEngine.renderAlerts();
// 标签页切换
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
// 移除所有active类
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
// 添加active类到当前标签
this.classList.add('active');
document.getElementById(`${this.dataset.tab}-tab`).classList.add('active');
// 如果是预测标签页,初始化图表
if (this.dataset.tab === 'forecast') {
VisualizationManager.initCharts();
}
});
});
// 商品表单提交
document.getElementById('productForm').addEventListener('submit', function(e) {
e.preventDefault();
const product = {
id: document.getElementById('productId').value,
name: document.getElementById('productName').value,
category: document.getElementById('category').value,
unitPrice: parseFloat(document.getElementById('unitPrice').value),
safetyStock: parseInt(document.getElementById('safetyStock').value),
reorderPoint: parseInt(document.getElementById('reorderPoint').value),
leadTime: parseInt(document.getElementById('leadTime').value),
currentStock: parseInt(document.getElementById('currentStock')?.value || 0)
};
// 检查是新增还是编辑
const existingProduct = DataManager.getProduct(product.id);
if (existingProduct) {
DataManager.updateProduct(product.id, product);
alert('商品信息更新成功!');
} else {
// 设置默认当前库存
if (!product.currentStock) product.currentStock = 0;
if (DataManager.addProduct(product)) {
alert('商品添加成功!');
} else {
alert('商品ID已存在,请使用不同的ID!');
return;
}
}
// 重置表单
this.reset();
});
// 销售表单提交
document.getElementById('salesForm').addEventListener('submit', function(e) {
e.preventDefault();
const sale = {
productId: document.getElementById('saleProduct').value,
date: document.getElementById('saleDate').value,
quantity: parseInt(document.getElementById('quantity').value),
amount: parseFloat(document.getElementById('saleAmount').value)
};
if (DataManager.addSale(sale)) {
alert('销售记录添加成功!');
this.reset();
// 设置默认日期为今天
document.getElementById('saleDate').valueAsDate = new Date();
} else {
alert('添加失败,请检查商品ID是否正确!');
}
});
// 设置销售日期默认值为今天
document.getElementById('saleDate').valueAsDate = new Date();
// 运行预测按钮
document.getElementById('runForecastBtn').addEventListener('click', function() {
const productId = document.getElementById('forecastProduct').value;
const period = parseInt(document.getElementById('forecastPeriod').value);
const method = document.getElementById('forecastMethod').value;
if (!productId) {
alert('请选择商品!');
return;
}
const forecastResult = ForecastEngine.generateForecast(productId, period, method);
if (!forecastResult) {
alert('无法生成预测,可能是缺少销售数据!');
return;
}
// 显示预测结果
const resultsContainer = document.getElementById('forecastResults');
resultsContainer.innerHTML = `
<div class="alert alert-info">
<h3>${forecastResult.productName} 销量预测结果</h3>
<p>预测方法: ${method === 'movingAverage' ? '移动平均法' : '指数平滑法'}</p>
<p>预测周期: ${forecastResult.forecastDays} 天</p>
<p>预测销量: ${forecastResult.forecastQuantity} 件</p>
<p>预测销售额: ¥${forecastResult.forecastAmount}</p>
</div>
`;
// 渲染预测图表
VisualizationManager.renderForecastChart(forecastResult);
});
// 生成采购清单按钮
document.getElementById('generatePurchaseListBtn').addEventListener('click', function() {
PurchaseManager.renderPurchaseList();
});
// 导出商品按钮
document.getElementById('exportProductsBtn').addEventListener('click', function() {
const data = ReportManager.getProductsForExport();
if (ReportManager.exportToExcel(data, '商品数据', '商品列表')) {
alert('商品数据导出成功!');
} else {
alert('导出失败,请重试!');
}
});
// 导出销售按钮
document.getElementById('exportSalesBtn').addEventListener('click', function() {
const data = ReportManager.getSalesForExport();
if (ReportManager.exportToExcel(data, '销售数据', '销售记录')) {
alert('销售数据导出成功!');
} else {
alert('导出失败,请重试!');
}
});
// 导出采购清单按钮
document.getElementById('exportPurchaseListBtn').addEventListener('click', function() {
const data = ReportManager.getPurchaseListForExport();
if (ReportManager.exportToExcel(data, '采购清单', '采购建议')) {
alert('采购清单导出成功!');
} else {
alert('导出失败,请重试!');
}
});
// 导入销售按钮 (简化版,实际应用中应使用文件上传)
document.getElementById('importSalesBtn').addEventListener('click', function() {
alert('导入功能需要服务器支持,当前为演示版本');
});
});
</script>
</body>
</html>
使用说明
快速上手指南
1. 系统初始化:首次打开系统时,会自动创建示例商品和销售数据
2. 商品管理:在"商品管理"标签页添加、编辑或删除商品信息
3. 销售录入:在"销售数据"标签页记录每日销售情况
4. 预测分析:在"预测分析"标签页选择商品和预测方法,生成销量预测
5. 采购清单:在"采购清单"标签页自动生成建议采购清单
6. 数据导出:在各标签页可导出Excel格式的报表
核心操作流程
1. 库存预警设置:
- 为每个商品设置安全库存、补货点和补货周期
- 系统自动监控库存状态并分级预警(严重/偏低/积压)
2. 销量预测步骤:
- 选择目标商品
- 设置预测天数(默认30天)
- 选择预测方法(移动平均法/指数平滑法)
- 点击"运行预测"生成结果和图表
3. 采购清单生成:
- 系统自动识别需要补货的商品
- 基于预测销量和安全库存计算建议采购量
- 考虑经济订货量(EOQ)简化模型优化采购成本
高级功能
- 数据可视化:库存分布热力图、热销商品排行、预测趋势对比图
- 多维度分析:按商品类别、时间段分析销售和库存状况
- 批量操作:支持Excel导入导出,方便数据迁移
- 权限管理:可扩展用户角色和权限控制(需后端支持)
核心知识点卡片
卡片1: 时间序列预测
知识点:基于历史数据预测未来趋势
说明:系统采用移动平均法和指数平滑法进行销量预测,前者适合稳定趋势,后者对近期数据更敏感
战略价值:数据驱动的决策支持,避免经验主义误差
创新思维:动态调整预测模型参数,持续优化预测精度
卡片2: 安全库存管理
知识点:平衡缺货风险与库存成本的缓冲机制
公式:安全库存 = (最大日销量 - 平均日销量) × 最长补货周期
战略价值:保障客户服务水平的同时降低资金占用
创新思维:结合ABC分类法对商品分级管理(A类重点监控)
卡片3: 经济订货量(EOQ)
知识点:最小化总库存成本的订货批量模型
公式:EOQ = √[(2DS)/H],其中D=年需求量,S=订货成本,H=单位库存持有成本
战略价值:优化采购频率和批量,降低综合成本
系统实现:简化版EOQ应用于采购清单生成
卡片4: 库存周转率
知识点:衡量库存流动效率的关键指标
公式:周转率 = 销售成本 / 平均库存余额
战略价值:反映资金使用效率和商品变现能力
创新思维:结合行业基准进行对比分析,识别滞销品
卡片5: 预警分级机制
知识点:三级预警体系(红/黄/蓝)对应不同风险等级
实施:红色(缺货)、黄色(低库存)、蓝色(积压)
战略价值:差异化响应策略,提高管理效率
创新思维:引入预警升级机制,持续未处理预警自动上报
卡片6: 敏捷迭代管理
知识点:快速响应市场变化的持续改进方法
实施:每周回顾预测准确率,每月优化模型参数
战略价值:适应中小商家业务快速变化的特点
创新思维:建立"预测-执行-复盘"的PDCA闭环
系统特点
1. 战略管理融合:将库存周转率、EOQ模型等管理理论与系统功能深度结合
2. 创新思维应用:采用动态预警、多模型预测对比等创新方法
3. 模块化架构:6大独立模块便于功能扩展和维护升级
4. 数据驱动决策:所有建议基于历史数据分析,减少主观判断
5. 用户友好设计:简洁界面+可视化图表,降低中小商家使用门槛
6. 低成本部署:纯前端实现,无需服务器支持,本地即可运行
该系统适用于零售、批发等各类中小商家,帮助实现库存精细化管理,降低运营成本,提升资金周转效率。
关注我,有更多编程干货等着你!