flpolprojects commited on
Commit
2f7185a
·
verified ·
1 Parent(s): 130dcb8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -68
app.py CHANGED
@@ -13,12 +13,12 @@ app = Flask(__name__)
13
  DATA_FILE = 'data.json'
14
 
15
  # Настройки Hugging Face
16
- REPO_ID = "flpolprojects/Clients"
17
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
18
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
19
 
20
  # Ссылка на логотип
21
- LOGO_URL = "https://cdn-avatars.huggingface.co/v1/production/uploads/67b22aaeae9b6a59f1cfb849/JrKb3It_r7IqEEikB9mZV.jpeg"
22
 
23
  # Настройка логирования
24
  logging.basicConfig(level=logging.DEBUG)
@@ -105,7 +105,7 @@ def catalog():
105
  <head>
106
  <meta charset="UTF-8">
107
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
108
- <title>Routine wholesale - Женская одежда </title>
109
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
110
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
111
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
@@ -137,7 +137,6 @@ def catalog():
137
  align-items: center;
138
  padding: 15px 0;
139
  border-bottom: 1px solid #e2e8f0;
140
- position: relative;
141
  }
142
  .header-logo {
143
  width: 60px;
@@ -211,8 +210,8 @@ def catalog():
211
  }
212
  .products-grid {
213
  display: grid;
214
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
215
- gap: 20px;
216
  padding: 10px;
217
  }
218
  .product {
@@ -251,7 +250,7 @@ def catalog():
251
  transform: scale(1.1);
252
  }
253
  .product h2 {
254
- font-size: 1.2rem;
255
  font-weight: 600;
256
  margin: 10px 0;
257
  text-align: center;
@@ -260,14 +259,14 @@ def catalog():
260
  text-overflow: ellipsis;
261
  }
262
  .product-price {
263
- font-size: 1.3rem;
264
  color: #ef4444;
265
  font-weight: 700;
266
  text-align: center;
267
  margin: 5px 0;
268
  }
269
  .product-description {
270
- font-size: 0.9rem;
271
  color: #718096;
272
  text-align: center;
273
  margin-bottom: 15px;
@@ -281,12 +280,12 @@ def catalog():
281
  .product-button {
282
  display: block;
283
  width: 100%;
284
- padding: 10px;
285
  border: none;
286
  border-radius: 8px;
287
  background-color: #3b82f6;
288
  color: white;
289
- font-size: 0.9rem;
290
  font-weight: 500;
291
  cursor: pointer;
292
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -314,19 +313,15 @@ def catalog():
314
  color: white;
315
  border: none;
316
  border-radius: 50%;
317
- width: 60px;
318
- height: 60px;
319
- font-size: 1.5rem;
320
  cursor: pointer;
321
  display: none;
322
  box-shadow: 0 4px 15px rgba(239, 68, 68, 0.4);
323
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
324
  z-index: 1000;
325
  }
326
- #cart-button:hover {
327
- background-color: #dc2626;
328
- transform: scale(1.1);
329
- }
330
  .modal {
331
  display: none;
332
  position: fixed;
@@ -389,12 +384,14 @@ def catalog():
389
  border-radius: 8px;
390
  margin-right: 15px;
391
  }
392
- .quantity-input {
393
- width: 70px;
 
394
  padding: 8px;
395
  border: 1px solid #e2e8f0;
396
  border-radius: 8px;
397
  font-size: 1rem;
 
398
  }
399
  .clear-cart {
400
  background-color: #ef4444;
@@ -410,34 +407,6 @@ def catalog():
410
  background-color: #059669;
411
  box-shadow: 0 4px 15px rgba(5, 150, 105, 0.4);
412
  }
413
- @media (max-width: 768px) {
414
- .products-grid {
415
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
416
- gap: 15px;
417
- }
418
- .product h2 {
419
- font-size: 1rem;
420
- }
421
- .product-price {
422
- font-size: 1.1rem;
423
- }
424
- .product-description {
425
- font-size: 0.8rem;
426
- }
427
- .product-button {
428
- font-size: 0.8rem;
429
- padding: 8px;
430
- }
431
- .header-logo {
432
- width: 50px;
433
- height: 50px;
434
- }
435
- #cart-button {
436
- width: 50px;
437
- height: 50px;
438
- font-size: 1.2rem;
439
- }
440
- }
441
  </style>
