| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- <!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="https://cdn.tailwindcss.com"></script>
- </head>
- <body class="bg-gray-50 min-h-screen">
- <!-- 导航栏 -->
- <nav class="bg-gradient-to-r from-blue-600 to-cyan-600 text-white shadow-lg">
- <div class="container mx-auto px-4 py-4">
- <div class="flex justify-between items-center">
- <div class="flex items-center gap-3">
- <span class="text-3xl">📦</span>
- <div>
- <h1 class="text-2xl font-bold">电商库存管理系统</h1>
- <p class="text-blue-200 text-sm">多平台库存同步 • 智能预警</p>
- </div>
- </div>
- <div class="flex gap-4">
- <button onclick="showView('products')" class="text-white hover:text-blue-200 transition">📦 商品管理</button>
- <button onclick="showView('stock')" class="text-white hover:text-blue-200 transition">📊 库存管理</button>
- <button onclick="showView('alerts')" class="text-white hover:text-blue-200 transition">⚠️ 库存预警</button>
- <button onclick="showAddModal()" class="bg-white text-blue-600 px-6 py-2 rounded-lg hover:bg-blue-50 font-medium transition shadow">
- ➕ 添加商品
- </button>
- </div>
- </div>
- </div>
- </nav>
-
- <!-- 主内容区 -->
- <div class="container mx-auto px-4 py-6">
-
- <!-- 统计卡片 -->
- <div class="grid grid-cols-1 md:grid-cols-6 gap-4 mb-6">
- <div class="bg-white p-4 rounded-xl shadow-md">
- <div class="text-gray-500 text-sm">总商品数</div>
- <div class="text-3xl font-bold text-blue-600" id="stat-total">-</div>
- </div>
- <div class="bg-white p-4 rounded-xl shadow-md">
- <div class="text-gray-500 text-sm">在售商品</div>
- <div class="text-3xl font-bold text-green-600" id="stat-active">-</div>
- </div>
- <div class="bg-white p-4 rounded-xl shadow-md">
- <div class="text-gray-500 text-sm">库存预警</div>
- <div class="text-3xl font-bold text-red-600" id="stat-low">-</div>
- </div>
- <div class="bg-white p-4 rounded-xl shadow-md">
- <div class="text-gray-500 text-sm">库存总值</div>
- <div class="text-2xl font-bold text-purple-600" id="stat-value">-</div>
- </div>
- <div class="bg-white p-4 rounded-xl shadow-md">
- <div class="text-gray-500 text-sm">今日入库</div>
- <div class="text-3xl font-bold text-green-600" id="stat-in">-</div>
- </div>
- <div class="bg-white p-4 rounded-xl shadow-md">
- <div class="text-gray-500 text-sm">今日出库</div>
- <div class="text-3xl font-bold text-orange-600" id="stat-out">-</div>
- </div>
- </div>
-
- <!-- 商品列表视图 -->
- <div id="products-view">
- <div class="bg-white rounded-xl shadow-md overflow-hidden">
- <div class="px-6 py-4 border-b flex justify-between items-center">
- <h2 class="text-lg font-semibold text-gray-800">商品列表</h2>
- <div class="flex gap-2">
- <input type="text" id="search-product" placeholder="搜索商品..." class="border rounded-lg px-4 py-2">
- <button onclick="loadProducts()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">搜索</button>
- </div>
- </div>
- <table class="w-full">
- <thead class="bg-gray-50">
- <tr>
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500">商品名称</th>
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500">SKU</th>
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500">分类</th>
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500">售价</th>
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500">库存</th>
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500">状态</th>
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500">操作</th>
- </tr>
- </thead>
- <tbody id="product-list" class="divide-y divide-gray-200">
- <tr><td colspan="7" class="px-6 py-12 text-center text-gray-500">⏳ 加载中...</td></tr>
- </tbody>
- </table>
- </div>
- </div>
-
- <!-- 库存管理视图 -->
- <div id="stock-view" class="hidden">
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
- <!-- 添加入库 -->
- <div class="bg-white rounded-xl shadow-md p-6">
- <h3 class="text-lg font-bold mb-4">📥 添加入库</h3>
- <form id="stock-in-form" class="space-y-4">
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">商品</label>
- <select id="stock-in-product" class="w-full border rounded-lg px-4 py-2" required></select>
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">平台/仓库</label>
- <select id="stock-in-platform" class="w-full border rounded-lg px-4 py-2">
- <option value="总仓">🏢 总仓</option>
- <option value="淘宝">🛒 淘宝</option>
- <option value="京东">📦 京东</option>
- <option value="拼多多">💰 拼多多</option>
- <option value="抖音">🎵 抖音</option>
- </select>
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">数量</label>
- <input type="number" id="stock-in-quantity" class="w-full border rounded-lg px-4 py-2" required min="1">
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">备注</label>
- <textarea id="stock-in-note" class="w-full border rounded-lg px-4 py-2" rows="2"></textarea>
- </div>
- <button type="submit" class="w-full bg-green-600 text-white py-3 rounded-lg hover:bg-green-700 font-medium">✅ 确认入库</button>
- </form>
- </div>
-
- <!-- 添加出库 -->
- <div class="bg-white rounded-xl shadow-md p-6">
- <h3 class="text-lg font-bold mb-4">📤 添加出库</h3>
- <form id="stock-out-form" class="space-y-4">
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">商品</label>
- <select id="stock-out-product" class="w-full border rounded-lg px-4 py-2" required></select>
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">平台/仓库</label>
- <select id="stock-out-platform" class="w-full border rounded-lg px-4 py-2">
- <option value="总仓">🏢 总仓</option>
- <option value="淘宝">🛒 淘宝</option>
- <option value="京东">📦 京东</option>
- <option value="拼多多">💰 拼多多</option>
- <option value="抖音">🎵 抖音</option>
- </select>
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">数量</label>
- <input type="number" id="stock-out-quantity" class="w-full border rounded-lg px-4 py-2" required min="1">
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">备注</label>
- <textarea id="stock-out-note" class="w-full border rounded-lg px-4 py-2" rows="2"></textarea>
- </div>
- <button type="submit" class="w-full bg-orange-600 text-white py-3 rounded-lg hover:bg-orange-700 font-medium">📤 确认出库</button>
- </form>
- </div>
- </div>
-
- <!-- 库存记录 -->
- <div class="bg-white rounded-xl shadow-md mt-6 p-6">
- <h3 class="text-lg font-bold mb-4">📋 库存记录</h3>
- <div id="stock-records" class="space-y-2">
- <p class="text-gray-500 text-center py-8">暂无记录</p>
- </div>
- </div>
- </div>
-
- <!-- 库存预警视图 -->
- <div id="alerts-view" class="hidden">
- <div class="bg-white rounded-xl shadow-md p-6">
- <h3 class="text-lg font-bold mb-4">⚠️ 库存预警</h3>
- <div id="alerts-list" class="space-y-4">
- <p class="text-gray-500 text-center py-12">✅ 所有商品库存充足</p>
- </div>
- </div>
- </div>
-
- </div>
-
- <!-- 添加商品模态框 -->
- <div id="add-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
- <div class="bg-white rounded-xl p-8 w-full max-w-lg mx-4 shadow-2xl">
- <div class="flex justify-between items-center mb-6">
- <h3 class="text-2xl font-bold text-gray-800">➕ 添加商品</h3>
- <button onclick="hideAddModal()" class="text-gray-400 hover:text-gray-600 text-2xl">×</button>
- </div>
- <form id="add-form" class="space-y-4">
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">商品名称 *</label>
- <input type="text" name="name" required class="w-full border rounded-lg px-4 py-3">
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">SKU *</label>
- <input type="text" name="sku" required class="w-full border rounded-lg px-4 py-3" placeholder="例如:SKU001">
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">分类</label>
- <input type="text" name="category" class="w-full border rounded-lg px-4 py-3">
- </div>
- <div class="grid grid-cols-2 gap-4">
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">售价</label>
- <input type="number" step="0.01" name="price" class="w-full border rounded-lg px-4 py-3">
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">成本</label>
- <input type="number" step="0.01" name="cost" class="w-full border rounded-lg px-4 py-3">
- </div>
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">最低库存预警</label>
- <input type="number" name="minStock" value="10" class="w-full border rounded-lg px-4 py-3">
- </div>
- <div>
- <label class="block text-sm font-medium text-gray-700 mb-2">描述</label>
- <textarea name="description" rows="3" class="w-full border rounded-lg px-4 py-3"></textarea>
- </div>
- <div class="flex gap-4 pt-6">
- <button type="submit" class="flex-1 bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 font-medium">💾 保存</button>
- <button type="button" onclick="hideAddModal()" class="flex-1 bg-gray-200 text-gray-700 py-3 rounded-lg hover:bg-gray-300 font-medium">取消</button>
- </div>
- </form>
- </div>
- </div>
-
- <script>
- const API_BASE = window.location.hostname === 'localhost' ? 'http://localhost:3003/api' : '/api';
- let products = [];
-
- // 切换视图
- function showView(view) {
- document.getElementById('products-view').classList.add('hidden');
- document.getElementById('stock-view').classList.add('hidden');
- document.getElementById('alerts-view').classList.add('hidden');
- document.getElementById(view + '-view').classList.remove('hidden');
-
- if (view === 'products') loadProducts();
- if (view === 'stock') { loadProducts(); loadStockRecords(); }
- if (view === 'alerts') loadAlerts();
- loadStats();
- }
-
- // 加载统计
- async function loadStats() {
- try {
- const res = await fetch(`${API_BASE}/stats`);
- const data = await res.json();
- if (data.success) {
- document.getElementById('stat-total').textContent = data.data.totalProducts;
- document.getElementById('stat-active').textContent = data.data.activeProducts;
- document.getElementById('stat-low').textContent = data.data.lowStock;
- document.getElementById('stat-value').textContent = '¥' + data.data.totalValue.toFixed(2);
- document.getElementById('stat-in').textContent = data.data.todayIn;
- document.getElementById('stat-out').textContent = data.data.todayOut;
- }
- } catch (error) {
- console.error('加载统计失败:', error);
- }
- }
-
- // 加载商品列表
- async function loadProducts() {
- try {
- const keyword = document.getElementById('search-product').value;
- const url = `${API_BASE}/products${keyword ? '?keyword=' + encodeURIComponent(keyword) : ''}`;
- const res = await fetch(url);
- const data = await res.json();
-
- products = data.data || [];
- const tbody = document.getElementById('product-list');
-
- if (products.length > 0) {
- tbody.innerHTML = products.map(p => `
- <tr class="hover:bg-gray-50">
- <td class="px-6 py-4 font-medium">${p.name}</td>
- <td class="px-6 py-4 text-gray-600">${p.sku}</td>
- <td class="px-6 py-4 text-gray-600">${p.category || '-'}</td>
- <td class="px-6 py-4 text-green-600 font-medium">¥${p.price}</td>
- <td class="px-6 py-4">
- <span class="${p.totalStock <= p.minStock ? 'text-red-600 font-bold' : 'text-gray-600'}">${p.totalStock}</span>
- </td>
- <td class="px-6 py-4">
- <span class="px-2 py-1 rounded-full text-xs ${p.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}">
- ${p.status === 'active' ? '在售' : '停售'}
- </span>
- </td>
- <td class="px-6 py-4">
- <button onclick="showStockModal(${p.id})" class="text-blue-600 hover:text-blue-800 mr-2">📊 库存</button>
- <button onclick="deleteProduct(${p.id})" class="text-red-600 hover:text-red-800">🗑️</button>
- </td>
- </tr>
- `).join('');
- } else {
- tbody.innerHTML = '<tr><td colspan="7" class="px-6 py-12 text-center text-gray-500">暂无商品</td></tr>';
- }
-
- // 填充下拉框
- const inSelect = document.getElementById('stock-in-product');
- const outSelect = document.getElementById('stock-out-product');
- const options = '<option value="">选择商品</option>' + products.map(p =>
- `<option value="${p.id}">${p.name} (${p.sku}) - 库存:${p.totalStock}</option>`
- ).join('');
- inSelect.innerHTML = options;
- outSelect.innerHTML = options;
- } catch (error) {
- console.error('加载商品失败:', error);
- }
- }
-
- // 加载库存记录
- async function loadStockRecords() {
- try {
- const res = await fetch(`${API_BASE}/stock-records`);
- const data = await res.json();
- const container = document.getElementById('stock-records');
-
- if (data.data && data.data.length > 0) {
- container.innerHTML = data.data.slice(0, 50).map(r => `
- <div class="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
- <div>
- <span class="${r.type === 'in' ? 'text-green-600' : 'text-orange-600'} font-bold">
- ${r.type === 'in' ? '📥 入库' : '📤 出库'}
- </span>
- <span class="ml-2 text-gray-600">${r.platform}</span>
- <span class="ml-2 text-gray-500 text-sm">${new Date(r.created_at).toLocaleString()}</span>
- </div>
- <div class="text-right">
- <span class="${r.type === 'in' ? 'text-green-600' : 'text-orange-600'} font-bold">
- ${r.type === 'in' ? '+' : '-'}${r.quantity}
- </span>
- ${r.note ? `<div class="text-xs text-gray-500">${r.note}</div>` : ''}
- </div>
- </div>
- `).join('');
- } else {
- container.innerHTML = '<p class="text-gray-500 text-center py-8">暂无库存记录</p>';
- }
- } catch (error) {
- console.error('加载库存记录失败:', error);
- }
- }
-
- // 加载预警
- async function loadAlerts() {
- try {
- const res = await fetch(`${API_BASE}/alerts`);
- const data = await res.json();
- const container = document.getElementById('alerts-list');
-
- if (data.data && data.data.length > 0) {
- container.innerHTML = data.data.map(a => `
- <div class="p-4 rounded-lg ${a.level === 'critical' ? 'bg-red-50 border-l-4 border-red-500' : 'bg-yellow-50 border-l-4 border-yellow-500'}">
- <div class="flex justify-between items-center">
- <div>
- <div class="font-bold ${a.level === 'critical' ? 'text-red-800' : 'text-yellow-800'}">
- ${a.level === 'critical' ? '🚨 严重缺货' : '⚠️ 库存不足'}
- </div>
- <div class="text-gray-700 mt-1">${a.name} (${a.sku})</div>
- </div>
- <div class="text-right">
- <div class="text-2xl font-bold ${a.level === 'critical' ? 'text-red-600' : 'text-yellow-600'}">${a.currentStock}</div>
- <div class="text-sm text-gray-500">最低库存:${a.minStock}</div>
- </div>
- </div>
- </div>
- `).join('');
- } else {
- container.innerHTML = '<p class="text-gray-500 text-center py-12">✅ 所有商品库存充足</p>';
- }
- } catch (error) {
- console.error('加载预警失败:', error);
- }
- }
-
- // 显示库存详情
- async function showStockModal(productId) {
- const product = products.find(p => p.id === productId);
- if (!product) return;
-
- try {
- const res = await fetch(`${API_BASE}/platform-stock/${productId}`);
- const data = await res.json();
-
- if (data.success) {
- const platforms = data.data.byPlatform;
- let info = `商品:${product.name}\nSKU: ${product.sku}\n总库存:${data.data.totalStock}\n\n`;
- info += '各平台库存:\n';
- for (const [platform, stock] of Object.entries(platforms)) {
- info += ` ${platform}: ${stock}\n`;
- }
- alert(info);
- }
- } catch (error) {
- alert('加载库存详情失败');
- }
- }
-
- // 添加商品
- document.getElementById('add-form').addEventListener('submit', async (e) => {
- e.preventDefault();
- const formData = new FormData(e.target);
- const data = Object.fromEntries(formData);
-
- try {
- const res = await fetch(`${API_BASE}/products`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(data)
- });
- const result = await res.json();
-
- if (result.success) {
- alert('✅ 商品添加成功!');
- hideAddModal();
- loadProducts();
- loadStats();
- }
- } catch (error) {
- alert('添加失败:' + error.message);
- }
- });
-
- // 添加入库
- document.getElementById('stock-in-form').addEventListener('submit', async (e) => {
- e.preventDefault();
- const data = {
- product_id: document.getElementById('stock-in-product').value,
- platform: document.getElementById('stock-in-platform').value,
- quantity: parseInt(document.getElementById('stock-in-quantity').value),
- note: document.getElementById('stock-in-note').value
- };
-
- try {
- const res = await fetch(`${API_BASE}/stock-in`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(data)
- });
- const result = await res.json();
-
- if (result.success) {
- alert('✅ 入库成功!');
- document.getElementById('stock-in-form').reset();
- loadProducts();
- loadStats();
- loadStockRecords();
- }
- } catch (error) {
- alert('入库失败:' + error.message);
- }
- });
-
- // 添加出库
- document.getElementById('stock-out-form').addEventListener('submit', async (e) => {
- e.preventDefault();
- const data = {
- product_id: document.getElementById('stock-out-product').value,
- platform: document.getElementById('stock-out-platform').value,
- quantity: parseInt(document.getElementById('stock-out-quantity').value),
- note: document.getElementById('stock-out-note').value
- };
-
- try {
- const res = await fetch(`${API_BASE}/stock-out`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(data)
- });
- const result = await res.json();
-
- if (result.success) {
- alert('✅ 出库成功!');
- document.getElementById('stock-out-form').reset();
- loadProducts();
- loadStats();
- loadStockRecords();
- }
- } catch (error) {
- alert('出库失败:' + error.message);
- }
- });
-
- // 删除商品
- async function deleteProduct(id) {
- if (!confirm('确定要删除这个商品吗?')) return;
- try {
- const res = await fetch(`${API_BASE}/products/${id}`, { method: 'DELETE' });
- const data = await res.json();
- if (data.success) {
- alert('✅ 商品已删除');
- loadProducts();
- loadStats();
- }
- } catch (error) {
- alert('删除失败:' + error.message);
- }
- }
-
- function showAddModal() {
- document.getElementById('add-modal').classList.remove('hidden');
- document.getElementById('add-modal').classList.add('flex');
- }
-
- function hideAddModal() {
- document.getElementById('add-modal').classList.add('hidden');
- document.getElementById('add-modal').classList.remove('flex');
- document.getElementById('add-form').reset();
- }
-
- // 初始化
- loadStats();
- loadProducts();
- </script>
- </body>
- </html>
|