flpolprojects commited on
Commit
0318b2a
·
verified ·
1 Parent(s): 51849f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -159
app.py CHANGED
@@ -188,143 +188,89 @@ def catalog():
188
  .product-button:hover {
189
  background-color: #2980b9;
190
  }
191
- @media (max-width: 768px) {
192
- .products-grid {
193
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
194
- gap: 10px;
195
- }
196
- body {
197
- padding: 10px;
198
- }
199
- h1 {
200
- font-size: 1.8em;
201
- margin-bottom: 20px;
202
- }
203
- .product {
204
- padding: 10px;
205
- }
206
- .product-image {
207
- height: 120px;
208
- }
209
- .product h2 {
210
- font-size: 1em;
211
- }
212
- .product-price {
213
- font-size: 1.1em;
214
- }
215
- .product-description {
216
- font-size: 0.8em;
217
- }
218
- .product-button {
219
- padding: 8px 15px;
220
- font-size: 0.9em;
221
- }
222
- }
223
- @keyframes fadeIn {
224
- from {
225
- opacity: 0;
226
- transform: translateY(20px);
227
- }
228
- to {
229
- opacity: 1;
230
- transform: translateY(0);
231
- }
232
- }
233
- .product {
234
- animation: fadeIn 0.5s ease-out forwards;
235
- }
236
- ::-webkit-scrollbar {
237
- width: 8px;
238
  }
239
- ::-webkit-scrollbar-track {
240
- background: #f1f1f1;
241
  }
242
- ::-webkit-scrollbar-thumb {
243
- background: #888;
244
- border-radius: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
245
  }
246
- ::-webkit-scrollbar-thumb:hover {
247
- background: #555;
248
  }
249
-
250
- /* Modal Styles */
251
  .modal {
252
- display: none; /* Hidden by default */
253
- position: fixed; /* Stay in place */
254
- z-index: 1; /* Sit on top */
255
  left: 0;
256
  top: 0;
257
- width: 100%; /* Full width */
258
- height: 100%; /* Full height */
259
- overflow: auto; /* Enable scroll if needed */
260
- background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
261
  }
262
-
263
  .modal-content {
264
  position: relative;
265
  background-color: #fefefe;
266
- margin: 10% auto; /* 15% from the top and centered */
267
  padding: 20px;
268
  border: 1px solid #888;
269
  width: 80%;
270
  max-width: 600px;
271
- box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
272
- animation-name: animatetop;
273
- animation-duration: 0.4s
274
  }
275
-
276
- /* Add Animation */
277
  @keyframes animatetop {
278
  from {top: -300px; opacity: 0}
279
  to {top: 10%; opacity: 1}
280
  }
281
-
282
- /* The Close Button */
283
  .close {
284
  color: #aaa;
285
  float: right;
286
  font-size: 28px;
287
  font-weight: bold;
288
- }
289
-
290
- .close:hover,
291
- .close:focus {
292
- color: black;
293
- text-decoration: none;
294
  cursor: pointer;
295
  }
296
-
297
- /* Swiper Styles */
298
- .swiper-container {
299
- width: 100%;
300
- height: 300px;
301
  }
302
-
303
- .swiper-slide {
304
- text-align: center;
305
- font-size: 18px;
306
- background: #fff;
307
-
308
- /* Center slide text vertically */
309
- display: -webkit-box;
310
- display: -ms-flexbox;
311
- display: -webkit-flex;
312
  display: flex;
313
- -webkit-box-pack: center;
314
- -ms-flex-pack: center;
315
- -webkit-justify-content: center;
316
- justify-content: center;
317
- -webkit-box-align: center;
318
- -ms-flex-align: center;
319
- -webkit-align-items: center;
320
  align-items: center;
 
 
321
  }
322
-
323
- .swiper-slide img {
324
- display: block;
325
- width: 100%;
326
- height: 100%;
327
  object-fit: cover;
 
 
 
 
 
 
 
 
 
 
328
  }
329
  </style>
330
  </head>
@@ -337,7 +283,7 @@ def catalog():
337
  {% if product.get('photos') and product['photos']|length > 0 %}
338
  <div class="product-image">
339
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
340
- alt="{{ product['name'] }}"
341
  loading="lazy">
342
  </div>