442
  </head>
443
  <body>
@@ -489,12 +458,13 @@ def catalog():
489
  </div>
490
  </div>
491
 
492
- <!-- Quantity Modal -->
493
  <div id="quantityModal" class="modal">
494
  <div class="modal-content">
495
  <span class="close" onclick="closeModal('quantityModal')">×</span>
496
- <h2>Укажите количество</h2>
497
  <input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
 
498
  <button class="product-button" onclick="confirmAddToCart()">Добавить</button>
499
  </div>
500
  </div>
@@ -568,6 +538,22 @@ def catalog():
568
 
569
  function openQuantityModal(index) {
570
  selectedProductIndex = index;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  document.getElementById('quantityModal').style.display = 'block';
572
  document.getElementById('quantityInput').value = 1;
573
  }
@@ -575,22 +561,26 @@ def catalog():
575
  function confirmAddToCart() {
576
  if (selectedProductIndex === null) return;
577
  const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
 
578
  if (quantity <= 0) {
579
  alert("Укажите количество больше 0");
580
  return;
581
  }
582
  let cart = JSON.parse(localStorage.getItem('cart') || '[]');
583
  const product = products[selectedProductIndex];
584
- const existingItem = cart.find(item => item.name === product.name);
 
585
 
586
  if (existingItem) {
587
  existingItem.quantity += quantity;
588
  } else {
589
  cart.push({
 
590
  name: product.name,
591
  price: product.price,
592
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
593
- quantity: quantity
 
594
  });
595
  }
596
 
@@ -618,7 +608,7 @@ def catalog():
618
  ${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${item.photo}" alt="${item.name}">` : ''}
619
  <div>
620
  <strong>${item.name}</strong>
621
- <p>${item.price} с × ${item.quantity}</p>
622
  </div>
623
  </div>
624
  <span>${itemTotal} с</span>
@@ -641,7 +631,7 @@ def catalog():
641
  cart.forEach((item, index) => {
642
  const itemTotal = item.price * item.quantity;
643
  total += itemTotal;
644
- orderText += `${index + 1}. ${item.name} - ${item.price} с × ${item.quantity}%0A`;
645
  });
646
  orderText += `Итого: ${total} с`;
647
  window.open(`https://api.whatsapp.com/send?phone=996709513331&text=${orderText}`, '_blank');
@@ -722,6 +712,7 @@ def product_detail(index):
722
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
723
  <p><strong>Цена:</strong> {{ product['price'] }} с</p>
724
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
 
725
  </div>
726
  '''
727
  return render_template_string(detail_html, product=product, repo_id=REPO_ID)
@@ -758,10 +749,11 @@ def admin():
758
  description = request.form.get('description')
759
  category = request.form.get('category')
760
  photos_files = request.files.getlist('photos')
 
761
  photos_list = []
762
 
763
  if photos_files:
764
- for photo in photos_files[:2]:
765
  if photo and photo.filename:
766
  photo_filename = secure_filename(photo.filename)
767
  uploads_dir = 'uploads'
@@ -790,7 +782,8 @@ def admin():
790
  'price': price,
791
  'description': description,
792
  'category': category if category in categories else 'Без категории',
793
- 'photos': photos_list
 
794
  }
795
  products.append(new_product)
796
  save_data(data)
@@ -803,10 +796,11 @@ def admin():
803
  description = request.form.get('description')
804
  category = request.form.get('category')
805
  photos_files = request.files.getlist('photos')
 
806
 
807
  if photos_files and any(photo.filename for photo in photos_files):
808
  new_photos_list = []
809
- for photo in photos_files[:2]:
810
  if photo and photo.filename:
811
  photo_filename = secure_filename(photo.filename)
812
  uploads_dir = 'uploads'
@@ -831,6 +825,7 @@ def admin():
831
  products[index]['price'] = float(price.replace(',', '.'))
832
  products[index]['description'] = description
833
  products[index]['category'] = category if category in categories else 'Без категории'
 
834
  save_data(data)
835
  return redirect(url_for('admin'))
836
 
@@ -947,11 +942,16 @@ def admin():
947
  background: #f7fafc;
948
  border-radius: 10px;
949
  }
950
- @media (max-width: 768px) {
951
- .header-logo {
952
- width: 50px;
953
- height: 50px;
954
- }
 
 
 
 
 
955
  }
956
  </style>
957
  </head>
@@ -977,9 +977,16 @@ def admin():
977
  <option value="{{ category }}">{{ category }}</option>
978
  {% endfor %}
979
  </select>
980
- <label>Фотографии (до 2):</label>
981
  <input type="file" name="photos" accept="image/*" multiple>
982
- <button type="submit">Добавить</button>
 
 
 
 
 
 
 
983
  </form>
984
 
985
  <h1>Управление категориями</h1>
@@ -1020,10 +1027,15 @@ def admin():
1020
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1021
  <p><strong>Цена:</strong> {{ product['price'] }} с</p>
1022
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
 
1023
  {% if product.get('photos') and product['photos']|length > 0 %}
1024
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
1025
- alt="{{ product['name'] }}"
1026
- style="max-width: 100px; border-radius: 10px;">
 
 
 
 
1027
  {% endif %}
1028
  <details>
1029
  <summary>Редактировать</summary>
@@ -1043,8 +1055,17 @@ def admin():
1043
  <option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>
1044
  {% endfor %}
1045
  </select>
1046
- <label>Фотографии:</label>
1047
  <input type="file" name="photos" accept="image/*" multiple>
 
 
 
 
 
 
 
 
 
1048
  <button type="submit">Сохранить</button>
1049
  </form>
1050
  </details>
@@ -1057,6 +1078,15 @@ def admin():
1057
  {% endfor %}
1058
  </div>
1059
  </div>
 
 
 
 
 
 
 
 
 
1060
  </body>
1061
  </html>
1062
  '''
 
13
  DATA_FILE = 'data.json'
14
 
15
  # Настройки Hugging Face
16
+ REPO_ID = "flpol/clients"
17
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
18
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
19
 
20
  # Ссылка на логотип
21
+ LOGO_URL = "https://cdn-avatars.huggingface.co/v1/production/uploads/67c280ccb9d3dfdee58ecfdd/hyc6QWsSZ6FejTAAqDGUS.jpeg"
22
 
23
  # Настройка логирования
24
  logging.basicConfig(level=logging.DEBUG)
 
105
  <head>
106
  <meta charset="UTF-8">
107
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
108
+ <title>Liza Brand - женская одежда оптом </title>
109
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
110
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
111
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
 
137
  align-items: center;
138
  padding: 15px 0;
139
  border-bottom: 1px solid #e2e8f0;
 
140
  }
141
  .header-logo {
142
  width: 60px;
 
210
  }
211
  .products-grid {
212
  display: grid;
213
+ grid-template-columns: repeat(2, minmax(200px, 1fr));
214
+ gap: 15px;
215
  padding: 10px;
216
  }
217
  .product {
 
250
  transform: scale(1.1);
251
  }
252
  .product h2 {
253
+ font-size: 1rem;
254
  font-weight: 600;
255
  margin: 10px 0;
256
  text-align: center;
 
259
  text-overflow: ellipsis;
260
  }
261
  .product-price {
262
+ font-size: 1.1rem;
263
  color: #ef4444;
264
  font-weight: 700;
265
  text-align: center;
266
  margin: 5px 0;
267
  }
268
  .product-description {
269
+ font-size: 0.8rem;
270
  color: #718096;
271
  text-align: center;
272
  margin-bottom: 15px;
 
280
  .product-button {
281
  display: block;
282
  width: 100%;
283
+ padding: 8px;
284
  border: none;
285
  border-radius: 8px;
286
  background-color: #3b82f6;
287
  color: white;
288
+ font-size: 0.8rem;
289
  font-weight: 500;
290
  cursor: pointer;
291
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 
313
  color: white;
314
  border: none;
315
  border-radius: 50%;
316
+ width: 50px;
317
+ height: 50px;
318
+ font-size: 1.2rem;
319
  cursor: pointer;
320
  display: none;
321
  box-shadow: 0 4px 15px rgba(239, 68, 68, 0.4);
322
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
323
  z-index: 1000;
324
  }
 
 
 
 
325
  .modal {
326
  display: none;
327
  position: fixed;
 
384
  border-radius: 8px;
385
  margin-right: 15px;
386
  }
387
+ .quantity-input, .color-select {
388
+ width: 100%;
389
+ max-width: 150px;
390
  padding: 8px;
391
  border: 1px solid #e2e8f0;
392
  border-radius: 8px;
393
  font-size: 1rem;
394
+ margin: 5px 0;
395
  }
396
  .clear-cart {
397
  background-color: #ef4444;
 
407
  background-color: #059669;
408
  box-shadow: 0 4px 15px rgba(5, 150, 105, 0.4);
409
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  </style>
411
  </head>
412
  <body>
 
458
  </div>
459
  </div>
460
 
461
+ <!-- Quantity and Color Modal -->
462
  <div id="quantityModal" class="modal">
463
  <div class="modal-content">
464
  <span class="close" onclick="closeModal('quantityModal')">×</span>
465
+ <h2>Укажите количество и цвет</h2>
466
  <input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
467
+ <select id="colorSelect" class="color-select"></select>
468
  <button class="product-button" onclick="confirmAddToCart()">Добавить</button>
469
  </div>
470
  </div>
 
538
 
539
  function openQuantityModal(index) {
540
  selectedProductIndex = index;
541
+ const product = products[index];
542
+ const colorSelect = document.getElementById('colorSelect');
543
+ colorSelect.innerHTML = '';
544
+ if (product.colors && product.colors.length > 0) {
545
+ product.colors.forEach(color => {
546
+ const option = document.createElement('option');
547
+ option.value = color;
548
+ option.text = color;
549
+ colorSelect.appendChild(option);
550
+ });
551
+ } else {
552
+ const option = document.createElement('option');
553
+ option.value = 'Нет цвета';
554
+ option.text = 'Нет цвета';
555
+ colorSelect.appendChild(option);
556
+ }
557
  document.getElementById('quantityModal').style.display = 'block';
558
  document.getElementById('quantityInput').value = 1;
559
  }
 
561
  function confirmAddToCart() {
562
  if (selectedProductIndex === null) return;
563
  const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
564
+ const color = document.getElementById('colorSelect').value;
565
  if (quantity <= 0) {
566
  alert("Укажите количество больше 0");
567
  return;
568
  }
569
  let cart = JSON.parse(localStorage.getItem('cart') || '[]');
570
  const product = products[selectedProductIndex];
571
+ const cartItemId = `${product.name}-${color}`;
572
+ const existingItem = cart.find(item => item.id === cartItemId);
573
 
574
  if (existingItem) {
575
  existingItem.quantity += quantity;
576
  } else {
577
  cart.push({
578
+ id: cartItemId,
579
  name: product.name,
580
  price: product.price,
581
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
582
+ quantity: quantity,
583
+ color: color
584
  });
585
  }
586
 
 
608
  ${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${item.photo}" alt="${item.name}">` : ''}
609
  <div>
610
  <strong>${item.name}</strong>
611
+ <p>${item.price} с × ${item.quantity} (Цвет: ${item.color})</p>
612
  </div>
613
  </div>
614
  <span>${itemTotal} с</span>
 
631
  cart.forEach((item, index) => {
632
  const itemTotal = item.price * item.quantity;
633
  total += itemTotal;
634
+ orderText += `${index + 1}. ${item.name} - ${item.price} с × ${item.quantity} (Цвет: ${item.color})%0A`;
635
  });
636
  orderText += `Итого: ${total} с`;
637
  window.open(`https://api.whatsapp.com/send?phone=996709513331&text=${orderText}`, '_blank');
 
712
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
713
  <p><strong>Цена:</strong> {{ product['price'] }} с</p>
714
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
715
+ <p><strong>Доступные цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
716
  </div>
717
  '''
718
  return render_template_string(detail_html, product=product, repo_id=REPO_ID)
 
749
  description = request.form.get('description')
750
  category = request.form.get('category')
751
  photos_files = request.files.getlist('photos')
752
+ colors = request.form.getlist('colors')
753
  photos_list = []
754
 
755
  if photos_files:
756
+ for photo in photos_files[:10]: # Ограничение до 10 фото
757
  if photo and photo.filename:
758
  photo_filename = secure_filename(photo.filename)
759
  uploads_dir = 'uploads'
 
782
  'price': price,
783
  'description': description,
784
  'category': category if category in categories else 'Без категории',
785
+ 'photos': photos_list,
786
+ 'colors': colors if colors else []
787
  }
788
  products.append(new_product)
789
  save_data(data)
 
796
  description = request.form.get('description')
797
  category = request.form.get('category')
798
  photos_files = request.files.getlist('photos')
799
+ colors = request.form.getlist('colors')
800
 
801
  if photos_files and any(photo.filename for photo in photos_files):
802
  new_photos_list = []
803
+ for photo in photos_files[:10]: # Ограничение до 10 фото
804
  if photo and photo.filename:
805
  photo_filename = secure_filename(photo.filename)
806
  uploads_dir = 'uploads'
 
825
  products[index]['price'] = float(price.replace(',', '.'))
826
  products[index]['description'] = description
827
  products[index]['category'] = category if category in categories else 'Без категории'
828
+ products[index]['colors'] = colors if colors else []
829
  save_data(data)
830
  return redirect(url_for('admin'))
831
 
 
942
  background: #f7fafc;
943
  border-radius: 10px;
944
  }
945
+ .color-input-group {
946
+ display: flex;
947
+ gap: 10px;
948
+ margin-top: 5px;
949
+ }
950
+ .add-color-btn {
951
+ background-color: #10b981;
952
+ }
953
+ .add-color-btn:hover {
954
+ background-color: #059669;
955
  }
956
  </style>
957
  </head>
 
977
  <option value="{{ category }}">{{ category }}</option>
978
  {% endfor %}
979
  </select>
980
+ <label>Фотографии (до 10):</label>
981
  <input type="file" name="photos" accept="image/*" multiple>
982
+ <label>Цвета:</label>
983
+ <div id="color-inputs">
984
+ <div class="color-input-group">
985
+ <input type="text" name="colors" placeholder="Например: Красный">
986
+ </div>
987
+ </div>
988
+ <button type="button" class="add-color-btn" onclick="addColorInput()">Добавить цвет</button>
989
+ <button type="submit">Добавить товар</button>
990
  </form>
991
 
992
  <h1>Управление категориями</h1>
 
1027
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1028
  <p><strong>Цена:</strong> {{ product['price'] }} с</p>
1029
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
1030
+ <p><strong>Цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
1031
  {% if product.get('photos') and product['photos']|length > 0 %}
1032
+ <div style="display: flex; flex-wrap: wrap; gap: 10px;">
1033
+ {% for photo in product['photos'] %}
1034
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}"
1035
+ alt="{{ product['name'] }}"
1036
+ style="max-width: 100px; border-radius: 10px;">
1037
+ {% endfor %}
1038
+ </div>
1039
  {% endif %}
1040
  <details>
1041
  <summary>Редактировать</summary>
 
1055
  <option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>
1056
  {% endfor %}
1057
  </select>
1058
+ <label>Фотографии (до 10):</label>
1059
  <input type="file" name="photos" accept="image/*" multiple>
1060
+ <label>Цвета:</label>
1061
+ <div id="edit-color-inputs-{{ loop.index0 }}">
1062
+ {% for color in product.get('colors', []) %}
1063
+ <div class="color-input-group">
1064
+ <input type="text" name="colors" value="{{ color }}">
1065
+ </div>
1066
+ {% endfor %}
1067
+ </div>
1068
+ <button type="button" class="add-color-btn" onclick="addColorInput('edit-color-inputs-{{ loop.index0 }}')">Добавить цвет</button>
1069
  <button type="submit">Сохранить</button>
1070
  </form>
1071
  </details>
 
1078
  {% endfor %}
1079
  </div>
1080
  </div>
1081
+ <script>
1082
+ function addColorInput(containerId = 'color-inputs') {
1083
+ const container = document.getElementById(containerId);
1084
+ const newInput = document.createElement('div');
1085
+ newInput.className = 'color-input-group';
1086
+ newInput.innerHTML = '<input type="text" name="colors" placeholder="Например: Красный">';
1087
+ container.appendChild(newInput);
1088
+ }
1089
+ </script>
1090
  </body>
1091
  </html>
1092
  '''