File manager - Edit - /home/webapp69.cm.in.th/u69319090034/Shop 034/admin_product.php
Back
<?php // หลังบ้าน - หน้าเพิ่มสินค้าพร้อมระบบ Matrix Generator (admin_product.php) require_once 'auth.php'; requireSellerLogin(); require_once 'config.php'; try { $categories = $conn->query("SELECT * FROM categories")->fetchAll(); $sizeCharts = $conn->query("SELECT * FROM size_charts")->fetchAll(); } catch (PDOException $e) { die("เกิดข้อผิดพลาดในการโหลดข้อมูลส่วนเสริม: " . $e->getMessage()); } ?> <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>เพิ่มสินค้าแฟชั่นใหม่ (Matrix Generator) - AMARA STUDIO</title> <link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css"> <style> .attribute-tag { background-color: white; border: 1px solid var(--border); padding: 6px 12px; border-radius: 20px; display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; } .attribute-tag-remove { color: var(--danger); cursor: pointer; font-size: 16px; } .image-color-bind-select { margin-top: 5px; padding: 4px; border-radius: 4px; border: 1px solid var(--border); font-size: 12px; width: 100%; } </style> </head> <body> <!-- Header Navigation --> <header> <div class="nav-container"> <a href="index.php" class="logo">AMARA<span>STUDIO</span></a> <span style="background: var(--dark); color: white; padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 700; letter-spacing: 1px;">PORTAL ADMIN</span> <ul class="nav-menu"> <li style="display:flex; align-items:center; gap:8px; color: var(--text-muted); font-size:14px;"> <span>👤 <?= htmlspecialchars(getAdminName()) ?></span> </li> <li><a href="manual.php" class="nav-link" target="_blank">📖 คู่มือการใช้งาน</a></li> <li><a href="index.php" class="nav-link">หน้าร้านค้า</a></li> <li> <a href="logout.php" class="nav-link" id="logout-btn" style="color: var(--danger); font-weight: 600;" onclick="return confirmLogout(event)"> 🚪 ออกจากระบบ </a> </li> </ul> </div> </header> <div class="container"> <div class="admin-layout"> <!-- แผง Sidebar นำทางหลังบ้าน --> <aside class="admin-menu-sidebar"> <ul class="admin-menu-list"> <li class="admin-menu-item"><a href="admin.php">📊 แดชบอร์ดภาพรวม</a></li> <li class="admin-menu-item active"><a href="admin_product.php">➕ เพิ่มสินค้าใหม่ (Matrix Generator)</a></li> <li class="admin-menu-item"><a href="admin_orders.php">📦 จัดการออเดอร์ & ใบแพ็กของ</a></li> </ul> </aside> <!-- พื้นที่กรอกแบบฟอร์มสินค้าหลัก --> <main> <h1 style="margin-bottom: 25px; font-size: 24px; font-weight: 700;">เพิ่มสินค้าแฟชั่นใหม่เข้าระบบ</h1> <form id="product-form" onsubmit="saveProduct(event)"> <!-- 1. ข้อมูลพื้นฐานสินค้า --> <div class="admin-card"> <h2 style="font-size: 18px; font-weight: 700; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 10px;">1. ข้อมูลพื้นฐาน (Basic Details)</h2> <div class="form-group"> <label for="p-name">ชื่อสินค้าแฟชั่น *</label> <input type="text" id="p-name" placeholder="เช่น เดรสยาวเกาะอกซาตินพรีเมียม" required> </div> <div class="grid-three-cols"> <div class="form-group"> <label for="p-price">ราคาเริ่มต้น (Base Price) *</label> <input type="number" id="p-price" placeholder="เช่น 590" required oninput="suggestSku()"> </div> <div class="form-group"> <label for="p-category">หมวดหมู่สินค้า *</label> <select id="p-category" style="width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: 8px;" required> <option value="">-- เลือกหมวดหมู่ --</option> <?php foreach ($categories as $cat): ?> <option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option> <?php endforeach; ?> </select> </div> <div class="form-group"> <label for="p-size-chart">ตารางขนาดสินค้า (Size Chart) *</label> <select id="p-size-chart" style="width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: 8px;" required> <option value="">-- เลือกตารางไซซ์ --</option> <?php foreach ($sizeCharts as $chart): ?> <option value="<?= $chart['id'] ?>"><?= htmlspecialchars($chart['name']) ?></option> <?php endforeach; ?> </select> </div> </div> <div class="form-group"> <label for="p-desc">คำบรรยายและรายละเอียดสินค้า</label> <textarea id="p-desc" placeholder="เช่น ชนิดผ้า วิธีการซักดูแลรักษา สัดส่วนนางแบบพรีเซนเตอร์"></textarea> </div> </div> <!-- 2. ระบบจัดการรูปภาพสินค้า (Image Upload & Color Binding) --> <div class="admin-card"> <h2 style="font-size: 18px; font-weight: 700; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 10px;">2. จัดการรูปภาพสินค้า (Image Management)</h2> <p style="font-size: 13px; color: var(--text-muted); margin-bottom: 15px;"> อัปโหลดรูปภาพเสื้อผ้าของคุณ จากนั้นคุณสามารถ<strong>ผูกรูปภาพเฉพาะสี</strong>เพื่อเปลี่ยนภาพสินค้าในหน้าจอฝั่งลูกค้าทันทีเมื่อเลือกสีนั้นๆ </p> <!-- บริเวณลากวางอัปโหลดไฟล์จริง --> <div class="admin-uploader-box" onclick="triggerFileInput()"> <span style="font-size: 32px; display: block; margin-bottom: 10px;">📤</span> <strong>คลิกเพื่ออัปโหลดรูปภาพสินค้า</strong> <p style="color: var(--text-muted); font-size: 12px; margin-top: 5px;">รองรับนามสกุล JPG, PNG, WEBP แนะนำขนาด 4:5</p> <input type="file" id="image-file-input" style="display: none;" multiple accept="image/*" onchange="handleImageUpload(event)"> </div> <!-- ตารางพรีวิวรูปภาพเพื่อผูกสี --> <div class="admin-uploader-preview-grid" id="image-previews-grid"> <!-- ตัวอย่างพรีวิวจะสร้างขึ้นในส่วน JavaScript --> </div> </div> <!-- 3. จัดการตัวแปรแอตทริบิวต์ (Attributes Setup) --> <div class="admin-card"> <h2 style="font-size: 18px; font-weight: 700; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 10px;">3. กำหนดตัวเลือกแอตทริบิวต์ (Attributes Setup)</h2> <div class="attributes-setup-section"> <h3 style="font-size: 14px; margin-bottom: 15px;">เพิ่มตัวเลือก สี (Color Variants)</h3> <div class="attributes-input-group"> <input type="text" id="color-name-input" placeholder="ชื่อสี เช่น ชมพูพาสเทล, ดำสนิท"> <div style="display: flex; align-items: center; gap: 8px;"> <label style="font-size: 12px;">โค้ดสี HEX:</label> <input type="color" id="color-code-input" value="#ffc0cb" style="width: 50px; height: 35px; border: 1px solid var(--border); padding: 0; cursor: pointer;"> </div> <button type="button" class="btn btn-dark" onclick="addColorTag()">เพิ่มสี</button> </div> <div id="colors-list" style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;"> <!-- รายชื่อสีจะถูกแทรกเข้ามาในจุดนี้ --> </div> </div> <div class="attributes-setup-section" style="background-color: #fafafb; border: 1px solid var(--border);"> <h3 style="font-size: 14px; margin-bottom: 15px;">เพิ่มไซซ์สินค้า (Size Variants)</h3> <div class="attributes-input-group" style="grid-template-columns: 2fr auto;"> <input type="text" id="size-name-input" placeholder="ระบุไซซ์ เช่น S, M, L, XL, Free Size"> <button type="button" class="btn btn-dark" onclick="addSizeTag()">เพิ่มไซซ์</button> </div> <div id="sizes-list" style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;"> <!-- รายชื่อไซซ์จะถูกแทรกเข้ามาในจุดนี้ --> </div> </div> <div style="text-align: center; margin-top: 25px;"> <button type="button" class="btn btn-primary" style="padding: 12px 30px; font-size: 15px;" onclick="generateVariantMatrix()"> ⚙️ คำนวณและสร้างตาราง Variants (Cartesian Matrix) </button> </div> </div> <!-- 4. ตาราง Variant Matrix ย่อยสำหรับสต็อกและราคา --> <div class="admin-card variant-matrix-container" id="variant-matrix-section" style="display: none;"> <h2 style="font-size: 18px; font-weight: 700; margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 10px;">4. จัดการราคาสินค้าและสต็อกราย Variant (Product SKU Matrix)</h2> <p style="font-size: 13px; color: var(--text-muted); margin-bottom: 20px;"> ตารางถูกคำนวณแบบไขว้ตัวเลือกโดยอัตโนมัติ คุณสามารถเปลี่ยนรหัสสินค้า (SKU) เพิ่มเงินราคาส่วนต่าง (Price Modifier เช่น +50) และระบุจำนวนสต็อกแยกรายชิ้นได้ </p> <div style="overflow-x: auto;"> <table class="variant-matrix-table"> <thead> <tr> <th>สีสินค้า (Color)</th> <th>ไซซ์ (Size)</th> <th>รหัสสินค้า (SKU)*</th> <th>เพิ่มราคา (Price Modifier)</th> <th>สต็อกในคลัง (Stock Quantity)*</th> </tr> </thead> <tbody id="variant-matrix-rows"> <!-- แถวจะประมวลผลด้วย JS --> </tbody> </table> </div> <div class="flex-btn-group" style="justify-content: flex-end; margin-top: 30px;"> <a href="admin.php" class="btn btn-outline">ยกเลิก</a> <button type="submit" class="btn btn-dark" style="padding: 12px 30px;">💾 บันทึกสินค้าและเปิดขาย</button> </div> </div> </form> </main> </div> </div> <!-- JavaScript สำหรับระบบคำนวณ Matrix และ อัปโหลดรูปภาพ --> <script> let colors = []; // [{name: 'ชมพูพาสเทล', code: '#ffc0cb'}, ...] let sizes = []; // ['S', 'M', ...] let uploadedImages = []; // [{url: 'uploads/abc.jpg', color_name: null}, ...] function suggestSku() { // ฟังก์ชันอำนวยความสะดวกเฉยๆ } function triggerFileInput() { document.getElementById('image-file-input').click(); } // อัปโหลดไฟล์รูปภาพผ่าน AJAX function handleImageUpload(event) { const files = event.target.files; if (files.length === 0) return; for (let i = 0; i < files.length; i++) { const formData = new FormData(); formData.append('image', files[i]); fetch('api.php?action=upload_image', { method: 'POST', body: formData }) .then(res => res.json()) .then(data => { if (data.success) { uploadedImages.push({ url: data.url, color_name: null // เริ่มต้นแบบไม่มีการผูกสี }); renderImagePreviews(); } else { Swal.fire({ icon: 'error', title: 'อัปโหลดล้มเหลว', text: data.message, confirmButtonColor: '#c5a880' }); } }) .catch(err => { console.error(err); Swal.fire({ icon: 'error', title: 'เชื่อมต่อไม่ได้', text: 'ไม่สามารถส่งรูปภาพไปเซิร์ฟเวอร์ได้ กรุณาลองใหม่', confirmButtonColor: '#c5a880' }); }); } } // แสดงรายการพรีวิวรูปภาพเพื่อผูกสี function renderImagePreviews() { const grid = document.getElementById('image-previews-grid'); grid.innerHTML = ''; uploadedImages.forEach((img, index) => { const item = document.createElement('div'); item.className = 'uploader-preview-item'; // รูปพรีวิว const imageEl = document.createElement('img'); imageEl.src = img.url; item.appendChild(imageEl); // ปุ่มลบรูป const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'uploader-preview-remove'; removeBtn.innerHTML = '×'; removeBtn.onclick = () => { uploadedImages.splice(index, 1); renderImagePreviews(); }; item.appendChild(removeBtn); // กล่อง Dropdown ผูกสี const select = document.createElement('select'); select.className = 'image-color-bind-select'; select.onchange = (e) => { uploadedImages[index].color_name = e.target.value || null; }; const defaultOpt = document.createElement('option'); defaultOpt.value = ''; defaultOpt.innerText = 'รูปภาพทั่วไป (ไม่ผูกสี)'; select.appendChild(defaultOpt); colors.forEach(color => { const opt = document.createElement('option'); opt.value = color.name; opt.innerText = `ผูกกับสี: ${color.name}`; if (img.color_name === color.name) { opt.selected = true; } select.appendChild(opt); }); item.appendChild(select); grid.appendChild(item); }); } // เพิ่ม Tag สีสินค้า function addColorTag() { const nameInput = document.getElementById('color-name-input'); const codeInput = document.getElementById('color-code-input'); const name = nameInput.value.trim(); const code = codeInput.value; if (!name) { showToast('warning', 'กรุณากรอกชื่อสีก่อนกดเพิ่ม'); return; } // ตรวจไม่ให้ชื่อสีซ้ำ if (colors.some(c => c.name === name)) { showToast('warning', 'มีชื่อสีนี้อยู่ในรายการแล้ว'); return; } colors.push({ name: name, code: code }); nameInput.value = ''; renderColorTags(); renderImagePreviews(); // อัปเดต Dropdown ผูกสีที่พรีวิวรูป } function renderColorTags() { const list = document.getElementById('colors-list'); list.innerHTML = ''; colors.forEach((color, index) => { const tag = document.createElement('span'); tag.className = 'attribute-tag'; tag.innerHTML = ` <span style="display:inline-block; width:12px; height:12px; border-radius:50%; background-color:${color.code}; border:1px solid #ccc;"></span> ${color.name} <span class="attribute-tag-remove" onclick="removeColorTag(${index})">×</span> `; list.appendChild(tag); }); } function removeColorTag(index) { colors.splice(index, 1); renderColorTags(); renderImagePreviews(); } // เพิ่ม Tag ไซซ์เสื้อผ้า function addSizeTag() { const input = document.getElementById('size-name-input'); const size = input.value.trim().toUpperCase(); if (!size) { showToast('warning', 'กรุณากรอกไซซ์เสื้อผ้าก่อนกดเพิ่ม'); return; } if (sizes.includes(size)) { showToast('warning', 'มีไซซ์นี้อยู่ในรายการแล้ว'); return; } sizes.push(size); input.value = ''; renderSizeTags(); } function renderSizeTags() { const list = document.getElementById('sizes-list'); list.innerHTML = ''; sizes.forEach((size, index) => { const tag = document.createElement('span'); tag.className = 'attribute-tag'; tag.innerHTML = ` ${size} <span class="attribute-tag-remove" onclick="removeSizeTag(${index})">×</span> `; list.appendChild(tag); }); } function removeSizeTag(index) { sizes.splice(index, 1); renderSizeTags(); } // === ฟังก์ชันประมวลผล Cartesian Product (Color x Size Matrix) === function generateVariantMatrix() { if (colors.length === 0 || sizes.length === 0) { Swal.fire({ icon: 'info', title: 'ยังไม่มีตัวเลือกสินค้า', text: 'กรุณากำหนดตัวเลือกอย่างน้อย 1 สี และ 1 ไซซ์ ก่อนสร้าง SKU Matrix', confirmButtonColor: '#c5a880' }); return; } const tbody = document.getElementById('variant-matrix-rows'); tbody.innerHTML = ''; const productName = document.getElementById('p-name').value.trim() || 'PRODUCT'; // ทำความสะอาดชื่อสินค้าเพื่อนำไปเป็นพรีฟิกซ์รหัส SKU const skuPrefix = productName.substring(0, 8).toUpperCase().replace(/[^A-Z0-9]/g, ''); // วนคูณตัวเลือกสร้างแถวตาราง colors.forEach(color => { sizes.forEach(size => { const row = document.createElement('tr'); // 1. สี const colorTd = document.createElement('td'); colorTd.innerHTML = ` <div style="display:flex; align-items:center; gap:5px;"> <span style="width:10px; height:10px; border-radius:50%; background-color:${color.code};"></span> <strong>${color.name}</strong> </div> `; row.appendChild(colorTd); // 2. ไซซ์ const sizeTd = document.createElement('td'); sizeTd.innerHTML = `<strong>${size}</strong>`; row.appendChild(sizeTd); // 3. อินพุต SKU (Auto-fill ในรูปแบบ TSHIRT-COLOR-SIZE) const skuTd = document.createElement('td'); // แปลงชื่อสีเป็นอักษรละติน/รหัสย่อ หากเป็นภาษาไทยเพื่อความเป็นระบบ const colorShort = color.name.substring(0, 3).toUpperCase(); const suggestedSku = `${skuPrefix}-${colorShort}-${size}`; skuTd.innerHTML = `<input type="text" class="variant-sku" value="${suggestedSku}" data-color="${color.name}" data-size="${size}" required>`; row.appendChild(skuTd); // 4. ส่วนต่างราคา const priceTd = document.createElement('td'); priceTd.innerHTML = `<input type="number" class="variant-price-modifier" value="0.00" step="0.01">`; row.appendChild(priceTd); // 5. สต็อก const stockTd = document.createElement('td'); stockTd.innerHTML = `<input type="number" class="variant-stock" value="10" required min="0">`; row.appendChild(stockTd); tbody.appendChild(row); }); }); // เปิดแสดงตาราง Matrix document.getElementById('variant-matrix-section').style.display = 'block'; // เลื่อนหน้าจอไปที่ตาราง Matrix document.getElementById('variant-matrix-section').scrollIntoView({ behavior: 'smooth' }); } // บันทึกและส่งข้อมูลสินค้าทั้งหมดแบบ AJAX JSON Payload function saveProduct(event) { event.preventDefault(); const pName = document.getElementById('p-name').value.trim(); const pPrice = parseFloat(document.getElementById('p-price').value); const pCategory = document.getElementById('p-category').value; const pSizeChart = document.getElementById('p-size-chart').value; const pDesc = document.getElementById('p-desc').value.trim(); if (uploadedImages.length === 0) { Swal.fire({ icon: 'warning', title: 'ยังไม่มีรูปภาพสินค้า', text: 'กรุณาอัปโหลดรูปภาพสินค้าอย่างน้อย 1 รูป เพื่อจัดแสดงสินค้าฝั่งลูกค้า', confirmButtonColor: '#c5a880' }); return; } // รวบรวมข้อมูลรายการ Variants ย่อยจากตาราง Matrix const variantRows = document.querySelectorAll('#variant-matrix-rows tr'); const variantsData = []; variantRows.forEach(row => { const skuInput = row.querySelector('.variant-sku'); const priceModInput = row.querySelector('.variant-price-modifier'); const stockInput = row.querySelector('.variant-stock'); variantsData.push({ color_name: skuInput.getAttribute('data-color'), size_name: skuInput.getAttribute('data-size'), sku: skuInput.value.trim(), price_modifier: parseFloat(priceModInput.value) || 0.00, stock_quantity: parseInt(stockInput.value) || 0 }); }); const payload = { name: pName, base_price: pPrice, category_id: pCategory, size_chart_id: pSizeChart, description: pDesc, colors: colors, sizes: sizes, images: uploadedImages, variants: variantsData }; const submitBtn = document.querySelector('#variant-matrix-section button[type="submit"]'); submitBtn.disabled = true; submitBtn.innerText = 'กำลังสร้างสินค้าลงในฐานข้อมูล...'; fetch('api.php?action=admin_create_product', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(res => res.json()) .then(data => { if (data.success) { Swal.fire({ icon: 'success', title: 'สร้างสินค้าสำเร็จ! 🎉', text: 'สินค้าและ Variants SKU ทั้งหมดถูกบันทึกเรียบร้อยแล้ว กำลังพาไปยังหน้าจัดการสินค้า...', confirmButtonColor: '#c5a880', timer: 2500, timerProgressBar: true, showConfirmButton: false }).then(() => { window.location.href = 'admin.php'; }); } else { Swal.fire({ icon: 'error', title: 'บันทึกไม่สำเร็จ', text: data.message, confirmButtonColor: '#c5a880' }); submitBtn.disabled = false; submitBtn.innerText = '💾 บันทึกสินค้าและเปิดขาย'; } }) .catch(err => { console.error(err); Swal.fire({ icon: 'error', title: 'เกิดข้อผิดพลาด', text: 'ไม่สามารถบันทึกสินค้าได้ กรุณาลองใหม่อีกครั้ง', confirmButtonColor: '#c5a880' }); submitBtn.disabled = false; submitBtn.innerText = '💾 บันทึกสินค้าและเปิดขาย'; }); } // Toast notification เบาๆ สำหรับ Validation แจ้งเตือนที่ไม่รุนแรง function showToast(icon, message) { const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2500, timerProgressBar: true }); Toast.fire({ icon: icon, title: message }); } function confirmLogout(e) { e.preventDefault(); Swal.fire({ title: 'ออกจากระบบ?', text: 'ต้องการออกจากระบบผู้ดูแลใช่หรือไม่?', icon: 'question', showCancelButton: true, confirmButtonColor: '#e53e3e', cancelButtonColor: '#718096', confirmButtonText: 'ออกจากระบบ', cancelButtonText: 'ยกเลิก' }).then((result) => { if (result.isConfirmed) window.location.href = 'logout.php'; }); } </script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.3 |
proxy
|
phpinfo
|
Settings