343
  {% endif %}
@@ -345,54 +291,65 @@ def catalog():
345
  <div class="product-price">{{ product['price'] }} ₽</div>
346
  <p class="product-description">{{ product['description'][:100] }}{% if product['description']|length > 100 %}...{% endif %}</p>
347
  <button class="product-button" onclick="openModal({{ loop.index0 }})">Подробнее</button>
 
348
  </div>
349
  {% endfor %}
350
  </div>
351
  </div>
352
 
353
- <!-- The Modal -->
354
  <div id="productModal" class="modal">
355
  <div class="modal-content">
356
- <span class="close" onclick="closeModal()">&times;</span>
357
- <div id="modalContent">
358
- <!-- Product details will be loaded here -->
 
 
 
 
 
 
 
 
 
 
359
  </div>
360
  </div>
361
  </div>
362
 
 
 
363
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
364
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
365
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
366
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.js"></script>
367
  <script>
368
- // Function to open the modal
 
369
  function openModal(index) {
370
  loadProductDetails(index);
371
  document.getElementById('productModal').style.display = "block";
372
  }
373
 
374
- // Function to close the modal
375
- function closeModal() {
376
- document.getElementById('productModal').style.display = "none";
377
  }
378
 
379
- // Function to load product details into the modal
380
  function loadProductDetails(index) {
381
  fetch('/product/' + index)
382
  .then(response => response.text())
383
  .then(data => {
384
  document.getElementById('modalContent').innerHTML = data;
385
- // Initialize Swiper after the content is loaded
386
  initializeSwiper();
387
  });
388
  }
389
 
390
  function initializeSwiper() {
391
- var swiper = new Swiper('.swiper-container', {
392
  slidesPerView: 1,
393
  spaceBetween: 30,
394
  loop: true,
395
- grabCursor: true, // Enables the ability to grab with a cursor
396
  pagination: {
397
  el: '.swiper-pagination',
398
  clickable: true,
@@ -404,12 +361,73 @@ def catalog():
404
  });
405
  }
406
 
407
- // Close the modal if the user clicks outside of it
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  window.onclick = function(event) {
409
- if (event.target == document.getElementById('productModal')) {
410
- closeModal();
411
  }
412
  }
 
 
 
413
  </script>
414
  </body>
415
  </html>
@@ -429,20 +447,18 @@ def product_detail(index):
429
  <div class="swiper-container">
430
  <div class="swiper-wrapper">
431
  {% if product.get('photos') %}
432
- {% for photo in product['photos'] %}
433
- <div class="swiper-slide">
434
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="{{ product['name'] }}">
435
- </div>
436
- {% endfor %}
437
  {% else %}
438
- <div class="swiper-slide">
439
- <img src="https://via.placeholder.com/300" alt="No Image">
440
- </div>
441
  {% endif %}
442
  </div>
443
- <!-- Add Pagination -->
444
  <div class="swiper-pagination"></div>
445
- <!-- Add Navigation -->
446
  <div class="swiper-button-next"></div>
447
  <div class="swiper-button-prev"></div>
448
  </div>
@@ -464,7 +480,7 @@ def admin():
464
  photos_files = request.files.getlist('photos')
465
  photos_list = []
466
  if photos_files:
467
- for photo in photos_files:
468
  if photo and photo.filename:
469
  photo_filename = secure_filename(photo.filename)
470
  uploads_dir = 'uploads'
@@ -507,10 +523,9 @@ def admin():
507
  price = request.form.get('price')
508
  description = request.form.get('description')
509
  photos_files = request.files.getlist('photos')
510
- # Если загружены новые фото, обновляем список фотографий товара
511
  if photos_files and any(photo.filename for photo in photos_files):
512
  new_photos_list = []
513
- for photo in photos_files:
514
  if photo and photo.filename:
515
  photo_filename = secure_filename(photo.filename)
516
  uploads_dir = 'uploads'
@@ -614,23 +629,6 @@ def admin():
614
  border-radius: 5px;
615
  background-color: #f9f9f9;
616
  }
617
- @media (max-width: 600px) {
618
- body {
619
- margin: 10px;
620
- }
621
- h1 {
622
- font-size: 24px;
623
- }
624
- form {
625
- padding: 10px;
626
- }
627
- .product-item {
628
- padding: 10px;
629
- }
630
- input[type="file"] {
631
- margin-bottom: 10px;
632
- }
633
- }
634
  </style>
