Files

652 lines
28 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">
</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 active" href="/merchant/products.html">商品管理</a>
<a class="nav-link" href="/merchant/orders.html">订单管理</a>
<a class="nav-link" 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">
<h2 id="pageTitle">新增商品</h2>
<form id="productForm" enctype="multipart/form-data">
<input type="hidden" id="productId">
<div class="card mb-3">
<div class="card-header">基本信息</div>
<div class="card-body">
<div class="mb-3">
<label for="name" class="form-label">商品名称 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="name" required>
</div>
<div class="mb-3">
<label for="category" class="form-label">分类</label>
<input type="text" class="form-control" id="category" placeholder="例如:奶茶、果茶">
</div>
<div class="mb-3">
<label for="description" class="form-label">商品描述</label>
<textarea class="form-control" id="description" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="basePrice" class="form-label">基础价格 <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="basePrice" step="0.01" min="0" required>
</div>
<div class="mb-3">
<label for="image" class="form-label">商品图片</label>
<input type="file" class="form-control" id="image" accept="image/*">
<div id="imagePreview" class="mt-2"></div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<span>规格管理</span>
<button type="button" class="btn btn-sm btn-primary" onclick="addSpec()">添加规格</button>
</div>
<div class="card-body">
<div id="specsList"></div>
</div>
</div>
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<span>配料管理</span>
<button type="button" class="btn btn-sm btn-primary" onclick="addTopping()">添加配料</button>
</div>
<div class="card-body">
<div id="toppingsList"></div>
</div>
</div>
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<span>定制选项管理(甜度、冰度)</span>
<div>
<button type="button" class="btn btn-sm btn-primary" onclick="addCustom('sweetness')">添加甜度</button>
<button type="button" class="btn btn-sm btn-primary" onclick="addCustom('ice')">添加冰度</button>
</div>
</div>
<div class="card-body">
<div class="mb-3">
<h6>甜度选项</h6>
<div id="sweetnessList"></div>
</div>
<div class="mb-3">
<h6>冰度选项</h6>
<div id="iceList"></div>
</div>
</div>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">保存</button>
<a href="/merchant/products.html" class="btn btn-secondary">取消</a>
</div>
</form>
</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;
}
}
const urlParams = new URLSearchParams(window.location.search);
const productId = urlParams.get('id');
let specs = [];
let toppings = [];
let customs = {
sweetness: [],
ice: []
};
// 页面加载
checkLogin().then(isLoggedIn => {
if (isLoggedIn) {
if (productId) {
document.getElementById('pageTitle').textContent = '编辑商品';
loadProduct();
} else {
// 新商品时初始化默认选项
if (customs.sweetness.length === 0) {
customs.sweetness = [
{ optionType: 'sweetness', optionValue: '无糖', priceAdjust: 0 },
{ optionType: 'sweetness', optionValue: '少糖', priceAdjust: 0 },
{ optionType: 'sweetness', optionValue: '正常', priceAdjust: 0 },
{ optionType: 'sweetness', optionValue: '多糖', priceAdjust: 0 }
];
}
if (customs.ice.length === 0) {
customs.ice = [
{ optionType: 'ice', optionValue: '去冰', priceAdjust: 0 },
{ optionType: 'ice', optionValue: '少冰', priceAdjust: 0 },
{ optionType: 'ice', optionValue: '正常', priceAdjust: 0 },
{ optionType: 'ice', optionValue: '多冰', priceAdjust: 0 }
];
}
renderCustoms();
}
}
});
// 图片预览
document.getElementById('image').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('imagePreview').innerHTML =
`<img src="${e.target.result}" class="img-thumbnail" style="max-width: 200px;">`;
};
reader.readAsDataURL(file);
}
});
// 加载商品信息
async function loadProduct() {
try {
const response = await fetch(`/merchant/products/${productId}`, {
credentials: 'include'
});
const data = await response.json();
if (data.code === 200) {
const product = data.data.product;
document.getElementById('productId').value = product.id;
document.getElementById('name').value = product.name || '';
document.getElementById('category').value = product.category || '';
document.getElementById('description').value = product.description || '';
document.getElementById('basePrice').value = product.basePrice || '';
if (product.image) {
document.getElementById('imagePreview').innerHTML =
`<img src="${product.image}" class="img-thumbnail" style="max-width: 200px;">`;
}
specs = data.data.specs || [];
toppings = data.data.toppings || [];
const allCustoms = data.data.customs || [];
// 分离甜度和冰度选项
customs.sweetness = allCustoms.filter(c => c.optionType === 'sweetness');
customs.ice = allCustoms.filter(c => c.optionType === 'ice');
// 如果没有甜度选项,创建默认选项
if (customs.sweetness.length === 0) {
customs.sweetness = [
{ optionType: 'sweetness', optionValue: '无糖', priceAdjust: 0 },
{ optionType: 'sweetness', optionValue: '少糖', priceAdjust: 0 },
{ optionType: 'sweetness', optionValue: '正常', priceAdjust: 0 },
{ optionType: 'sweetness', optionValue: '多糖', priceAdjust: 0 }
];
}
// 如果没有冰度选项,创建默认选项
if (customs.ice.length === 0) {
customs.ice = [
{ optionType: 'ice', optionValue: '去冰', priceAdjust: 0 },
{ optionType: 'ice', optionValue: '少冰', priceAdjust: 0 },
{ optionType: 'ice', optionValue: '正常', priceAdjust: 0 },
{ optionType: 'ice', optionValue: '多冰', priceAdjust: 0 }
];
}
renderSpecs();
renderToppings();
renderCustoms();
} else {
if (data.message && data.message.includes('登录')) {
alert('请先登录');
window.location.href = '/merchant/login.html';
} else {
alert(data.message || '加载商品信息失败');
window.location.href = '/merchant/products.html';
}
}
} catch (error) {
console.error('Error:', error);
alert('加载商品信息失败');
window.location.href = '/merchant/products.html';
}
}
// 渲染规格列表
function renderSpecs() {
const container = document.getElementById('specsList');
if (specs.length === 0) {
container.innerHTML = '<p class="text-muted">暂无规格,点击"添加规格"按钮添加</p>';
return;
}
container.innerHTML = `
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>规格名称</th>
<th>价格调整</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${specs.map((spec, index) => `
<tr>
<td>
<input type="text" class="form-control form-control-sm"
value="${spec.specName || ''}"
onchange="updateSpec(${index}, 'specName', this.value)"
placeholder="例如:大杯、中杯、小杯">
</td>
<td>
<input type="number" class="form-control form-control-sm"
value="${spec.priceAdjust || 0}"
step="0.01"
onchange="updateSpec(${index}, 'priceAdjust', this.value)"
placeholder="价格调整">
</td>
<td>
<button type="button" class="btn btn-sm btn-danger" onclick="removeSpec(${index})">删除</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
// 渲染配料列表
function renderToppings() {
const container = document.getElementById('toppingsList');
if (toppings.length === 0) {
container.innerHTML = '<p class="text-muted">暂无配料,点击"添加配料"按钮添加</p>';
return;
}
container.innerHTML = `
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>配料名称</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${toppings.map((topping, index) => `
<tr>
<td>
<input type="text" class="form-control form-control-sm"
value="${topping.toppingName || ''}"
onchange="updateTopping(${index}, 'toppingName', this.value)"
placeholder="例如:珍珠、椰果、布丁">
</td>
<td>
<input type="number" class="form-control form-control-sm"
value="${topping.price || 0}"
step="0.01"
onchange="updateTopping(${index}, 'price', this.value)"
placeholder="价格">
</td>
<td>
<button type="button" class="btn btn-sm btn-danger" onclick="removeTopping(${index})">删除</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
// 添加规格
function addSpec() {
specs.push({ specName: '', priceAdjust: 0 });
renderSpecs();
}
// 更新规格
function updateSpec(index, field, value) {
if (specs[index]) {
specs[index][field] = field === 'priceAdjust' ? parseFloat(value) || 0 : value;
}
}
// 删除规格
function removeSpec(index) {
if (confirm('确定要删除这个规格吗?')) {
specs.splice(index, 1);
renderSpecs();
}
}
// 添加配料
function addTopping() {
toppings.push({ toppingName: '', price: 0 });
renderToppings();
}
// 更新配料
function updateTopping(index, field, value) {
if (toppings[index]) {
toppings[index][field] = field === 'price' ? parseFloat(value) || 0 : value;
}
}
// 删除配料
function removeTopping(index) {
if (confirm('确定要删除这个配料吗?')) {
toppings.splice(index, 1);
renderToppings();
}
}
// 渲染定制选项
function renderCustoms() {
renderCustomType('sweetness');
renderCustomType('ice');
}
// 渲染特定类型的定制选项
function renderCustomType(type) {
const container = document.getElementById(type + 'List');
const options = customs[type] || [];
if (options.length === 0) {
container.innerHTML = '<p class="text-muted">暂无' + (type === 'sweetness' ? '甜度' : '冰度') + '选项,点击"添加' + (type === 'sweetness' ? '甜度' : '冰度') + '"按钮添加</p>';
return;
}
container.innerHTML = `
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>选项值</th>
<th>价格调整</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${options.map((custom, index) => `
<tr>
<td>
<input type="text" class="form-control form-control-sm"
value="${custom.optionValue || ''}"
onchange="updateCustom('${type}', ${index}, 'optionValue', this.value)"
placeholder="例如:${type === 'sweetness' ? '无糖、少糖、正常、多糖' : '去冰、少冰、正常、多冰'}">
</td>
<td>
<input type="number" class="form-control form-control-sm"
value="${custom.priceAdjust || 0}"
step="0.01"
onchange="updateCustom('${type}', ${index}, 'priceAdjust', this.value)"
placeholder="价格调整">
</td>
<td>
<button type="button" class="btn btn-sm btn-danger" onclick="removeCustom('${type}', ${index})">删除</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
// 添加定制选项
function addCustom(type) {
if (!customs[type]) {
customs[type] = [];
}
customs[type].push({
optionType: type,
optionValue: '',
priceAdjust: 0
});
renderCustomType(type);
}
// 更新定制选项
function updateCustom(type, index, field, value) {
if (customs[type] && customs[type][index]) {
customs[type][index][field] = field === 'priceAdjust' ? parseFloat(value) || 0 : value;
}
}
// 删除定制选项
function removeCustom(type, index) {
if (confirm('确定要删除这个选项吗?')) {
customs[type].splice(index, 1);
renderCustomType(type);
}
}
// 提交表单
document.getElementById('productForm').addEventListener('submit', async function(e) {
e.preventDefault();
// 表单验证
if (!UIEnhancements.validateForm('productForm')) {
UIEnhancements.showWarning('请填写所有必填项');
return;
}
const submitBtn = e.target.querySelector('button[type="submit"]');
const formData = new FormData();
formData.append('name', document.getElementById('name').value);
formData.append('category', document.getElementById('category').value);
formData.append('description', document.getElementById('description').value);
formData.append('basePrice', document.getElementById('basePrice').value);
const imageFile = document.getElementById('image').files[0];
if (imageFile) {
formData.append('image', imageFile);
}
const productIdValue = document.getElementById('productId').value;
const url = productIdValue ? `/merchant/products/${productIdValue}` : '/merchant/products';
const method = productIdValue ? 'PUT' : 'POST';
try {
UIEnhancements.setButtonLoading(submitBtn, true);
// 先保存商品基本信息
const response = await fetch(url, {
method: method,
credentials: 'include',
body: formData
});
const data = await response.json();
if (data.code === 200) {
const savedProductId = data.data.id || productIdValue;
// 保存规格、配料和定制选项如果有商品ID
if (savedProductId) {
await saveSpecsAndToppings(savedProductId);
await saveCustoms(savedProductId);
}
UIEnhancements.showSuccess('商品保存成功!');
setTimeout(() => {
window.location.href = '/merchant/products.html';
}, 1000);
} else {
if (data.message && data.message.includes('登录')) {
UIEnhancements.showWarning('请先登录');
setTimeout(() => {
window.location.href = '/merchant/login.html';
}, 1500);
} else {
UIEnhancements.showError(data.message || '保存失败');
}
UIEnhancements.setButtonLoading(submitBtn, false);
}
} catch (error) {
console.error('Error:', error);
UIEnhancements.showError('保存失败,请检查网络连接');
UIEnhancements.setButtonLoading(submitBtn, false);
}
});
// 保存规格和配料
async function saveSpecsAndToppings(productId) {
// 获取现有的规格和配料
const existingResponse = await fetch(`/merchant/products/${productId}`, {
credentials: 'include'
});
const existingData = await existingResponse.json();
const existingSpecs = existingData.code === 200 ? (existingData.data.specs || []) : [];
const existingToppings = existingData.code === 200 ? (existingData.data.toppings || []) : [];
// 删除所有现有规格和配料
for (const spec of existingSpecs) {
if (spec.id) {
await fetch(`/merchant/products/specs/${spec.id}`, {
method: 'DELETE',
credentials: 'include'
});
}
}
for (const topping of existingToppings) {
if (topping.id) {
await fetch(`/merchant/products/toppings/${topping.id}`, {
method: 'DELETE',
credentials: 'include'
});
}
}
// 添加新的规格
for (const spec of specs) {
if (spec.specName && spec.specName.trim()) {
await fetch(`/merchant/products/${productId}/specs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
specName: spec.specName,
priceAdjust: spec.priceAdjust || 0
})
});
}
}
// 添加新的配料
for (const topping of toppings) {
if (topping.toppingName && topping.toppingName.trim()) {
await fetch(`/merchant/products/${productId}/toppings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
toppingName: topping.toppingName,
price: topping.price || 0
})
});
}
}
}
// 保存定制选项
async function saveCustoms(productId) {
// 获取现有的定制选项
const existingResponse = await fetch(`/merchant/products/${productId}/customs`, {
credentials: 'include'
});
const existingData = await existingResponse.json();
const existingCustoms = existingData.code === 200 ? (existingData.data || []) : [];
// 删除所有现有定制选项
for (const custom of existingCustoms) {
if (custom.id) {
await fetch(`/merchant/products/customs/${custom.id}`, {
method: 'DELETE',
credentials: 'include'
});
}
}
// 添加新的甜度选项
for (const custom of customs.sweetness) {
if (custom.optionValue && custom.optionValue.trim()) {
await fetch(`/merchant/products/${productId}/customs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
optionType: 'sweetness',
optionValue: custom.optionValue,
priceAdjust: custom.priceAdjust || 0
})
});
}
}
// 添加新的冰度选项
for (const custom of customs.ice) {
if (custom.optionValue && custom.optionValue.trim()) {
await fetch(`/merchant/products/${productId}/customs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
optionType: 'ice',
optionValue: custom.optionValue,
priceAdjust: custom.priceAdjust || 0
})
});
}
}
}
</script>
</body>
</html>