Update app.py
Browse files
app.py
CHANGED
|
@@ -190,6 +190,7 @@ CATALOG_TEMPLATE = '''
|
|
| 190 |
.photo-count { position: absolute; bottom: 5px; right: 5px; background: rgba(0,0,0,0.6); color: white; font-size: 0.7rem; padding: 2px 6px; border-radius: 10px; pointer-events: none; }
|
| 191 |
.product-info { flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between; min-width: 0; padding: 5px 0; }
|
| 192 |
.product-title { font-size: 0.95rem; font-weight: 600; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
|
|
| 193 |
.product-bottom { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; }
|
| 194 |
.product-price { font-weight: 700; font-size: 1rem; color: var(--primary); }
|
| 195 |
.quantity-control { display: flex; align-items: center; background: var(--bg); border-radius: 8px; overflow: hidden; border: 1px solid var(--border); }
|
|
@@ -366,6 +367,7 @@ CATALOG_TEMPLATE = '''
|
|
| 366 |
|
| 367 |
const photoIndicator = hasPhotos && p.photos.length > 1 ? `<div class="photo-count"><i class="fas fa-images"></i> ${p.photos.length}</div>` : '';
|
| 368 |
const imgClick = hasPhotos ? `onclick="openGallery('${p.product_id}')"` : '';
|
|
|
|
| 369 |
|
| 370 |
const div = document.createElement('div');
|
| 371 |
div.className = 'product-card';
|
|
@@ -375,7 +377,10 @@ CATALOG_TEMPLATE = '''
|
|
| 375 |
${photoIndicator}
|
| 376 |
</div>
|
| 377 |
<div class="product-info">
|
| 378 |
-
<div
|
|
|
|
|
|
|
|
|
|
| 379 |
<div class="product-bottom">
|
| 380 |
<div class="product-price">${p.price} ${currency}</div>
|
| 381 |
<div class="quantity-control">
|
|
@@ -700,16 +705,19 @@ ADMIN_TEMPLATE = '''
|
|
| 700 |
.card { background: var(--surface); padding: 20px; border-radius: 16px; box-shadow: 0 4px 15px rgba(0,0,0,0.03); margin-bottom: 20px; }
|
| 701 |
.card h2 { margin-top: 0; margin-bottom: 15px; font-size: 1.2rem; }
|
| 702 |
|
| 703 |
-
input[type="text"], input[type="number"], select { width: 100%; padding: 12px 15px; border: 1px solid var(--border); border-radius: 10px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; background: #fafafa; }
|
| 704 |
-
input[type="text"]:focus, input[type="number"]:focus { border-color: var(--info); background: #fff; }
|
|
|
|
| 705 |
|
| 706 |
.add-cat-form { display: flex; gap: 10px; flex-wrap: wrap; }
|
| 707 |
.add-cat-form input { flex: 1; min-width: 200px; }
|
| 708 |
.add-cat-form button { white-space: nowrap; }
|
| 709 |
|
| 710 |
.category-block { border: 1px solid var(--border); margin-bottom: 15px; border-radius: 12px; overflow: hidden; background: #fff; }
|
| 711 |
-
.category-header { background: #fafafa; padding: 15px 20px; font-weight: 700; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); }
|
| 712 |
-
.category-
|
|
|
|
|
|
|
| 713 |
|
| 714 |
.product-item { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-bottom: 1px solid var(--border); flex-wrap: wrap; gap: 10px; }
|
| 715 |
.product-item:last-child { border-bottom: none; }
|
|
@@ -717,9 +725,15 @@ ADMIN_TEMPLATE = '''
|
|
| 717 |
.product-img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; border: 1px solid #eee; background: #fafafa; }
|
| 718 |
.product-details { display: flex; flex-direction: column; }
|
| 719 |
.product-name { font-weight: 600; font-size: 0.95rem; }
|
| 720 |
-
.product-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
|
| 722 |
-
.add-product-form { background: #fdfdfd; padding: 20px;
|
| 723 |
.form-row { display: flex; gap: 10px; flex-wrap: wrap; }
|
| 724 |
.form-row > * { flex: 1; min-width: 150px; }
|
| 725 |
|
|
@@ -762,16 +776,41 @@ ADMIN_TEMPLATE = '''
|
|
| 762 |
|
| 763 |
{% for category in categories %}
|
| 764 |
<div class="category-block">
|
| 765 |
-
<div class="category-header">
|
| 766 |
-
<
|
| 767 |
-
|
|
|
|
|
|
|
|
|
|
| 768 |
<input type="hidden" name="action" value="delete_category">
|
| 769 |
<input type="hidden" name="category_name" value="{{ category }}">
|
| 770 |
<button type="submit" class="btn btn-danger"><i class="fas fa-trash-alt"></i></button>
|
| 771 |
</form>
|
| 772 |
</div>
|
| 773 |
-
<div class="category-content">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
{% for product in products %}
|
| 776 |
{% if product.category == category %}
|
| 777 |
<div class="product-item">
|
|
@@ -783,6 +822,9 @@ ADMIN_TEMPLATE = '''
|
|
| 783 |
{% endif %}
|
| 784 |
<div class="product-details">
|
| 785 |
<span class="product-name">{{ product.name }}</span>
|
|
|
|
|
|
|
|
|
|
| 786 |
<span class="product-meta">{{ product.price }} {{ currency_code }} • Фото: {{ product.photos|length if product.photos else 0 }}/10</span>
|
| 787 |
</div>
|
| 788 |
</div>
|
|
@@ -794,21 +836,6 @@ ADMIN_TEMPLATE = '''
|
|
| 794 |
</div>
|
| 795 |
{% endif %}
|
| 796 |
{% endfor %}
|
| 797 |
-
|
| 798 |
-
<form class="add-product-form" method="POST" enctype="multipart/form-data" onsubmit="showLoading(this)">
|
| 799 |
-
<input type="hidden" name="action" value="add_product">
|
| 800 |
-
<input type="hidden" name="category" value="{{ category }}">
|
| 801 |
-
<div style="font-weight: 600; font-size: 0.9rem; color: #636e72;">Добавить товар в "{{ category }}"</div>
|
| 802 |
-
<div class="form-row">
|
| 803 |
-
<input type="text" name="name" placeholder="Название товара" required autocomplete="off" style="flex:2;">
|
| 804 |
-
<input type="number" name="price" placeholder="Цена" required step="0.01" style="flex:1;">
|
| 805 |
-
</div>
|
| 806 |
-
<div class="file-input-wrapper">
|
| 807 |
-
<input type="file" name="photos" accept="image/*" multiple max="10" required>
|
| 808 |
-
<div style="font-size: 0.8rem; color: #999; margin-top: 5px;">Можно выбрать до 10 фото</div>
|
| 809 |
-
</div>
|
| 810 |
-
<button type="submit" class="btn btn-success" style="width: 100%; justify-content: center;"><i class="fas fa-plus-circle"></i> Добавить товар</button>
|
| 811 |
-
</form>
|
| 812 |
</div>
|
| 813 |
</div>
|
| 814 |
{% endfor %}
|
|
@@ -820,6 +847,25 @@ ADMIN_TEMPLATE = '''
|
|
| 820 |
btn.style.pointerEvents = 'none';
|
| 821 |
btn.style.opacity = '0.7';
|
| 822 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
</script>
|
| 824 |
</body>
|
| 825 |
</html>
|
|
@@ -913,6 +959,7 @@ def admin():
|
|
| 913 |
elif action == 'add_product':
|
| 914 |
name = request.form.get('name', '').strip()
|
| 915 |
price = float(request.form.get('price', 0))
|
|
|
|
| 916 |
category = request.form.get('category')
|
| 917 |
uploaded_photos = request.files.getlist('photos')[:10]
|
| 918 |
|
|
@@ -948,6 +995,7 @@ def admin():
|
|
| 948 |
'product_id': uuid4().hex,
|
| 949 |
'name': name,
|
| 950 |
'price': price,
|
|
|
|
| 951 |
'category': category,
|
| 952 |
'photos': photos_list
|
| 953 |
}
|
|
|
|
| 190 |
.photo-count { position: absolute; bottom: 5px; right: 5px; background: rgba(0,0,0,0.6); color: white; font-size: 0.7rem; padding: 2px 6px; border-radius: 10px; pointer-events: none; }
|
| 191 |
.product-info { flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between; min-width: 0; padding: 5px 0; }
|
| 192 |
.product-title { font-size: 0.95rem; font-weight: 600; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
| 193 |
+
.product-desc { font-size: 0.8rem; color: var(--text-muted); margin-top: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
| 194 |
.product-bottom { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; }
|
| 195 |
.product-price { font-weight: 700; font-size: 1rem; color: var(--primary); }
|
| 196 |
.quantity-control { display: flex; align-items: center; background: var(--bg); border-radius: 8px; overflow: hidden; border: 1px solid var(--border); }
|
|
|
|
| 367 |
|
| 368 |
const photoIndicator = hasPhotos && p.photos.length > 1 ? `<div class="photo-count"><i class="fas fa-images"></i> ${p.photos.length}</div>` : '';
|
| 369 |
const imgClick = hasPhotos ? `onclick="openGallery('${p.product_id}')"` : '';
|
| 370 |
+
const descHtml = p.description ? `<div class="product-desc">${p.description}</div>` : '';
|
| 371 |
|
| 372 |
const div = document.createElement('div');
|
| 373 |
div.className = 'product-card';
|
|
|
|
| 377 |
${photoIndicator}
|
| 378 |
</div>
|
| 379 |
<div class="product-info">
|
| 380 |
+
<div>
|
| 381 |
+
<div class="product-title">${p.name}</div>
|
| 382 |
+
${descHtml}
|
| 383 |
+
</div>
|
| 384 |
<div class="product-bottom">
|
| 385 |
<div class="product-price">${p.price} ${currency}</div>
|
| 386 |
<div class="quantity-control">
|
|
|
|
| 705 |
.card { background: var(--surface); padding: 20px; border-radius: 16px; box-shadow: 0 4px 15px rgba(0,0,0,0.03); margin-bottom: 20px; }
|
| 706 |
.card h2 { margin-top: 0; margin-bottom: 15px; font-size: 1.2rem; }
|
| 707 |
|
| 708 |
+
input[type="text"], input[type="number"], select, textarea { width: 100%; padding: 12px 15px; border: 1px solid var(--border); border-radius: 10px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; background: #fafafa; }
|
| 709 |
+
input[type="text"]:focus, input[type="number"]:focus, textarea:focus { border-color: var(--info); background: #fff; }
|
| 710 |
+
textarea { resize: vertical; min-height: 80px; font-family: inherit; }
|
| 711 |
|
| 712 |
.add-cat-form { display: flex; gap: 10px; flex-wrap: wrap; }
|
| 713 |
.add-cat-form input { flex: 1; min-width: 200px; }
|
| 714 |
.add-cat-form button { white-space: nowrap; }
|
| 715 |
|
| 716 |
.category-block { border: 1px solid var(--border); margin-bottom: 15px; border-radius: 12px; overflow: hidden; background: #fff; }
|
| 717 |
+
.category-header { background: #fafafa; padding: 15px 20px; font-weight: 700; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.2s; }
|
| 718 |
+
.category-header:hover { background: #f0f0f0; }
|
| 719 |
+
.category-content { padding: 0; display: none; }
|
| 720 |
+
.category-content.active { display: block; }
|
| 721 |
|
| 722 |
.product-item { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-bottom: 1px solid var(--border); flex-wrap: wrap; gap: 10px; }
|
| 723 |
.product-item:last-child { border-bottom: none; }
|
|
|
|
| 725 |
.product-img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; border: 1px solid #eee; background: #fafafa; }
|
| 726 |
.product-details { display: flex; flex-direction: column; }
|
| 727 |
.product-name { font-weight: 600; font-size: 0.95rem; }
|
| 728 |
+
.product-desc { font-size: 0.85rem; color: #636e72; margin-top: 2px; }
|
| 729 |
+
.product-meta { font-size: 0.8rem; color: #b2bec3; margin-top: 4px; }
|
| 730 |
+
|
| 731 |
+
.add-product-wrapper { display: none; }
|
| 732 |
+
.add-product-wrapper.active { display: block; }
|
| 733 |
+
.toggle-add-product { width: 100%; text-align: center; background: #fafafa; padding: 15px; cursor: pointer; color: var(--success); font-weight: 600; transition: background 0.2s; border-bottom: 1px solid var(--border); }
|
| 734 |
+
.toggle-add-product:hover { background: #f0f0f0; }
|
| 735 |
|
| 736 |
+
.add-product-form { background: #fdfdfd; padding: 20px; display: flex; flex-direction: column; gap: 15px; }
|
| 737 |
.form-row { display: flex; gap: 10px; flex-wrap: wrap; }
|
| 738 |
.form-row > * { flex: 1; min-width: 150px; }
|
| 739 |
|
|
|
|
| 776 |
|
| 777 |
{% for category in categories %}
|
| 778 |
<div class="category-block">
|
| 779 |
+
<div class="category-header" onclick="toggleCategory('cat-{{ loop.index }}')">
|
| 780 |
+
<div style="display: flex; align-items: center; gap: 10px;">
|
| 781 |
+
<i class="fas fa-chevron-down" id="icon-cat-{{ loop.index }}" style="color: #636e72;"></i>
|
| 782 |
+
<span><i class="fas fa-folder-open" style="color:var(--info); margin-right:5px;"></i> {{ category }}</span>
|
| 783 |
+
</div>
|
| 784 |
+
<form method="POST" style="margin:0;" onclick="event.stopPropagation();" onsubmit="return confirm('Удалить категорию и все ее товары?');">
|
| 785 |
<input type="hidden" name="action" value="delete_category">
|
| 786 |
<input type="hidden" name="category_name" value="{{ category }}">
|
| 787 |
<button type="submit" class="btn btn-danger"><i class="fas fa-trash-alt"></i></button>
|
| 788 |
</form>
|
| 789 |
</div>
|
| 790 |
+
<div class="category-content" id="cat-{{ loop.index }}">
|
| 791 |
+
|
| 792 |
+
<div class="toggle-add-product" onclick="toggleAddProduct('add-prod-{{ loop.index }}')">
|
| 793 |
+
<i class="fas fa-plus"></i> Добавить товар
|
| 794 |
+
</div>
|
| 795 |
|
| 796 |
+
<div class="add-product-wrapper" id="add-prod-{{ loop.index }}">
|
| 797 |
+
<form class="add-product-form" method="POST" enctype="multipart/form-data" onsubmit="showLoading(this)">
|
| 798 |
+
<input type="hidden" name="action" value="add_product">
|
| 799 |
+
<input type="hidden" name="category" value="{{ category }}">
|
| 800 |
+
<div style="font-weight: 600; font-size: 0.9rem; color: #636e72;">Новый товар в категории "{{ category }}"</div>
|
| 801 |
+
<div class="form-row">
|
| 802 |
+
<input type="text" name="name" placeholder="Название товара" required autocomplete="off" style="flex:2;">
|
| 803 |
+
<input type="number" name="price" placeholder="Цена" required step="0.01" style="flex:1;">
|
| 804 |
+
</div>
|
| 805 |
+
<textarea name="description" placeholder="Описание товара (необязательно)"></textarea>
|
| 806 |
+
<div class="file-input-wrapper">
|
| 807 |
+
<input type="file" name="photos" accept="image/*" multiple max="10" required>
|
| 808 |
+
<div style="font-size: 0.8rem; color: #999; margin-top: 5px;">Можно выбрать до 10 фото</div>
|
| 809 |
+
</div>
|
| 810 |
+
<button type="submit" class="btn btn-success" style="width: 100%; justify-content: center;"><i class="fas fa-check"></i> Сохранить товар</button>
|
| 811 |
+
</form>
|
| 812 |
+
</div>
|
| 813 |
+
|
| 814 |
{% for product in products %}
|
| 815 |
{% if product.category == category %}
|
| 816 |
<div class="product-item">
|
|
|
|
| 822 |
{% endif %}
|
| 823 |
<div class="product-details">
|
| 824 |
<span class="product-name">{{ product.name }}</span>
|
| 825 |
+
{% if product.description %}
|
| 826 |
+
<span class="product-desc">{{ product.description[:50] }}{{ '...' if product.description|length > 50 else '' }}</span>
|
| 827 |
+
{% endif %}
|
| 828 |
<span class="product-meta">{{ product.price }} {{ currency_code }} • Фото: {{ product.photos|length if product.photos else 0 }}/10</span>
|
| 829 |
</div>
|
| 830 |
</div>
|
|
|
|
| 836 |
</div>
|
| 837 |
{% endif %}
|
| 838 |
{% endfor %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 839 |
</div>
|
| 840 |
</div>
|
| 841 |
{% endfor %}
|
|
|
|
| 847 |
btn.style.pointerEvents = 'none';
|
| 848 |
btn.style.opacity = '0.7';
|
| 849 |
}
|
| 850 |
+
|
| 851 |
+
function toggleCategory(id) {
|
| 852 |
+
const content = document.getElementById(id);
|
| 853 |
+
const icon = document.getElementById('icon-' + id);
|
| 854 |
+
if(content.classList.contains('active')) {
|
| 855 |
+
content.classList.remove('active');
|
| 856 |
+
icon.classList.remove('fa-chevron-up');
|
| 857 |
+
icon.classList.add('fa-chevron-down');
|
| 858 |
+
} else {
|
| 859 |
+
content.classList.add('active');
|
| 860 |
+
icon.classList.remove('fa-chevron-down');
|
| 861 |
+
icon.classList.add('fa-chevron-up');
|
| 862 |
+
}
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
function toggleAddProduct(id) {
|
| 866 |
+
const form = document.getElementById(id);
|
| 867 |
+
form.classList.toggle('active');
|
| 868 |
+
}
|
| 869 |
</script>
|
| 870 |
</body>
|
| 871 |
</html>
|
|
|
|
| 959 |
elif action == 'add_product':
|
| 960 |
name = request.form.get('name', '').strip()
|
| 961 |
price = float(request.form.get('price', 0))
|
| 962 |
+
description = request.form.get('description', '').strip()
|
| 963 |
category = request.form.get('category')
|
| 964 |
uploaded_photos = request.files.getlist('photos')[:10]
|
| 965 |
|
|
|
|
| 995 |
'product_id': uuid4().hex,
|
| 996 |
'name': name,
|
| 997 |
'price': price,
|
| 998 |
+
'description': description,
|
| 999 |
'category': category,
|
| 1000 |
'photos': photos_list
|
| 1001 |
}
|