Каталог из данных: фильтр по категориям, поиск, пустое состояние
This commit is contained in:
parent
fb076b21d6
commit
27f9495a65
|
|
@ -1,18 +1,23 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
|
using TechnoWorld.Data;
|
||||||
using TechnoWorld.Models;
|
using TechnoWorld.Models;
|
||||||
|
|
||||||
namespace TechnoWorld.Controllers;
|
namespace TechnoWorld.Controllers;
|
||||||
|
|
||||||
public class CatalogController : Controller
|
public class CatalogController : Controller
|
||||||
{
|
{
|
||||||
|
[HttpGet]
|
||||||
public IActionResult Index()
|
public IActionResult Index(string? category = null, string? q = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
ViewData["Title"] = "Каталог";
|
ViewData["Title"] = "Каталог";
|
||||||
|
|
||||||
return View();
|
var products = ProductStore.GetByFilter(category, q);
|
||||||
|
|
||||||
|
ViewData["Categories"] = ProductStore.Categories;
|
||||||
|
ViewData["CurrentCategory"] = category;
|
||||||
|
ViewData["SearchQuery"] = q;
|
||||||
|
|
||||||
|
return View(products);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using TechnoWorld.Data;
|
||||||
using TechnoWorld.Models;
|
using TechnoWorld.Models;
|
||||||
|
|
||||||
namespace TechnoWorld.Controllers;
|
namespace TechnoWorld.Controllers;
|
||||||
|
|
||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly ILogger<HomeController> _logger;
|
private readonly ILogger<HomeController> _logger;
|
||||||
|
|
||||||
public HomeController(ILogger<HomeController> logger)
|
public HomeController(ILogger<HomeController> logger)
|
||||||
|
|
@ -19,39 +18,30 @@ public class HomeController : Controller
|
||||||
|
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
|
|
||||||
ViewData["Title"] = "Главная";
|
ViewData["Title"] = "Главная";
|
||||||
|
ViewData["Hits"] = ProductStore.GetHits();
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult About()
|
public IActionResult About()
|
||||||
{
|
{
|
||||||
|
|
||||||
ViewData["Title"] = "О системе";
|
ViewData["Title"] = "О системе";
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult Contacts()
|
public IActionResult Contacts()
|
||||||
{
|
{
|
||||||
|
|
||||||
ViewData["Title"] = "Контакты";
|
ViewData["Title"] = "Контакты";
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|
||||||
public IActionResult Contacts(ContactViewModel model)
|
public IActionResult Contacts(ContactViewModel model)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
|
|
||||||
TempData["ContactSuccess"] = "Ваше сообщение принято! Мы свяжемся с вами в ближайшее время.";
|
TempData["ContactSuccess"] = "Ваше сообщение принято! Мы свяжемся с вами в ближайшее время.";
|
||||||
|
|
||||||
return RedirectToAction(nameof(Contacts));
|
return RedirectToAction(nameof(Contacts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,14 +50,11 @@ public class HomeController : Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
|
||||||
public IActionResult Error(int? statusCode = null)
|
public IActionResult Error(int? statusCode = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
var model = new ErrorViewModel
|
var model = new ErrorViewModel
|
||||||
{
|
{
|
||||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
|
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
|
||||||
|
|
||||||
StatusCode = statusCode
|
StatusCode = statusCode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
197
Data/ProductStore.cs
Normal file
197
Data/ProductStore.cs
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
using TechnoWorld.Models;
|
||||||
|
|
||||||
|
namespace TechnoWorld.Data;
|
||||||
|
|
||||||
|
public static class ProductStore
|
||||||
|
{
|
||||||
|
public static readonly List<Product> Products =
|
||||||
|
[
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Ноутбук Pro 15",
|
||||||
|
Category = "Ноутбуки",
|
||||||
|
Price = 89990m,
|
||||||
|
Specs = "Экран 15.6\u2033 · 8 ГБ RAM · SSD 512 ГБ",
|
||||||
|
Icon = "bi-laptop",
|
||||||
|
Badge = "badge-hit",
|
||||||
|
BadgeText = "Хит продаж"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Name = "Ноутбук Air 13",
|
||||||
|
Category = "Ноутбуки",
|
||||||
|
Price = 64990m,
|
||||||
|
Specs = "Экран 13.3\u2033 · 16 ГБ RAM · SSD 1 ТБ",
|
||||||
|
Icon = "bi-laptop",
|
||||||
|
Badge = "badge-new",
|
||||||
|
BadgeText = "Новинка"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Name = "Ноутбук Gaming X17",
|
||||||
|
Category = "Ноутбуки",
|
||||||
|
Price = 119990m,
|
||||||
|
Specs = "Экран 17.3\u2033 · 32 ГБ RAM · RTX 4060 · SSD 1 ТБ",
|
||||||
|
Icon = "bi-laptop",
|
||||||
|
Badge = "badge-hot",
|
||||||
|
BadgeText = "Выгодно"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
Name = "Смартфон Nova X",
|
||||||
|
Category = "Смартфоны",
|
||||||
|
Price = 29990m,
|
||||||
|
Specs = "Экран 6.7\u2033 · Камера 108 Мп · 128 ГБ",
|
||||||
|
Icon = "bi-phone",
|
||||||
|
Badge = "badge-hit",
|
||||||
|
BadgeText = "Хит продаж"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 5,
|
||||||
|
Name = "Смартфон Vega Plus",
|
||||||
|
Category = "Смартфоны",
|
||||||
|
Price = 45990m,
|
||||||
|
Specs = "Экран 6.5\u2033 · 256 ГБ · 5G",
|
||||||
|
Icon = "bi-phone",
|
||||||
|
Badge = "badge-new",
|
||||||
|
BadgeText = "Новинка"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 6,
|
||||||
|
Name = "Смартфон Neo SE",
|
||||||
|
Category = "Смартфоны",
|
||||||
|
Price = 17990m,
|
||||||
|
Specs = "Экран 6.1\u2033 · 64 ГБ · NFC",
|
||||||
|
Icon = "bi-phone",
|
||||||
|
Badge = ""
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 7,
|
||||||
|
Name = "Планшет Tab Lite",
|
||||||
|
Category = "Планшеты",
|
||||||
|
Price = 18490m,
|
||||||
|
Specs = "Экран 11\u2033 · 4 ГБ RAM · 128 ГБ",
|
||||||
|
Icon = "bi-tablet",
|
||||||
|
Badge = "badge-new",
|
||||||
|
BadgeText = "Новинка"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 8,
|
||||||
|
Name = "Планшет Tab Pro 12",
|
||||||
|
Category = "Планшеты",
|
||||||
|
Price = 42990m,
|
||||||
|
Specs = "Экран 12.4\u2033 · 8 ГБ RAM · 256 ГБ",
|
||||||
|
Icon = "bi-tablet",
|
||||||
|
Badge = "badge-hot",
|
||||||
|
BadgeText = "Выгодно"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 9,
|
||||||
|
Name = "Наушники SoundMax",
|
||||||
|
Category = "Наушники",
|
||||||
|
Price = 3990m,
|
||||||
|
Specs = "Bluetooth 5.3 · ANC · 40 ч автономной работы",
|
||||||
|
Icon = "bi-headphones",
|
||||||
|
Badge = "badge-hit",
|
||||||
|
BadgeText = "Хит продаж"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 10,
|
||||||
|
Name = "Наушники AirBuds Mini",
|
||||||
|
Category = "Наушники",
|
||||||
|
Price = 2490m,
|
||||||
|
Specs = "Bluetooth 5.2 · 25 ч · зарядный кейс",
|
||||||
|
Icon = "bi-headphones",
|
||||||
|
Badge = "badge-new",
|
||||||
|
BadgeText = "Новинка"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 11,
|
||||||
|
Name = "Наушники Studio Over",
|
||||||
|
Category = "Наушники",
|
||||||
|
Price = 7990m,
|
||||||
|
Specs = "Проводные · студийные · 32 Ом",
|
||||||
|
Icon = "bi-headphones",
|
||||||
|
Badge = ""
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 12,
|
||||||
|
Name = "Клавиатура Mech RGB",
|
||||||
|
Category = "Аксессуары",
|
||||||
|
Price = 5490m,
|
||||||
|
Specs = "Механическая · RGB-подсветка · RU",
|
||||||
|
Icon = "bi-keyboard",
|
||||||
|
Badge = "badge-hot",
|
||||||
|
BadgeText = "Выгодно"
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 13,
|
||||||
|
Name = "Мышь Ergo Pro",
|
||||||
|
Category = "Аксессуары",
|
||||||
|
Price = 1990m,
|
||||||
|
Specs = "Беспроводная · 6000 DPI · тихие кнопки",
|
||||||
|
Icon = "bi-mouse",
|
||||||
|
Badge = ""
|
||||||
|
},
|
||||||
|
new Product
|
||||||
|
{
|
||||||
|
Id = 14,
|
||||||
|
Name = "Монитор View 27",
|
||||||
|
Category = "Аксессуары",
|
||||||
|
Price = 24990m,
|
||||||
|
Specs = "27\u2033 · QHD · 144 Гц · IPS",
|
||||||
|
Icon = "bi-display",
|
||||||
|
Badge = "badge-hit",
|
||||||
|
BadgeText = "Хит продаж"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> Categories =
|
||||||
|
[
|
||||||
|
"Ноутбуки",
|
||||||
|
"Смартфоны",
|
||||||
|
"Планшеты",
|
||||||
|
"Наушники",
|
||||||
|
"Аксессуары"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static IEnumerable<Product> GetHits()
|
||||||
|
{
|
||||||
|
return Products
|
||||||
|
.Where(p => p.BadgeText == "Хит продаж")
|
||||||
|
.Take(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Product> GetByFilter(string? category, string? search)
|
||||||
|
{
|
||||||
|
IEnumerable<Product> query = Products;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(category))
|
||||||
|
{
|
||||||
|
query = query.Where(p =>
|
||||||
|
p.Category.Equals(category, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
query = query.Where(p =>
|
||||||
|
p.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
p.Specs.Contains(search, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Models/Product.cs
Normal file
13
Models/Product.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace TechnoWorld.Models;
|
||||||
|
|
||||||
|
public class Product
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Category { get; set; } = string.Empty;
|
||||||
|
public decimal Price { get; set; }
|
||||||
|
public string Specs { get; set; } = string.Empty;
|
||||||
|
public string Icon { get; set; } = "bi-box";
|
||||||
|
public string Badge { get; set; } = "";
|
||||||
|
public string BadgeText { get; set; } = "";
|
||||||
|
}
|
||||||
12
README.md
12
README.md
|
|
@ -14,12 +14,20 @@ ASP.NET Core MVC проект интернет-магазина техники.
|
||||||
|
|
||||||
| Страница | Описание |
|
| Страница | Описание |
|
||||||
|----------------|-----------------------------------------------------------------|
|
|----------------|-----------------------------------------------------------------|
|
||||||
| **Главная** | Промо-баннер, преимущества магазина, хиты продаж, CTA |
|
| **Главная** | Промо-баннер, преимущества магазина, хиты продаж (из данных), CTA |
|
||||||
| **Каталог** | 4 карточки товаров с характеристиками и кнопкой «Купить» |
|
| **Каталог** | 14 товаров из данных, фильтр по 5 категориям, поиск |
|
||||||
| **О системе** | Описание сервиса, роли пользователей, план развития |
|
| **О системе** | Описание сервиса, роли пользователей, план развития |
|
||||||
| **Контакты** | Контактная информация, форма обратной связи с валидацией |
|
| **Контакты** | Контактная информация, форма обратной связи с валидацией |
|
||||||
| **404** | Страница ошибки с кнопкой возврата на главную |
|
| **404** | Страница ошибки с кнопкой возврата на главную |
|
||||||
|
|
||||||
|
### Каталог и данные
|
||||||
|
|
||||||
|
- Модель `Product` и хранилище `Data/ProductStore` с 14 товарами по 5 категориям
|
||||||
|
- Категории: Ноутбуки, Смартфоны, Планшеты, Наушники, Аксессуары
|
||||||
|
- Фильтр по категории: `Catalog/Index?category=...`
|
||||||
|
- Поиск по названию и характеристикам: `Catalog/Index?q=...`
|
||||||
|
- Общая частичная карточка `_ProductCard.cshtml` для каталога и хитов
|
||||||
|
|
||||||
### Технический стек
|
### Технический стек
|
||||||
|
|
||||||
- **ASP.NET Core MVC** (.NET 10)
|
- **ASP.NET Core MVC** (.NET 10)
|
||||||
|
|
|
||||||
|
|
@ -1,85 +1,58 @@
|
||||||
@{
|
@model IEnumerable<TechnoWorld.Models.Product>
|
||||||
|
@{
|
||||||
ViewData["Title"] = "Каталог";
|
ViewData["Title"] = "Каталог";
|
||||||
|
var categories = (List<string>?)ViewData["Categories"] ?? [];
|
||||||
|
var currentCategory = (string?)ViewData["CurrentCategory"];
|
||||||
|
var searchQuery = (string?)ViewData["SearchQuery"];
|
||||||
|
var count = Model.Count();
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="page-head">
|
<div class="page-head">
|
||||||
<span class="section-overline">Каталог</span>
|
<span class="section-overline">Каталог</span>
|
||||||
<h1 class="page-title">Каталог техники</h1>
|
<h1 class="page-title">Каталог техники</h1>
|
||||||
<p class="page-subtitle">4 товара · Ноутбуки, смартфоны, планшеты, наушники</p>
|
<p class="page-subtitle">@(count == 0 ? "Ничего не найдено" : count + (count == 1 ? " товар" : count < 5 ? " товара" : " товаров"))</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3 g-md-4">
|
<div class="catalog-toolbar">
|
||||||
|
<div class="catalog-filter">
|
||||||
|
<a class="catalog-filter__link @(string.IsNullOrEmpty(currentCategory) ? "is-active" : "")"
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
asp-controller="Catalog" asp-action="Index">Все</a>
|
||||||
<article class="product-card">
|
@foreach (var category in categories)
|
||||||
<span class="product-card__badge badge-hit">Хит продаж</span>
|
{
|
||||||
<div class="product-card__media"><i class="bi bi-laptop"></i></div>
|
<a class="catalog-filter__link @(category == currentCategory ? "is-active" : "")"
|
||||||
<div class="product-card__body">
|
asp-controller="Catalog" asp-action="Index"
|
||||||
<h5 class="product-card__name">Ноутбук Pro 15</h5>
|
asp-route-category="@category">@category</a>
|
||||||
<div class="product-card__specs">
|
}
|
||||||
<span>Экран 15.6" · 8 ГБ RAM · SSD 512 ГБ</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">89 990 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
|
||||||
<article class="product-card">
|
|
||||||
<span class="product-card__badge badge-hit">Хит продаж</span>
|
|
||||||
<div class="product-card__media"><i class="bi bi-phone"></i></div>
|
|
||||||
<div class="product-card__body">
|
|
||||||
<h5 class="product-card__name">Смартфон Nova X</h5>
|
|
||||||
<div class="product-card__specs">
|
|
||||||
<span>Экран 6.7" · Камера 108 Мп · 128 ГБ</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">29 990 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
|
||||||
<article class="product-card">
|
|
||||||
<span class="product-card__badge badge-new">Новинка</span>
|
|
||||||
<div class="product-card__media"><i class="bi bi-tablet"></i></div>
|
|
||||||
<div class="product-card__body">
|
|
||||||
<h5 class="product-card__name">Планшет Tab Lite</h5>
|
|
||||||
<div class="product-card__specs">
|
|
||||||
<span>Экран 11" · 4 ГБ RAM · 128 ГБ</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">18 490 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
|
||||||
<article class="product-card">
|
|
||||||
<span class="product-card__badge badge-hot">Выгодно</span>
|
|
||||||
<div class="product-card__media"><i class="bi bi-headphones"></i></div>
|
|
||||||
<div class="product-card__body">
|
|
||||||
<h5 class="product-card__name">Наушники SoundMax</h5>
|
|
||||||
<div class="product-card__specs">
|
|
||||||
<span>Bluetooth 5.3 · ANC · 40 ч автономной работы</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">3 990 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form class="catalog-search" asp-controller="Catalog" asp-action="Index" method="get">
|
||||||
|
<input class="catalog-search__input" type="search" name="q" value="@searchQuery"
|
||||||
|
placeholder="Поиск по каталогу..." aria-label="Поиск по каталогу" />
|
||||||
|
<button class="catalog-search__btn" type="submit" aria-label="Искать">
|
||||||
|
<i class="bi bi-search"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<div class="row g-3 g-md-4">
|
||||||
|
@foreach (var product in Model)
|
||||||
|
{
|
||||||
|
<div class="col-lg-3 col-md-6 col-12">
|
||||||
|
<partial name="_ProductCard" model="product" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="catalog-empty">
|
||||||
|
<span class="catalog-empty__icon"><i class="bi bi-search"></i></span>
|
||||||
|
<h2 class="catalog-empty__title">По вашему запросу ничего не найдено</h2>
|
||||||
|
<p class="catalog-empty__text">
|
||||||
|
Попробуйте изменить запрос или сбросить фильтр категории.
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-primary" asp-controller="Catalog" asp-action="Index">Сбросить фильтры</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
@ -65,80 +65,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3 g-md-4">
|
<div class="row g-3 g-md-4">
|
||||||
|
@foreach (var product in (IEnumerable<TechnoWorld.Models.Product>)ViewData["Hits"]!)
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
{
|
||||||
<article class="product-card">
|
<div class="col-lg-3 col-md-6 col-12">
|
||||||
|
<partial name="_ProductCard" model="product" />
|
||||||
<span class="product-card__badge badge-hit">Хит продаж</span>
|
</div>
|
||||||
|
}
|
||||||
<div class="product-card__media"><i class="bi bi-laptop"></i></div>
|
|
||||||
<div class="product-card__body">
|
|
||||||
<h5 class="product-card__name">Ноутбук Pro 15</h5>
|
|
||||||
|
|
||||||
<div class="product-card__specs">
|
|
||||||
<span>Экран 15.6" · 8 ГБ RAM · SSD 512 ГБ</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">89 990 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
|
||||||
<article class="product-card">
|
|
||||||
<span class="product-card__badge badge-hit">Хит продаж</span>
|
|
||||||
<div class="product-card__media"><i class="bi bi-phone"></i></div>
|
|
||||||
<div class="product-card__body">
|
|
||||||
<h5 class="product-card__name">Смартфон Nova X</h5>
|
|
||||||
<div class="product-card__specs">
|
|
||||||
<span>Экран 6.7" · Камера 108 Мп · 128 ГБ</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">29 990 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
|
||||||
<article class="product-card">
|
|
||||||
<span class="product-card__badge badge-new">Новинка</span>
|
|
||||||
<div class="product-card__media"><i class="bi bi-tablet"></i></div>
|
|
||||||
<div class="product-card__body">
|
|
||||||
<h5 class="product-card__name">Планшет Tab Lite</h5>
|
|
||||||
<div class="product-card__specs">
|
|
||||||
<span>Экран 11" · 4 ГБ RAM · 128 ГБ</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">18 490 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-lg-3 col-md-6 col-12">
|
|
||||||
<article class="product-card">
|
|
||||||
<span class="product-card__badge badge-hot">Выгодно</span>
|
|
||||||
<div class="product-card__media"><i class="bi bi-headphones"></i></div>
|
|
||||||
<div class="product-card__body">
|
|
||||||
<h5 class="product-card__name">Наушники SoundMax</h5>
|
|
||||||
<div class="product-card__specs">
|
|
||||||
<span>Bluetooth 5.3 · ANC · 40 ч</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-card__footer">
|
|
||||||
<span class="product-card__price">3 990 ₽</span>
|
|
||||||
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,11 +129,11 @@
|
||||||
<i class="bi bi-list"></i> Все товары
|
<i class="bi bi-list"></i> Все товары
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index">Ноутбуки</a>
|
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index" asp-route-category="Ноутбуки">Ноутбуки</a>
|
||||||
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index">Смартфоны</a>
|
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index" asp-route-category="Смартфоны">Смартфоны</a>
|
||||||
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index">Планшеты</a>
|
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index" asp-route-category="Планшеты">Планшеты</a>
|
||||||
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index">Наушники</a>
|
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index" asp-route-category="Наушники">Наушники</a>
|
||||||
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index">Аксессуары</a>
|
<a class="category-nav__link" asp-controller="Catalog" asp-action="Index" asp-route-category="Аксессуары">Аксессуары</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
@ -176,11 +176,11 @@
|
||||||
<div class="col-6 col-lg-3">
|
<div class="col-6 col-lg-3">
|
||||||
<h6 class="footer-heading">Каталог</h6>
|
<h6 class="footer-heading">Каталог</h6>
|
||||||
<ul class="footer-links">
|
<ul class="footer-links">
|
||||||
<li><a asp-controller="Catalog" asp-action="Index">Ноутбуки</a></li>
|
<li><a asp-controller="Catalog" asp-action="Index" asp-route-category="Ноутбуки">Ноутбуки</a></li>
|
||||||
<li><a asp-controller="Catalog" asp-action="Index">Смартфоны</a></li>
|
<li><a asp-controller="Catalog" asp-action="Index" asp-route-category="Смартфоны">Смартфоны</a></li>
|
||||||
<li><a asp-controller="Catalog" asp-action="Index">Планшеты</a></li>
|
<li><a asp-controller="Catalog" asp-action="Index" asp-route-category="Планшеты">Планшеты</a></li>
|
||||||
<li><a asp-controller="Catalog" asp-action="Index">Наушники</a></li>
|
<li><a asp-controller="Catalog" asp-action="Index" asp-route-category="Наушники">Наушники</a></li>
|
||||||
<li><a asp-controller="Catalog" asp-action="Index">Аксессуары</a></li>
|
<li><a asp-controller="Catalog" asp-action="Index" asp-route-category="Аксессуары">Аксессуары</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
19
Views/Shared/_ProductCard.cshtml
Normal file
19
Views/Shared/_ProductCard.cshtml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
@model TechnoWorld.Models.Product
|
||||||
|
|
||||||
|
<article class="product-card">
|
||||||
|
@if (!string.IsNullOrEmpty(Model.Badge))
|
||||||
|
{
|
||||||
|
<span class="product-card__badge @Model.Badge">@Model.BadgeText</span>
|
||||||
|
}
|
||||||
|
<div class="product-card__media"><i class="bi @Model.Icon"></i></div>
|
||||||
|
<div class="product-card__body">
|
||||||
|
<h5 class="product-card__name">@Model.Name</h5>
|
||||||
|
<div class="product-card__specs">
|
||||||
|
<span>@Model.Specs</span>
|
||||||
|
</div>
|
||||||
|
<div class="product-card__footer">
|
||||||
|
<span class="product-card__price">@Model.Price.ToString("N0") ₽</span>
|
||||||
|
<a href="#" class="btn btn-primary btn-sm product-card__btn">Купить</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
@ -999,6 +999,110 @@ html[data-theme="dark"] .btn-primary:hover {
|
||||||
box-shadow: 0 4px 10px rgba(255, 122, 69, 0.25);
|
box-shadow: 0 4px 10px rgba(255, 122, 69, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.catalog-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-filter__link {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: var(--bg-soft);
|
||||||
|
transition: background var(--transition), color var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-filter__link:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-filter__link.is-active {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-search {
|
||||||
|
display: flex;
|
||||||
|
min-width: 240px;
|
||||||
|
max-width: 340px;
|
||||||
|
flex: 1 1 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-search__input {
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-right: 0;
|
||||||
|
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
||||||
|
padding: 0.45rem 0.8rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background: var(--bg-body);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-search__input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-search__btn {
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
||||||
|
padding: 0 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-search__btn:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-empty__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--bg-soft);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-empty__title {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.catalog-empty__text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
|
|
||||||
.header-main {
|
.header-main {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user