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

1550 lines
58 KiB
HTML
Raw Permalink Normal View History

2025-09-09 16:22:19 +00:00
<!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="store">发布店铺</div>
<div class="tab" data-tab="basic">基础信息</div>
2025-09-09 16:22:19 +00:00
<div class="tab" data-tab="sales">销售信息</div>
<div class="tab" data-tab="other">其他信息</div>
</div>
</div>
<!-- 表单内容 -->
<form id="addProductForm">
<!-- 发布店铺标签页 -->
<div id="store" class="tab-pane active">
<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="storeSelect" required>
<option value="">请选择需要发布的店铺</option>
<option value="store1">旗舰店</option>
<option value="store2">生鲜直营店</option>
<option value="store3">水果专营店</option>
<option value="store4">蔬菜专卖店</option>
<option value="store5">海鲜市场店</option>
</select>
</div>
<div class="error-message" id="storeSelectError" style="display: none;">不能为空。</div>
</div>
<div class="form-group">
<label class="form-label">店铺说明</label>
<div style="padding: 16px; background: #f5f5f5; border-radius: 6px; color: #666; font-size: 14px; line-height: 1.6;">
<p style="margin: 0 0 8px 0;"><strong>提示:</strong></p>
<p style="margin: 0;">• 请先选择要发布商品的店铺</p>
<p style="margin: 4px 0 0 0;">• 商品发布后将在所选店铺中展示</p>
<p style="margin: 4px 0 0 0;">• 同一商品可以发布到多个店铺</p>
</div>
</div>
</div>
</div>
2025-09-09 16:22:19 +00:00
<!-- 基础信息标签页 -->
<div id="basic" class="tab-pane">
2025-09-09 16:22:19 +00:00
<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 < 3) {
2025-09-09 16:22:19 +00:00
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 < 3) {
2025-09-09 16:22:19 +00:00
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 === 1) {
2025-09-09 16:22:19 +00:00
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 < 4; i++) {
2025-09-09 16:22:19 +00:00
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>