635
  </head>
636
  <body>
@@ -643,7 +641,7 @@ def admin():
643
  <input type="number" id="price" name="price" step="0.01" required>
644
  <label for="description">Описание:</label>
645
  <textarea id="description" name="description" rows="4" required></textarea>
646
- <label for="photos">Фотографии товара (до 5):</label>
647
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
648
  <button type="submit">Добавить товар</button>
649
  </form>
@@ -652,7 +650,6 @@ def admin():
652
  <form method="POST" action="{{ url_for('backup') }}">
653
  <button type="submit">Создать резервную копию</button>
654
  </form>
655
-
656
  <form method="GET" action="{{ url_for('download') }}">
657
  <button type="submit">Скачать базу данных</button>
658
  </form>
@@ -665,11 +662,10 @@ def admin():
665
  <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
666
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
667
  {% if product.get('photos') and product['photos']|length > 0 %}
668
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
669
- alt="{{ product['name'] }}"
670
  style="max-width: 100px;">
671
  {% endif %}
672
-
673
  <details>
674
  <summary>Редактировать</summary>
675
  <form method="POST" enctype="multipart/form-data" class="edit-form">
@@ -681,12 +677,11 @@ def admin():
681
  <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
682
  <label for="description">Описание:</label>
683
  <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
684
- <label for="photos">Фотографии товара (до 5):</label>
685
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
686
  <button type="submit">Сохранить изменения</button>
687
  </form>
688
  </details>
689
-
690
  <form method="POST">
691
  <input type="hidden" name="action" value="delete">
692
  <input type="hidden" name="index" value="{{ loop.index0 }}">
@@ -719,4 +714,4 @@ if __name__ == '__main__':
719
  except Exception as e:
720
  logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
721
 
722
- app.run(debug=True, host='0.0.0.0', port=7860)
 
188
  .product-button:hover {
189
  background-color: #2980b9;
190
  }
191
+ .add-to-cart {
192
+ background-color: #27ae60;
193
+ margin-top: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  }
195
+ .add-to-cart:hover {
196
+ background-color: #219653;
197
  }
198
+ #cart-button {
199
+ position: fixed;
200
+ bottom: 20px;
201
+ right: 20px;
202
+ background-color: #e74c3c;
203
+ color: white;
204
+ border: none;
205
+ border-radius: 50%;
206
+ width: 60px;
207
+ height: 60px;
208
+ font-size: 20px;
209
+ cursor: pointer;
210
+ display: none;
211
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
212
+ z-index: 1000;
213
  }
214
+ #cart-button:hover {
215
+ background-color: #c0392b;
216
  }
 
 
217
  .modal {
218
+ display: none;
219
+ position: fixed;
220
+ z-index: 1001;
221
  left: 0;
222
  top: 0;
223
+ width: 100%;
224
+ height: 100%;
225
+ overflow: auto;
226
+ background-color: rgba(0,0,0,0.4);
227
  }
 
228
  .modal-content {
229
  position: relative;
230
  background-color: #fefefe;
231
+ margin: 10% auto;
232
  padding: 20px;
233
  border: 1px solid #888;
234
  width: 80%;
235
  max-width: 600px;
236
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
237
+ animation: animatetop 0.4s;
 
238
  }
 
 
239
  @keyframes animatetop {
240
  from {top: -300px; opacity: 0}
241
  to {top: 10%; opacity: 1}
242
  }
 
 
243
  .close {
244
  color: #aaa;
245
  float: right;
246
  font-size: 28px;
247
  font-weight: bold;
 
 
 
 
 
 
248
  cursor: pointer;
249
  }
250
+ .close:hover {
251
+ color: black;
 
 
 
252
  }
253
+ .cart-item {
 
 
 
 
 
 
 
 
 
254
  display: flex;
255
+ justify-content: space-between;
 
 
 
 
 
 
256
  align-items: center;
257
+ padding: 10px 0;
258
+ border-bottom: 1px solid #eee;
259
  }
