1526 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			1526 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			HTML
		
	
	
	
| <!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>
 | ||
|                             <div class="select-wrapper">
 | ||
|                                 <select class="form-select" id="status">
 | ||
|                                     <option value="warehouse">仓库中</option>
 | ||
|                                     <option value="online">立即上架</option>
 | ||
|                                 </select>
 | ||
|                             </div>
 | ||
|                         </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> |