Files

467 lines
20 KiB
HTML
Raw Permalink Normal View History

2026-01-11 18:14:42 +08:00
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>库存管理 - 商家后台</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<style>
/* 侧边栏样式 */
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 260px;
height: 100vh;
background: linear-gradient(180deg, #2c3e50 0%, #34495e 100%);
color: #fff;
z-index: 1050;
display: flex;
flex-direction: column;
transition: transform 0.3s ease;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
}
.sidebar-header {
padding: 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-brand {
font-size: 1.25rem;
font-weight: 600;
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 1rem 0;
}
.sidebar-nav .nav-link {
color: rgba(255, 255, 255, 0.8);
padding: 0.75rem 1.5rem;
display: flex;
align-items: center;
transition: all 0.2s;
text-decoration: none;
}
.sidebar-nav .nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #fff;
}
.sidebar-nav .nav-link.active {
background-color: rgba(255, 255, 255, 0.15);
color: #fff;
border-left: 3px solid #3498db;
}
.sidebar-nav .nav-link i {
width: 20px;
margin-right: 0.75rem;
font-size: 1.1rem;
}
.main-content {
margin-left: 260px;
padding: 2rem;
min-height: 100vh;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.show {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/merchant/dashboard.html">商家后台</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/merchant/dashboard.html">首页</a>
<a class="nav-link" href="/merchant/products.html">商品管理</a>
<a class="nav-link" href="/merchant/orders.html">订单管理</a>
<a class="nav-link active" href="/merchant/inventory.html">库存管理</a>
<a class="nav-link" href="/merchant/statistics.html">数据统计</a>
<a class="nav-link" href="/merchant/announcements.html">公告管理</a>
<a class="nav-link" href="/merchant/messages.html">留言管理</a>
<a class="nav-link" href="/merchant/reviews.html">评价管理</a>
<a class="nav-link" href="/merchant/users.html">用户管理</a>
</div>
</div>
</nav>
<div class="container mt-4">
<div class="d-flex justify-content-between mb-3">
<h2>库存管理</h2>
<button class="btn btn-primary" onclick="showAddInventoryModal()">新增库存</button>
</div>
<div class="mb-3">
<button class="btn btn-outline-warning" onclick="loadLowStockAlerts()">低库存预警</button>
</div>
<div id="inventoryList"></div>
</div>
<!-- 添加/编辑库存模态框 -->
<div class="modal fade" id="inventoryModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="inventoryModalTitle">新增库存</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="inventoryForm">
<input type="hidden" id="inventoryId">
<div class="mb-3">
<label for="materialName" class="form-label">原料名称 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="materialName" required>
</div>
<div class="mb-3">
<label for="quantity" class="form-label">数量 <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="quantity" step="0.01" min="0" required>
</div>
<div class="mb-3">
<label for="unit" class="form-label">单位</label>
<input type="text" class="form-control" id="unit" placeholder="例如kg、L、个">
</div>
<div class="mb-3">
<label for="minThreshold" class="form-label">最低库存阈值</label>
<input type="number" class="form-control" id="minThreshold" step="0.01" min="0" value="0">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="saveInventory()">保存</button>
</div>
</div>
</div>
</div>
<!-- 入库/出库模态框 -->
<div class="modal fade" id="stockModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="stockModalTitle">入库</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="stockForm">
<input type="hidden" id="stockInventoryId">
<div class="mb-3">
<label for="stockQuantity" class="form-label">数量 <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="stockQuantity" step="0.01" min="0" required>
</div>
<div class="mb-3">
<label for="operator" class="form-label">操作人</label>
<input type="text" class="form-control" id="operator">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="saveStock()">确认</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/sidebar-replacer.js"></script>
<script src="/static/js/ui-enhancements.js"></script>
<script>
// 使用Session浏览器自动发送Cookie
// 检查登录状态
async function checkLogin() {
try {
const response = await fetch('/merchant/profile', {
credentials: 'include'
});
const data = await response.json();
if (data.code !== 200 || !data.data) {
window.location.href = '/merchant/login.html';
return false;
}
return true;
} catch (error) {
console.error('Error:', error);
window.location.href = '/merchant/login.html';
return false;
}
}
let stockOperationType = 'in'; // 'in' 或 'out'
// 页面加载
checkLogin().then(isLoggedIn => {
if (isLoggedIn) {
loadInventory();
}
});
// 加载库存列表
async function loadInventory() {
try {
const response = await fetch('/merchant/inventory', {
credentials: 'include'
});
const data = await response.json();
const container = document.getElementById('inventoryList');
if (data.code === 200 && data.data) {
const inventory = data.data || [];
if (inventory.length === 0) {
container.innerHTML = '<div class="alert alert-info">暂无库存记录,请点击"新增库存"按钮添加</div>';
return;
}
container.innerHTML = `
<table class="table table-hover">
<thead>
<tr>
<th>原料名称</th>
<th>数量</th>
<th>单位</th>
<th>最低阈值</th>
<th>库存状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${inventory.map(item => {
const isLowStock = parseFloat(item.quantity || 0) <= parseFloat(item.minThreshold || 0);
return `
<tr class="${isLowStock ? 'table-warning' : ''}">
<td>${item.materialName || ''}</td>
<td>${item.quantity || '0'}</td>
<td>${item.unit || '-'}</td>
<td>${item.minThreshold || '0'}</td>
<td>
${isLowStock ?
'<span class="badge bg-warning">库存不足</span>' :
'<span class="badge bg-success">正常</span>'
}
</td>
<td>
<button class="btn btn-sm btn-success" onclick="showStockModal(${item.id}, 'in')">入库</button>
<button class="btn btn-sm btn-warning" onclick="showStockModal(${item.id}, 'out')">出库</button>
<button class="btn btn-sm btn-primary" onclick="editInventory(${item.id})">编辑</button>
<button class="btn btn-sm btn-danger" onclick="deleteInventory(${item.id})">删除</button>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
`;
} else {
container.innerHTML = '<div class="alert alert-warning">加载库存列表失败:' + (data.message || '未知错误') + '</div>';
}
} catch (error) {
console.error('Error:', error);
document.getElementById('inventoryList').innerHTML = '<div class="alert alert-danger">加载库存列表失败,请刷新页面重试</div>';
}
}
// 加载低库存预警
async function loadLowStockAlerts() {
try {
const response = await fetch('/merchant/inventory/alerts', {
credentials: 'include'
});
const data = await response.json();
if (data.code === 200 && data.data) {
const alerts = data.data || [];
if (alerts.length === 0) {
alert('暂无低库存预警');
} else {
alert(`有 ${alerts.length} 个原料库存不足:\n${alerts.map(a => `${a.materialName} (当前: ${a.quantity}, 阈值: ${a.minThreshold})`).join('\n')}`);
}
}
} catch (error) {
console.error('Error:', error);
alert('加载低库存预警失败');
}
}
// 显示添加库存模态框
function showAddInventoryModal() {
document.getElementById('inventoryModalTitle').textContent = '新增库存';
document.getElementById('inventoryId').value = '';
document.getElementById('inventoryForm').reset();
const modal = new bootstrap.Modal(document.getElementById('inventoryModal'));
modal.show();
}
// 编辑库存
async function editInventory(inventoryId) {
try {
const response = await fetch('/merchant/inventory', {
credentials: 'include'
});
const data = await response.json();
if (data.code === 200 && data.data) {
const inventory = data.data.find(i => i.id === inventoryId);
if (inventory) {
document.getElementById('inventoryModalTitle').textContent = '编辑库存';
document.getElementById('inventoryId').value = inventory.id;
document.getElementById('materialName').value = inventory.materialName || '';
document.getElementById('quantity').value = inventory.quantity || '';
document.getElementById('unit').value = inventory.unit || '';
document.getElementById('minThreshold').value = inventory.minThreshold || '0';
const modal = new bootstrap.Modal(document.getElementById('inventoryModal'));
modal.show();
}
}
} catch (error) {
console.error('Error:', error);
}
}
// 保存库存
async function saveInventory() {
const inventoryId = document.getElementById('inventoryId').value;
const inventoryData = {
materialName: document.getElementById('materialName').value,
quantity: parseFloat(document.getElementById('quantity').value) || 0,
unit: document.getElementById('unit').value,
minThreshold: parseFloat(document.getElementById('minThreshold').value) || 0
};
if (!inventoryData.materialName) {
UIEnhancements.showWarning('请填写材料名称');
return;
}
const saveBtn = document.querySelector('#inventoryModal .btn-primary');
try {
const url = inventoryId ? `/merchant/inventory/${inventoryId}` : '/merchant/inventory';
const method = inventoryId ? 'PUT' : 'POST';
const { data } = await UIEnhancements.enhancedFetch(url, {
method: method,
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(inventoryData),
button: saveBtn
});
if (data.code === 200) {
UIEnhancements.showSuccess(inventoryId ? '库存信息已更新' : '库存已添加');
const modal = bootstrap.Modal.getInstance(document.getElementById('inventoryModal'));
modal.hide();
loadInventory();
} else {
UIEnhancements.showError(data.message || '保存失败');
}
} catch (error) {
console.error('Error:', error);
UIEnhancements.showError('保存失败,请稍后重试');
}
}
// 显示入库/出库模态框
function showStockModal(inventoryId, type) {
stockOperationType = type;
document.getElementById('stockModalTitle').textContent = type === 'in' ? '入库' : '出库';
document.getElementById('stockInventoryId').value = inventoryId;
document.getElementById('stockForm').reset();
const modal = new bootstrap.Modal(document.getElementById('stockModal'));
modal.show();
}
// 保存入库/出库
async function saveStock() {
const inventoryId = document.getElementById('stockInventoryId').value;
const quantity = parseFloat(document.getElementById('stockQuantity').value);
const operator = document.getElementById('operator').value;
if (!quantity || quantity <= 0) {
UIEnhancements.showWarning('请输入有效的数量');
return;
}
const saveBtn = document.querySelector('#stockModal .btn-primary');
try {
const url = `/merchant/inventory/${inventoryId}/${stockOperationType}`;
const { data } = await UIEnhancements.enhancedFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
credentials: 'include',
body: `quantity=${quantity}&operator=${encodeURIComponent(operator || '')}`,
button: saveBtn
});
if (data.code === 200) {
UIEnhancements.showSuccess(stockOperationType === 'in' ? '入库成功' : '出库成功');
const modal = bootstrap.Modal.getInstance(document.getElementById('stockModal'));
modal.hide();
loadInventory();
} else {
UIEnhancements.showError(data.message || '操作失败');
}
} catch (error) {
console.error('Error:', error);
UIEnhancements.showError('操作失败,请稍后重试');
}
}
// 删除库存
async function deleteInventory(inventoryId) {
const confirmed = await UIEnhancements.confirmAction('确定要删除这个库存记录吗?', '确认删除');
if (!confirmed) return;
try {
const { data } = await UIEnhancements.enhancedFetch(`/merchant/inventory/${inventoryId}`, {
method: 'DELETE',
credentials: 'include'
});
if (data.code === 200) {
UIEnhancements.showSuccess('库存记录已删除');
loadInventory();
} else {
UIEnhancements.showError(data.message || '删除失败');
}
} catch (error) {
console.error('Error:', error);
UIEnhancements.showError('删除失败,请稍后重试');
}
}
</script>
</body>
</html>