dm-design/商家端web/商品管理/添加商品.html

1517 lines
56 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>添加商品 - 大妈集市商户端</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
background-color: #f5f5f5;
color: #333;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
}
.modal-header {
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
background: #fafafa;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #1a1a1a;
}
.close-btn {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #999;
padding: 4px;
border-radius: 4px;
transition: background-color 0.3s;
}
.close-btn:hover {
background-color: #f0f0f0;
}
.modal-body {
padding: 0;
}
.tabs-container {
border-bottom: 1px solid #f0f0f0;
}
.tabs {
display: flex;
padding: 0 24px;
}
.tab {
padding: 16px 20px;
cursor: pointer;
border-bottom: 2px solid transparent;
font-weight: 500;
color: #666;
transition: all 0.3s;
position: relative;
}
.tab.active {
color: #1890ff;
border-bottom-color: #1890ff;
}
.tab:hover:not(.active) {
color: #1890ff;
}
.tab-content {
padding: 24px;
}
.form-group {
margin-bottom: 24px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.required {
color: #ff4d4f;
}
.form-input, .form-select, .form-textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s, box-shadow 0.3s;
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.upload-area {
border: 2px dashed #d9d9d9;
border-radius: 6px;
padding: 32px;
text-align: center;
background: #fafafa;
transition: border-color 0.3s;
cursor: pointer;
}
.upload-area:hover {
border-color: #1890ff;
}
.upload-area.dragover {
border-color: #1890ff;
background: #f0f8ff;
}
.upload-icon {
font-size: 32px;
color: #d9d9d9;
margin-bottom: 12px;
}
.upload-text {
color: #666;
font-size: 14px;
}
.upload-highlight {
color: #1890ff;
text-decoration: none;
}
.upload-hint {
color: #999;
font-size: 12px;
margin-top: 8px;
}
.error-message {
color: #ff4d4f;
font-size: 12px;
margin-top: 4px;
}
.image-preview {
display: flex;
gap: 12px;
margin-top: 12px;
flex-wrap: wrap;
}
.preview-item {
position: relative;
width: 80px;
height: 80px;
border-radius: 6px;
overflow: hidden;
border: 1px solid #d9d9d9;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.preview-remove {
position: absolute;
top: 4px;
right: 4px;
background: rgba(0,0,0,0.5);
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-footer {
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
display: flex;
gap: 12px;
justify-content: flex-end;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
min-width: 80px;
}
.btn-primary {
background-color: #1890ff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #40a9ff;
}
.btn-secondary {
background-color: #f5f5f5;
color: #666;
border: 1px solid #d9d9d9;
}
.btn-secondary:hover:not(:disabled) {
border-color: #40a9ff;
color: #1890ff;
}
.btn:disabled {
background-color: #f5f5f5;
color: #d9d9d9;
cursor: not-allowed;
border-color: #f5f5f5;
}
.tab-pane {
display: none;
}
.tab-pane.active {
display: block;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.checkbox {
width: 16px;
height: 16px;
}
.number-input-group {
display: flex;
align-items: center;
gap: 8px;
}
.unit-text {
color: #666;
font-size: 14px;
}
.select-wrapper {
position: relative;
}
.select-wrapper::after {
content: '';
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #999;
pointer-events: none;
}
/* 规格配置弹窗样式 */
.spec-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.spec-modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.spec-modal-content {
position: relative;
width: 90%;
max-width: 1200px;
max-height: 90vh;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.spec-modal-header {
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
background: #fafafa;
}
.spec-modal-title {
font-size: 18px;
font-weight: 600;
color: #1a1a1a;
}
.spec-modal-body {
padding: 24px;
max-height: calc(90vh - 120px);
overflow-y: auto;
}
.spec-combinations {
margin-top: 24px;
}
.spec-config-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.spec-config-table th,
.spec-config-table td {
padding: 12px 8px;
text-align: left;
border: 1px solid #f0f0f0;
}
.spec-config-table th {
background-color: #fafafa;
font-weight: 600;
color: #333;
}
.spec-config-table td {
color: #666;
}
.spec-attr-input {
width: 120px;
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
}
.spec-attr-input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.spec-values-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.spec-value-tag {
display: inline-block;
padding: 4px 8px;
background-color: #f0f0f0;
border-radius: 4px;
font-size: 12px;
position: relative;
}
.spec-value-remove {
background: none;
border: none;
color: #ff4d4f;
cursor: pointer;
font-size: 14px;
padding: 0 4px;
margin-left: 4px;
}
.spec-value-input {
width: 150px;
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 12px;
}
.spec-value-input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.btn-sm {
padding: 4px 8px;
font-size: 12px;
min-width: auto;
}
.spec-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #f0f0f0;
font-size: 14px;
}
.spec-table th,
.spec-table td {
padding: 12px 8px;
text-align: center;
border: 1px solid #f0f0f0;
}
.spec-table th {
background-color: #fafafa;
font-weight: 600;
color: #333;
}
.spec-table td {
color: #666;
}
.spec-input {
width: 80px;
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 12px;
text-align: center;
}
.spec-input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.btn-danger {
background-color: #ff4d4f;
color: white;
border: none;
}
.btn-danger:hover {
background-color: #ff7875;
}
.btn-small {
padding: 4px 8px;
font-size: 12px;
min-width: auto;
}
.spec-modal-footer {
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
display: flex;
gap: 12px;
justify-content: flex-end;
}
/* 只读规格表格样式 */
.spec-readonly-container {
border: 1px solid #f0f0f0;
border-radius: 6px;
overflow: hidden;
background: #fafafa;
}
.spec-readonly-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
background: white;
}
.spec-readonly-table th,
.spec-readonly-table td {
padding: 10px 8px;
text-align: center;
border: 1px solid #f0f0f0;
}
.spec-readonly-table th {
background-color: #fafafa;
font-weight: 600;
color: #333;
}
.spec-readonly-table td {
color: #666;
background: white;
}
.spec-readonly-actions {
padding: 12px 16px;
background: #fafafa;
text-align: right;
}
</style>
</head>
<body>
<div class="container">
<!-- 模态框头部 -->
<div class="modal-header">
<h2 class="modal-title">添加商品</h2>
<button class="close-btn" onclick="closeModal()">×</button>
</div>
<!-- 模态框内容 -->
<div class="modal-body">
<!-- 标签页导航 -->
<div class="tabs-container">
<div class="tabs">
<div class="tab active" data-tab="basic">基础信息</div>
<div class="tab" data-tab="sales">销售信息</div>
<div class="tab" data-tab="other">其他信息</div>
</div>
</div>
<!-- 表单内容 -->
<form id="addProductForm">
<!-- 基础信息标签页 -->
<div id="basic" class="tab-pane active">
<div class="tab-content">
<div class="form-group">
<label class="form-label">
<span class="required">*</span>商品图片
</label>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📷</div>
<div class="upload-text">
将文件拖到此处,或<a href="#" class="upload-highlight">点击上传</a>
</div>
<div class="upload-hint">只能上传jpg/png文件且不超过500kb</div>
</div>
<div class="image-preview" id="imagePreview"></div>
<div class="error-message" id="imageError" style="display: none;">不能为空。</div>
<input type="file" id="fileInput" accept="image/jpeg,image/png" multiple style="display: none;">
</div>
<div class="form-group">
<label class="form-label">
<span class="required">*</span>商品类目
</label>
<div class="select-wrapper">
<select class="form-select" id="category" required>
<option value="">请选择商品类目</option>
<option value="vegetables">蔬菜类</option>
<option value="fruits">水果类</option>
<option value="meat">肉类</option>
<option value="seafood">海鲜类</option>
<option value="grains">粮油类</option>
<option value="dairy">乳制品</option>
<option value="beverages">饮料类</option>
<option value="snacks">零食类</option>
</select>
</div>
<div class="error-message" id="categoryError" style="display: none;">不能为空。</div>
</div>
<div class="form-group">
<label class="form-label">
<span class="required">*</span>商品名称
</label>
<input type="text" class="form-input" id="productName" placeholder="请输入商品名称" required>
<div class="error-message" id="nameError" style="display: none;">不能为空。</div>
</div>
<div class="form-group">
<label class="form-label">
<span class="required">*</span>商品描述
</label>
<textarea class="form-textarea" id="description" placeholder="请简单描述,如别称、口感、用途等" required></textarea>
<div class="error-message" id="descriptionError" style="display: none;">不能为空。</div>
</div>
</div>
</div>
<!-- 销售信息标签页 -->
<div id="sales" class="tab-pane">
<div class="tab-content">
<div class="form-group">
<label class="form-label">
<span class="required">*</span>销售单位
</label>
<div class="select-wrapper">
<select class="form-select" id="salesUnit" required>
<option value="">请选择销售单位</option>
<option value="piece"></option>
<option value="kg">公斤</option>
<option value="bag"></option>
<option value="box"></option>
<option value="bottle"></option>
<option value="pack"></option>
</select>
</div>
<div class="error-message" id="salesUnitError" style="display: none;">不能为空。</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">
<span class="required">*</span>成本
</label>
<div class="number-input-group">
<input type="number" class="form-input" id="costPrice" placeholder="请输入价格" min="0" step="0.01" required>
<span class="unit-text">单位(元)</span>
</div>
<div class="error-message" id="costPriceError" style="display: none;">不能为空。</div>
</div>
<div class="form-group">
<label class="form-label">
<span class="required">*</span>市场价
</label>
<div class="number-input-group">
<input type="number" class="form-input" id="marketPrice" placeholder="请输入市场价" min="0" step="0.01" required>
<span class="unit-text">单位(元)</span>
</div>
<div class="error-message" id="marketPriceError" style="display: none;">不能为空。</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">
<span class="required">*</span>库存
</label>
<input type="number" class="form-input" id="stock" placeholder="请输入库存" min="0" required>
<div class="error-message" id="stockError" style="display: none;">不能为空。</div>
</div>
<div class="form-group">
<label class="form-label">
<span class="required">*</span>重量
</label>
<div class="number-input-group">
<input type="number" class="form-input" id="weight" placeholder="请输入重量" min="0" step="0.01" required>
<span class="unit-text">KG</span>
</div>
<div class="error-message" id="weightError" style="display: none;">不能为空。</div>
</div>
</div>
<div class="form-group">
<label class="form-label">
<span class="required">*</span>体积
</label>
<div class="number-input-group">
<input type="number" class="form-input" id="volume" placeholder="请输入体积" min="0" step="0.01" required>
<span class="unit-text">立方米</span>
</div>
<div class="error-message" id="volumeError" style="display: none;">不能为空。</div>
</div>
<!-- 规格配置显示区域 -->
<div class="form-group">
<label class="form-label">规格配置</label>
<!-- 无规格配置时显示 -->
<div id="noSpecGroup">
<button type="button" class="btn btn-secondary" onclick="configureSpecs()">添加多规格配置</button>
<div style="margin-top: 8px; color: #999; font-size: 12px;">单规格商品无需配置</div>
</div>
<!-- 有规格配置时显示 -->
<div id="specDisplayGroup" style="display: none;">
<div class="spec-readonly-container">
<table class="spec-readonly-table" id="specReadonlyTable">
<thead>
<tr>
<th>序号</th>
<th>属性</th>
<th>成本(元)</th>
<th>市场价(元)</th>
<th>库存</th>
<th>重量(千克)</th>
<th>体积(立方米)</th>
</tr>
</thead>
<tbody id="specReadonlyTableBody">
</tbody>
</table>
<div class="spec-readonly-actions">
<button type="button" class="btn btn-secondary" onclick="configureSpecs()">修改规格配置</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 其他信息标签页 -->
<div id="other" class="tab-pane">
<div class="tab-content">
<div class="form-group">
<label class="form-label">产地</label>
<input type="text" class="form-input" id="origin" placeholder="请选择">
</div>
<div class="form-group">
<label class="form-label">保质期</label>
<div class="select-wrapper">
<select class="form-select" id="shelfLife">
<option value="">请填写保质期</option>
<option value="1day">1天</option>
<option value="3days">3天</option>
<option value="1week">1周</option>
<option value="2weeks">2周</option>
<option value="1month">1个月</option>
<option value="3months">3个月</option>
<option value="6months">6个月</option>
<option value="1year">1年</option>
<option value="2years">2年</option>
<option value="custom">自定义</option>
</select>
</div>
</div>
<div class="form-group" id="customShelfLifeGroup" style="display: none;">
<label class="form-label">自定义保质期</label>
<input type="text" class="form-input" id="customShelfLife" placeholder="请输入保质期">
</div>
<div class="form-group">
<label class="form-label">商品介绍图</label>
<div class="upload-area" id="productIntroUploadArea">
<div class="upload-icon">📷</div>
<div class="upload-text">
将文件拖到此处,或<a href="#" class="upload-highlight">点击上传</a>
</div>
<div class="upload-hint">支持mp4、avi、mov等视频格式建议不超过50MB</div>
</div>
<div class="image-preview" id="productIntroPreview"></div>
<input type="file" id="productIntroFileInput" accept="video/*,image/*" multiple style="display: none;">
</div>
<div class="form-group">
<label class="form-label">商品标签</label>
<input type="text" class="form-input" id="tags" placeholder="多个标签用逗号分隔,如:新鲜,有机,精选">
</div>
<div class="form-group">
<label class="form-label">备注信息</label>
<textarea class="form-textarea" id="remarks" placeholder="商品备注信息"></textarea>
</div>
<div class="form-group">
<label class="form-label">排序权重</label>
<div class="number-input-group">
<input type="number" class="form-input" id="sortOrder" placeholder="0" value="0">
<span class="unit-text">数值越大排序越靠前</span>
</div>
</div>
</div>
</div>
</form>
</div>
<!-- 模态框底部 -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="resetForm()">重置</button>
<button type="button" class="btn btn-primary" id="nextBtn" onclick="nextStep()">下一步</button>
<button type="button" class="btn btn-primary" id="saveBtn" onclick="saveProduct()" style="display: none;">保存并放入仓库</button>
<button type="button" class="btn btn-primary" id="publishBtn" onclick="publishProduct()" style="display: none;">保存并上架</button>
</div>
</div>
<!-- 规格配置弹窗 -->
<div id="specModal" class="spec-modal" style="display: none;">
<div class="spec-modal-overlay" onclick="closeSpecModal()"></div>
<div class="spec-modal-content">
<div class="spec-modal-header">
<h3 class="spec-modal-title">添加属性</h3>
<button class="close-btn" onclick="closeSpecModal()">×</button>
</div>
<div class="spec-modal-body">
<div class="form-group">
<label class="form-label">属性配置</label>
<table class="spec-config-table" id="specConfigTable">
<thead>
<tr>
<th>属性名</th>
<th>属性值</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" class="spec-attr-input" value="温度" data-attr="temperature"></td>
<td>
<div class="spec-values-container" data-attr="temperature">
<span class="spec-value-tag"><button type="button" class="spec-value-remove" onclick="removeSpecValue('temperature', '热')">×</button></span>
<span class="spec-value-tag"><button type="button" class="spec-value-remove" onclick="removeSpecValue('temperature', '冷')">×</button></span>
<button type="button" class="btn btn-secondary btn-sm" onclick="addSpecValue('temperature')">+增加属性值</button>
<input type="text" class="spec-value-input" placeholder="输入属性值按回车确认" style="display: none;" onkeypress="handleSpecValueInput(event, 'temperature')">
</div>
</td>
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeSpecAttribute('temperature')">删除</button></td>
</tr>
<tr>
<td><input type="text" class="spec-attr-input" value="体积" data-attr="volume"></td>
<td>
<div class="spec-values-container" data-attr="volume">
<span class="spec-value-tag"><button type="button" class="spec-value-remove" onclick="removeSpecValue('volume', '大')">×</button></span>
<span class="spec-value-tag"><button type="button" class="spec-value-remove" onclick="removeSpecValue('volume', '小')">×</button></span>
<button type="button" class="btn btn-secondary btn-sm" onclick="addSpecValue('volume')">+增加属性值</button>
<input type="text" class="spec-value-input" placeholder="输入属性值按回车确认" style="display: none;" onkeypress="handleSpecValueInput(event, 'volume')">
</div>
</td>
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeSpecAttribute('volume')">删除</button></td>
</tr>
</tbody>
</table>
<div style="margin-top: 10px;">
<button type="button" class="btn btn-secondary" onclick="addSpecAttribute()">+添加属性</button>
</div>
</div>
<!-- 规格组合表格 -->
<div class="spec-combinations">
<table class="spec-table">
<thead>
<tr>
<th>序号</th>
<th>属性</th>
<th>成本(元)</th>
<th>市场价(元)</th>
<th>库存</th>
<th>重量(千克)</th>
<th>体积(立方米)</th>
<th>操作</th>
</tr>
</thead>
<tbody id="specCombinationsTable">
</tbody>
</table>
</div>
</div>
<div class="spec-modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeSpecModal()">取消</button>
<button type="button" class="btn btn-primary" onclick="saveSpecConfig()">确定</button>
</div>
</div>
</div>
<script>
// 标签页切换
const tabs = document.querySelectorAll('.tab');
const tabPanes = document.querySelectorAll('.tab-pane');
let currentTabIndex = 0;
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => {
switchTab(index);
});
});
function switchTab(index) {
// 移除所有活动状态
tabs.forEach(tab => tab.classList.remove('active'));
tabPanes.forEach(pane => pane.classList.remove('active'));
// 激活当前标签页
tabs[index].classList.add('active');
tabPanes[index].classList.add('active');
currentTabIndex = index;
// 更新按钮显示
updateButtons();
}
function updateButtons() {
const nextBtn = document.getElementById('nextBtn');
const saveBtn = document.getElementById('saveBtn');
const publishBtn = document.getElementById('publishBtn');
if (currentTabIndex < 2) {
nextBtn.style.display = 'block';
saveBtn.style.display = 'none';
publishBtn.style.display = 'none';
} else {
nextBtn.style.display = 'none';
saveBtn.style.display = 'block';
publishBtn.style.display = 'block';
}
}
function nextStep() {
if (validateCurrentTab()) {
if (currentTabIndex < 2) {
switchTab(currentTabIndex + 1);
}
}
}
function validateCurrentTab() {
let isValid = true;
const currentPane = tabPanes[currentTabIndex];
const requiredFields = currentPane.querySelectorAll('[required]');
requiredFields.forEach(field => {
const errorElement = document.getElementById(field.id + 'Error');
if (!field.value.trim()) {
if (errorElement) {
errorElement.style.display = 'block';
}
isValid = false;
} else {
if (errorElement) {
errorElement.style.display = 'none';
}
}
});
// 特殊验证:图片上传
if (currentTabIndex === 0) {
const imagePreview = document.getElementById('imagePreview');
const imageError = document.getElementById('imageError');
if (imagePreview.children.length === 0) {
imageError.style.display = 'block';
isValid = false;
} else {
imageError.style.display = 'none';
}
}
return isValid;
}
// 文件上传功能
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const imagePreview = document.getElementById('imagePreview');
uploadArea.addEventListener('click', () => {
fileInput.click();
});
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
handleFiles(files);
});
fileInput.addEventListener('change', (e) => {
handleFiles(e.target.files);
});
function handleFiles(files) {
Array.from(files).forEach(file => {
if (file.type.startsWith('image/') && file.size <= 500 * 1024) {
const reader = new FileReader();
reader.onload = (e) => {
addImagePreview(e.target.result, file.name);
};
reader.readAsDataURL(file);
} else {
alert('请选择小于500KB的图片文件');
}
});
}
function addImagePreview(src, name) {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
previewItem.innerHTML = `
<img src="${src}" alt="${name}" class="preview-image">
<button type="button" class="preview-remove" onclick="removeImage(this)">×</button>
`;
imagePreview.appendChild(previewItem);
}
function removeImage(button) {
button.parentElement.remove();
}
// 保质期自定义选项
const shelfLifeSelect = document.getElementById('shelfLife');
const customShelfLifeGroup = document.getElementById('customShelfLifeGroup');
shelfLifeSelect.addEventListener('change', () => {
customShelfLifeGroup.style.display = shelfLifeSelect.value === 'custom' ? 'block' : 'none';
});
// 商品介绍图上传功能
const productIntroUploadArea = document.getElementById('productIntroUploadArea');
const productIntroFileInput = document.getElementById('productIntroFileInput');
const productIntroPreview = document.getElementById('productIntroPreview');
productIntroUploadArea.addEventListener('click', () => {
productIntroFileInput.click();
});
productIntroUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
productIntroUploadArea.classList.add('dragover');
});
productIntroUploadArea.addEventListener('dragleave', () => {
productIntroUploadArea.classList.remove('dragover');
});
productIntroUploadArea.addEventListener('drop', (e) => {
e.preventDefault();
productIntroUploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
handleProductIntroFiles(files);
});
productIntroFileInput.addEventListener('change', (e) => {
handleProductIntroFiles(e.target.files);
});
function handleProductIntroFiles(files) {
Array.from(files).forEach(file => {
if ((file.type.startsWith('image/') || file.type.startsWith('video/')) && file.size <= 50 * 1024 * 1024) {
const reader = new FileReader();
reader.onload = (e) => {
addProductIntroPreview(e.target.result, file.name, file.type);
};
reader.readAsDataURL(file);
} else {
alert('请选择小于50MB的图片或视频文件');
}
});
}
function addProductIntroPreview(src, name, type) {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
if (type.startsWith('video/')) {
previewItem.innerHTML = `
<video src="${src}" class="preview-image" controls></video>
<button type="button" class="preview-remove" onclick="removeProductIntroMedia(this)">×</button>
`;
} else {
previewItem.innerHTML = `
<img src="${src}" alt="${name}" class="preview-image">
<button type="button" class="preview-remove" onclick="removeProductIntroMedia(this)">×</button>
`;
}
productIntroPreview.appendChild(previewItem);
}
function removeProductIntroMedia(button) {
button.parentElement.remove();
}
// 规格配置功能
function configureSpecs() {
document.getElementById('specModal').style.display = 'flex';
// 初始化现有属性值的显示
updateSpecValuesDisplay('temperature');
updateSpecValuesDisplay('volume');
// 初始化规格表格
regenerateSpecTable();
}
function closeSpecModal() {
document.getElementById('specModal').style.display = 'none';
}
// 规格配置数据管理
let specData = {
temperature: ['热', '冷'],
volume: ['大', '小']
};
// 删除属性
function deleteAttribute(attributeName) {
if (confirm(`确定要删除${attributeName === 'temperature' ? '温度' : '体积'}属性吗?`)) {
delete specData[attributeName];
updateSpecModal();
regenerateSpecTable();
}
}
// 删除规格选项
function removeOption(attributeName, index) {
specData[attributeName].splice(index, 1);
if (specData[attributeName].length === 0) {
delete specData[attributeName];
}
updateSpecModal();
regenerateSpecTable();
}
// 添加规格选项
function addOption(attributeName) {
const optionName = prompt(`请输入新的${attributeName === 'temperature' ? '温度' : '体积'}规格:`);
if (optionName && optionName.trim()) {
specData[attributeName].push(optionName.trim());
updateSpecModal();
regenerateSpecTable();
}
}
// 添加新属性
function addSpecAttribute() {
const tableBody = document.querySelector('#specConfigTable tbody');
const attributeKey = 'attribute' + Date.now(); // 使用时间戳作为唯一标识
const newRow = document.createElement('tr');
newRow.innerHTML = `
<td><input type="text" class="spec-attr-input" value="新属性" data-attr="${attributeKey}"></td>
<td>
<div class="spec-values-container" data-attr="${attributeKey}">
<button type="button" class="btn btn-secondary btn-sm" onclick="addSpecValue('${attributeKey}')">+增加属性值</button>
<input type="text" class="spec-value-input" placeholder="输入属性值按回车确认" style="display: none;" onkeypress="handleSpecValueInput(event, '${attributeKey}')">
</div>
</td>
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeSpecAttribute('${attributeKey}')">删除</button></td>
`;
tableBody.appendChild(newRow);
// 初始化新属性的数据
specData[attributeKey] = [];
}
// 增加属性值
function addSpecValue(attributeKey) {
const container = document.querySelector(`.spec-values-container[data-attr="${attributeKey}"]`);
const input = container.querySelector('.spec-value-input');
const addButton = container.querySelector('.btn-sm');
// 隐藏按钮,显示输入框
addButton.style.display = 'none';
input.style.display = 'inline-block';
input.focus();
}
// 处理属性值输入
function handleSpecValueInput(event, attributeKey) {
if (event.key === 'Enter') {
const inputValue = event.target.value.trim();
if (inputValue) {
// 添加属性值到数据
if (!specData[attributeKey]) {
specData[attributeKey] = [];
}
specData[attributeKey].push(inputValue);
// 更新显示
updateSpecValuesDisplay(attributeKey);
// 清空输入框并重新显示按钮
event.target.value = '';
event.target.style.display = 'none';
const addButton = event.target.parentNode.querySelector('.btn-sm');
addButton.style.display = 'inline-block';
// 重新生成规格组合表格
regenerateSpecTable();
}
}
}
// 更新属性值显示
function updateSpecValuesDisplay(attributeKey) {
const container = document.querySelector(`.spec-values-container[data-attr="${attributeKey}"]`);
if (!container) return;
// 清空容器(保留输入框和按钮)
const input = container.querySelector('.spec-value-input');
const addButton = container.querySelector('.btn-sm');
container.innerHTML = '';
container.appendChild(addButton);
container.appendChild(input);
// 添加所有属性值标签
if (specData[attributeKey]) {
specData[attributeKey].forEach(value => {
const tag = document.createElement('span');
tag.className = 'spec-value-tag';
tag.innerHTML = `${value} <button type="button" class="spec-value-remove" onclick="removeSpecValue('${attributeKey}', '${value}')">×</button>`;
container.insertBefore(tag, addButton);
});
}
}
// 删除属性值
function removeSpecValue(attributeKey, value) {
if (specData[attributeKey]) {
const index = specData[attributeKey].indexOf(value);
if (index > -1) {
specData[attributeKey].splice(index, 1);
updateSpecValuesDisplay(attributeKey);
regenerateSpecTable();
}
}
}
// 删除整个属性
function removeSpecAttribute(attributeKey) {
// 删除数据
delete specData[attributeKey];
// 删除表格行
const row = document.querySelector(`input[data-attr="${attributeKey}"]`).closest('tr');
row.remove();
// 重新生成规格表格
regenerateSpecTable();
}
// 监听属性名称变化
function setupAttributeNameListener() {
document.addEventListener('input', function(event) {
if (event.target.classList.contains('spec-attr-input')) {
// 属性名称变化时重新生成规格表格
regenerateSpecTable();
}
});
}
// 生成所有规格组合
function generateCombinations(attributes) {
const keys = Object.keys(attributes);
if (keys.length === 0) return [];
function combine(arrays) {
if (arrays.length === 1) return arrays[0].map(item => [item]);
const result = [];
const firstArray = arrays[0];
const remainingCombinations = combine(arrays.slice(1));
for (const item of firstArray) {
for (const combination of remainingCombinations) {
result.push([item, ...combination]);
}
}
return result;
}
const attributeArrays = keys.map(key => attributes[key]);
return combine(attributeArrays);
}
// 重新生成规格表格
function regenerateSpecTable() {
// 获取实际的属性数据
const actualSpecData = {};
const rows = document.querySelectorAll('#specConfigTable tbody tr');
rows.forEach(row => {
const attrInput = row.querySelector('.spec-attr-input');
const attrKey = attrInput.getAttribute('data-attr');
const attrName = attrInput.value.trim();
if (attrName && specData[attrKey] && specData[attrKey].length > 0) {
actualSpecData[attrName] = [...specData[attrKey]];
}
});
const combinations = generateCombinations(actualSpecData);
const tableBody = document.getElementById('specCombinationsTable');
if (!tableBody) return;
tableBody.innerHTML = combinations.map((combination, index) => `
<tr>
<td>${index + 1}</td>
<td>${combination.join('')}</td>
<td><input type="number" class="spec-input" value="0" min="0" step="0.01"></td>
<td><input type="number" class="spec-input" value="0" min="0" step="0.01"></td>
<td><input type="number" class="spec-input" value="0" min="0"></td>
<td><input type="number" class="spec-input" value="0" min="0" step="0.01"></td>
<td><input type="number" class="spec-input" value="0" min="0" step="0.01"></td>
<td><button type="button" class="btn btn-danger btn-small" onclick="deleteSpecRow(this)">删除</button></td>
</tr>
`).join('');
}
// 删除规格行
function deleteSpecRow(button) {
button.closest('tr').remove();
}
// 保存规格配置
function saveSpecConfig() {
const tableRows = document.querySelectorAll('#specCombinationsTable tr');
const specConfigs = [];
tableRows.forEach(row => {
const inputs = row.querySelectorAll('.spec-input');
const spec = row.cells[1].textContent;
specConfigs.push({
spec: spec,
cost: parseFloat(inputs[0].value) || 0,
marketPrice: parseFloat(inputs[1].value) || 0,
stock: parseInt(inputs[2].value) || 0,
weight: parseFloat(inputs[3].value) || 0,
volume: parseFloat(inputs[4].value) || 0
});
});
// 更新销售信息中的只读表格
updateReadonlySpecTable(specConfigs);
console.log('规格配置已保存:', specConfigs);
alert('规格配置已保存');
closeSpecModal();
}
// 更新销售信息中的只读规格表格
function updateReadonlySpecTable(specConfigs) {
const noSpecGroup = document.getElementById('noSpecGroup');
const specDisplayGroup = document.getElementById('specDisplayGroup');
const tableBody = document.getElementById('specReadonlyTableBody');
if (specConfigs && specConfigs.length > 0) {
// 隐藏无规格区域,显示规格配置区域
noSpecGroup.style.display = 'none';
specDisplayGroup.style.display = 'block';
// 更新表格内容
tableBody.innerHTML = specConfigs.map((config, index) => `
<tr>
<td>${index + 1}</td>
<td>${config.spec}</td>
<td>${config.cost.toFixed(2)}</td>
<td>${config.marketPrice.toFixed(2)}</td>
<td>${config.stock}</td>
<td>${config.weight.toFixed(2)}</td>
<td>${config.volume.toFixed(2)}</td>
</tr>
`).join('');
} else {
// 显示无规格区域,隐藏规格配置区域
noSpecGroup.style.display = 'block';
specDisplayGroup.style.display = 'none';
}
}
// 检查是否有规格配置并更新显示
function checkAndUpdateSpecDisplay() {
// 获取实际的属性数据
const actualSpecData = {};
const rows = document.querySelectorAll('#specConfigTable tbody tr');
rows.forEach(row => {
const attrInput = row.querySelector('.spec-attr-input');
const attrKey = attrInput.getAttribute('data-attr');
const attrName = attrInput.value.trim();
if (attrName && specData[attrKey] && specData[attrKey].length > 0) {
actualSpecData[attrName] = [...specData[attrKey]];
}
});
const combinations = generateCombinations(actualSpecData);
if (combinations.length > 0) {
const defaultConfigs = combinations.map((combination, index) => ({
spec: combination.join(''),
cost: 0,
marketPrice: 0,
stock: 0,
weight: 0,
volume: 0
}));
updateReadonlySpecTable(defaultConfigs);
} else {
updateReadonlySpecTable([]);
}
}
// 表单操作
function resetForm() {
document.getElementById('addProductForm').reset();
imagePreview.innerHTML = '';
switchTab(0);
hideAllErrors();
}
function hideAllErrors() {
document.querySelectorAll('.error-message').forEach(error => {
error.style.display = 'none';
});
}
function saveProduct() {
if (validateAllTabs()) {
alert('商品已保存到仓库');
// 这里添加保存逻辑
}
}
function publishProduct() {
if (validateAllTabs()) {
alert('商品已保存并上架');
// 这里添加发布逻辑
}
}
function validateAllTabs() {
let allValid = true;
for (let i = 0; i < 3; i++) {
const originalIndex = currentTabIndex;
currentTabIndex = i;
if (!validateCurrentTab()) {
allValid = false;
if (originalIndex !== i) {
switchTab(i);
}
break;
}
currentTabIndex = originalIndex;
}
return allValid;
}
function closeModal() {
if (confirm('确定要关闭吗?未保存的数据将丢失。')) {
// 关闭模态框逻辑
window.history.back();
}
}
// 初始化
updateButtons();
// 初始化规格表格
document.addEventListener('DOMContentLoaded', function() {
regenerateSpecTable();
setupAttributeNameListener();
checkAndUpdateSpecDisplay();
});
</script>
</body>
</html>