260
+ .cart-item img {
261
+ width: 50px;
262
+ height: 50px;
 
 
263
  object-fit: cover;
264
+ margin-right: 10px;
265
+ }
266
+ @media (max-width: 768px) {
267
+ .products-grid {
268
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
269
+ gap: 10px;
270
+ }
271
+ .product-image {
272
+ height: 120px;
273
+ }
274
  }
275
  </style>
276
  </head>
 
283
  {% if product.get('photos') and product['photos']|length > 0 %}
284
  <div class="product-image">
285
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
286
+ alt="{{ product['name'] }}"
287
  loading="lazy">
288
  </div>
289
  {% endif %}
 
291
  <div class="product-price">{{ product['price'] }} ₽</div>
292
  <p class="product-description">{{ product['description'][:100] }}{% if product['description']|length > 100 %}...{% endif %}</p>
293
  <button class="product-button" onclick="openModal({{ loop.index0 }})">Подробнее</button>
294
+ <button class="product-button add-to-cart" onclick="addToCart({{ loop.index0 }})">В корзину</button>
295
  </div>
296
  {% endfor %}
297
  </div>
298
  </div>
299
 
300
+ <!-- Product Modal -->
301
  <div id="productModal" class="modal">
302
  <div class="modal-content">
303
+ <span class="close" onclick="closeModal('productModal')">×</span>
304
+ <div id="modalContent"></div>
305
+ </div>
306
+ </div>
307
+
308
+ <!-- Cart Modal -->
309
+ <div id="cartModal" class="modal">
310
+ <div class="modal-content">
311
+ <span class="close" onclick="closeModal('cartModal')">×</span>
312
+ <h2>Корзина</h2>
313
+ <div id="cartContent"></div>
314
+ <div style="margin-top: 20px; text-align: right;">
315
+ <strong>Итого: <span id="cartTotal">0</span> ₽</strong>
316
  </div>
317
  </div>
318
  </div>
319
 
320
+ <button id="cart-button" onclick="openCartModal()">🛒</button>
321
+
322
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
323
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
324
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
325
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.js"></script>
326
  <script>
327
+ const products = {{ products|tojson }};
328
+
329
  function openModal(index) {
330
  loadProductDetails(index);
331
  document.getElementById('productModal').style.display = "block";
332
  }
333
 
334
+ function closeModal(modalId) {
335
+ document.getElementById(modalId).style.display = "none";
 
336
  }
337
 
 
338
  function loadProductDetails(index) {
339
  fetch('/product/' + index)
340
  .then(response => response.text())
341
  .then(data => {
342
  document.getElementById('modalContent').innerHTML = data;
 
343
  initializeSwiper();
344
  });
345
  }
346
 
347
  function initializeSwiper() {
348
+ new Swiper('.swiper-container', {
349
  slidesPerView: 1,
350
  spaceBetween: 30,
351
  loop: true,
352
+ grabCursor: true,
353
  pagination: {
354
  el: '.swiper-pagination',
355
  clickable: true,
 
361
  });
362
  }
363
 
364
+ function addToCart(index) {
365
+ let cart = JSON.parse(localStorage.getItem('cart') || '[]');
366
+ const product = products[index];
367
+ const existingItem = cart.find(item => item.name === product.name);
368
+
369
+ if (existingItem) {
370
+ existingItem.quantity += 1;
371
+ } else {
372
+ cart.push({
373
+ name: product.name,
374
+ price: product.price,
375
+ photo: product.photos?.[0] || '',
376
+ quantity: 1
377
+ });
378
+ }
379
+
380
+ localStorage.setItem('cart', JSON.stringify(cart));
381
+ updateCartButton();
382
+ }
383
+
384
+ function updateCartButton() {
385
+ const cart = JSON.parse(localStorage.getItem('cart') || '[]');
386
+ const cartButton = document.getElementById('cart-button');
387
+ cartButton.style.display = cart.length > 0 ? 'block' : 'none';
388
+ }
389
+
390
+ function openCartModal() {
391
+ const cart = JSON.parse(localStorage.getItem('cart') || '[]');
392
+ const cartContent = document.getElementById('cartContent');
393
+ let total = 0;
394
+
395
+ if (cart.length === 0) {
396
+ cartContent.innerHTML = '<p>Корзина пуста</p>';
397
+ } else {
398
+ cartContent.innerHTML = cart.map(item => {
399
+ const itemTotal = item.price * item.quantity;
400
+ total += itemTotal;
401
+ return `
402
+ <div class="cart-item">
403
+ <div style="display: flex; align-items: center;">
404
+ ${item.photo ? `
405
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${item.photo}"
406
+ alt="${item.name}">
407
+ ` : ''}
408
+ <div>
409
+ <strong>${item.name}</strong>
410
+ <p>${item.price} ₽ × ${item.quantity}</p>
411
+ </div>
412
+ </div>
413
+ <span>${itemTotal} ₽</span>
414
+ </div>
415
+ `;
416
+ }).join('');
417
+ }
418
+
419
+ document.getElementById('cartTotal').textContent = total;
420
+ document.getElementById('cartModal').style.display = 'block';
421
+ }
422
+
423
  window.onclick = function(event) {
424
+ if (event.target.className === 'modal') {
425
+ event.target.style.display = "none";
426
  }
427
  }
428
+
429
+ // Инициализация при загрузке страницы
430
+ updateCartButton();
431
  </script>
432
  </body>
433
  </html>
 
447
  <div class="swiper-container">
448
  <div class="swiper-wrapper">
449
  {% if product.get('photos') %}
450
+ {% for photo in product['photos'] %}
451
+ <div class="swiper-slide">
452
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="{{ product['name'] }}">
453
+ </div>
454
+ {% endfor %}
455
  {% else %}
456
+ <div class="swiper-slide">
457
+ <img src="https://via.placeholder.com/300" alt="No Image">
458
+ </div>
459
  {% endif %}
460
  </div>
 
461
  <div class="swiper-pagination"></div>
 
462
  <div class="swiper-button-next"></div>
463
  <div class="swiper-button-prev"></div>
464
  </div>
 
480
  photos_files = request.files.getlist('photos')
481
  photos_list = []
482
  if photos_files:
483
+ for i, photo in enumerate(photos_files[:2]): # Ограничение до 2 фото
484
  if photo and photo.filename:
485
  photo_filename = secure_filename(photo.filename)
486
  uploads_dir = 'uploads'
 
523
  price = request.form.get('price')
524
  description = request.form.get('description')
525
  photos_files = request.files.getlist('photos')
 
526
  if photos_files and any(photo.filename for photo in photos_files):
527
  new_photos_list = []
528
+ for i, photo in enumerate(photos_files[:2]): # Ограничение до 2 фото
529
  if photo and photo.filename:
530
  photo_filename = secure_filename(photo.filename)
531
  uploads_dir = 'uploads'
 
629
  border-radius: 5px;
630
  background-color: #f9f9f9;
631
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
  </style>
633
  </head>
634
  <body>
 
641
  <input type="number" id="price" name="price" step="0.01" required>
642
  <label for="description">Описание:</label>
643
  <textarea id="description" name="description" rows="4" required></textarea>
644
+ <label for="photos">Фотографии товара (максимум 2):</label>
645
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
646
  <button type="submit">Добавить товар</button>
647
  </form>
 
650
  <form method="POST" action="{{ url_for('backup') }}">
651
  <button type="submit">Создать резервную копию</button>
652
  </form>
 
653
  <form method="GET" action="{{ url_for('download') }}">
654
  <button type="submit">Скачать базу данных</button>
655
  </form>
 
662
  <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
663
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
664
  {% if product.get('photos') and product['photos']|length > 0 %}
665
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
666
+ alt="{{ product['name'] }}"
667
  style="max-width: 100px;">
668
  {% endif %}
 
669
  <details>
670
  <summary>Редактировать</summary>
671
  <form method="POST" enctype="multipart/form-data" class="edit-form">
 
677
  <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
678
  <label for="description">Описание:</label>
679
  <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
680
+ <label for="photos">Фотографии товара (максимум 2):</label>
681
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
682
  <button type="submit">Сохранить изменения</button>
683
  </form>
684
  </details>
 
685
  <form method="POST">
686
  <input type="hidden" name="action" value="delete">
687
  <input type="hidden" name="index" value="{{ loop.index0 }}">
 
714
  except Exception as e:
715
  logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
716
 
717
+ app.run(debug=True, host='0.0.0.0', port=7860)