From 27f9495a65c243141a15b9d51b9397940e1f7d26 Mon Sep 17 00:00:00 2001 From: Triton Date: Mon, 14 Sep 2026 03:31:20 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=B0=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85:=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D1=82=D1=80=20=D0=BF=D0=BE=20=D0=BA=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D1=8F=D0=BC,=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B8=D1=81=D0=BA,=20=D0=BF=D1=83=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B5=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controllers/CatalogController.cs | 19 +-- Controllers/HomeController.cs | 21 +--- Data/ProductStore.cs | 197 +++++++++++++++++++++++++++++++ Models/Product.cs | 13 ++ README.md | 12 +- Views/Catalog/Index.cshtml | 121 ++++++++----------- Views/Home/Index.cshtml | 80 +------------ Views/Shared/_Layout.cshtml | 20 ++-- Views/Shared/_ProductCard.cshtml | 19 +++ wwwroot/css/site.css | 104 ++++++++++++++++ 10 files changed, 422 insertions(+), 184 deletions(-) create mode 100644 Data/ProductStore.cs create mode 100644 Models/Product.cs create mode 100644 Views/Shared/_ProductCard.cshtml diff --git a/Controllers/CatalogController.cs b/Controllers/CatalogController.cs index 146f2bc..9b222d2 100644 --- a/Controllers/CatalogController.cs +++ b/Controllers/CatalogController.cs @@ -1,18 +1,23 @@ - -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; +using TechnoWorld.Data; using TechnoWorld.Models; namespace TechnoWorld.Controllers; public class CatalogController : Controller { - - public IActionResult Index() + [HttpGet] + public IActionResult Index(string? category = null, string? q = null) { - ViewData["Title"] = "Каталог"; - return View(); + var products = ProductStore.GetByFilter(category, q); + + ViewData["Categories"] = ProductStore.Categories; + ViewData["CurrentCategory"] = category; + ViewData["SearchQuery"] = q; + + return View(products); } -} +} \ No newline at end of file diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index a5991d0..ea09427 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -1,15 +1,14 @@ - -using System.Diagnostics; +using System.Diagnostics; using Microsoft.AspNetCore.Mvc; +using TechnoWorld.Data; using TechnoWorld.Models; namespace TechnoWorld.Controllers; public class HomeController : Controller { - private readonly ILogger _logger; public HomeController(ILogger logger) @@ -19,39 +18,30 @@ public class HomeController : Controller public IActionResult Index() { - ViewData["Title"] = "Главная"; - + ViewData["Hits"] = ProductStore.GetHits(); return View(); } public IActionResult About() { - ViewData["Title"] = "О системе"; - return View(); } [HttpGet] public IActionResult Contacts() { - ViewData["Title"] = "Контакты"; - return View(); } [HttpPost] - public IActionResult Contacts(ContactViewModel model) { - if (ModelState.IsValid) { - TempData["ContactSuccess"] = "Ваше сообщение принято! Мы свяжемся с вами в ближайшее время."; - return RedirectToAction(nameof(Contacts)); } @@ -60,14 +50,11 @@ public class HomeController : Controller } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error(int? statusCode = null) { - var model = new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, - StatusCode = statusCode }; @@ -78,4 +65,4 @@ public class HomeController : Controller return View(model); } -} +} \ No newline at end of file diff --git a/Data/ProductStore.cs b/Data/ProductStore.cs new file mode 100644 index 0000000..838802d --- /dev/null +++ b/Data/ProductStore.cs @@ -0,0 +1,197 @@ +using TechnoWorld.Models; + +namespace TechnoWorld.Data; + +public static class ProductStore +{ + public static readonly List 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 Categories = + [ + "Ноутбуки", + "Смартфоны", + "Планшеты", + "Наушники", + "Аксессуары" + ]; + + public static IEnumerable GetHits() + { + return Products + .Where(p => p.BadgeText == "Хит продаж") + .Take(4); + } + + public static IEnumerable GetByFilter(string? category, string? search) + { + IEnumerable 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(); + } +} \ No newline at end of file diff --git a/Models/Product.cs b/Models/Product.cs new file mode 100644 index 0000000..7c9d4fe --- /dev/null +++ b/Models/Product.cs @@ -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; } = ""; +} \ No newline at end of file diff --git a/README.md b/README.md index 51b6b12..9c078d7 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,20 @@ ASP.NET Core MVC проект интернет-магазина техники. | Страница | Описание | |----------------|-----------------------------------------------------------------| -| **Главная** | Промо-баннер, преимущества магазина, хиты продаж, CTA | -| **Каталог** | 4 карточки товаров с характеристиками и кнопкой «Купить» | +| **Главная** | Промо-баннер, преимущества магазина, хиты продаж (из данных), CTA | +| **Каталог** | 14 товаров из данных, фильтр по 5 категориям, поиск | | **О системе** | Описание сервиса, роли пользователей, план развития | | **Контакты** | Контактная информация, форма обратной связи с валидацией | | **404** | Страница ошибки с кнопкой возврата на главную | +### Каталог и данные + +- Модель `Product` и хранилище `Data/ProductStore` с 14 товарами по 5 категориям +- Категории: Ноутбуки, Смартфоны, Планшеты, Наушники, Аксессуары +- Фильтр по категории: `Catalog/Index?category=...` +- Поиск по названию и характеристикам: `Catalog/Index?q=...` +- Общая частичная карточка `_ProductCard.cshtml` для каталога и хитов + ### Технический стек - **ASP.NET Core MVC** (.NET 10) diff --git a/Views/Catalog/Index.cshtml b/Views/Catalog/Index.cshtml index c261137..ad27a61 100644 --- a/Views/Catalog/Index.cshtml +++ b/Views/Catalog/Index.cshtml @@ -1,85 +1,58 @@ -@{ +@model IEnumerable +@{ ViewData["Title"] = "Каталог"; + var categories = (List?)ViewData["Categories"] ?? []; + var currentCategory = (string?)ViewData["CurrentCategory"]; + var searchQuery = (string?)ViewData["SearchQuery"]; + var count = Model.Count(); }
Каталог

Каталог техники

-

4 товара · Ноутбуки, смартфоны, планшеты, наушники

+

@(count == 0 ? "Ничего не найдено" : count + (count == 1 ? " товар" : count < 5 ? " товара" : " товаров"))

-
- - -
-
- Хит продаж -
-
-
Ноутбук Pro 15
-
- Экран 15.6" · 8 ГБ RAM · SSD 512 ГБ -
- -
-
-
- - -
-
- Хит продаж -
-
-
Смартфон Nova X
-
- Экран 6.7" · Камера 108 Мп · 128 ГБ -
- -
-
-
- - -
-
- Новинка -
-
-
Планшет Tab Lite
-
- Экран 11" · 4 ГБ RAM · 128 ГБ -
- -
-
-
- - -
-
- Выгодно -
-
-
Наушники SoundMax
-
- Bluetooth 5.3 · ANC · 40 ч автономной работы -
- -
-
+
+
+ Все + @foreach (var category in categories) + { + @category + }
+
+ +@if (Model.Any()) +{ +
+ @foreach (var product in Model) + { +
+ +
+ } +
+} +else +{ +
+ +

По вашему запросу ничего не найдено

+

+ Попробуйте изменить запрос или сбросить фильтр категории. +

+ Сбросить фильтры +
+} \ No newline at end of file diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 45ee948..8146c91 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -65,80 +65,12 @@
- -
-
- - Хит продаж - -
-
-
Ноутбук Pro 15
- -
- Экран 15.6" · 8 ГБ RAM · SSD 512 ГБ -
- -
-
-
- - -
-
- Хит продаж -
-
-
Смартфон Nova X
-
- Экран 6.7" · Камера 108 Мп · 128 ГБ -
- -
-
-
- - -
-
- Новинка -
-
-
Планшет Tab Lite
-
- Экран 11" · 4 ГБ RAM · 128 ГБ -
- -
-
-
- - -
-
- Выгодно -
-
-
Наушники SoundMax
-
- Bluetooth 5.3 · ANC · 40 ч -
- -
-
-
+ @foreach (var product in (IEnumerable)ViewData["Hits"]!) + { +
+ +
+ }
diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index cc9cfa4..5b95df9 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -129,11 +129,11 @@ Все товары - Ноутбуки - Смартфоны - Планшеты - Наушники - Аксессуары + Ноутбуки + Смартфоны + Планшеты + Наушники + Аксессуары
@@ -176,11 +176,11 @@ diff --git a/Views/Shared/_ProductCard.cshtml b/Views/Shared/_ProductCard.cshtml new file mode 100644 index 0000000..4250eb3 --- /dev/null +++ b/Views/Shared/_ProductCard.cshtml @@ -0,0 +1,19 @@ +@model TechnoWorld.Models.Product + +
+ @if (!string.IsNullOrEmpty(Model.Badge)) + { + @Model.BadgeText + } +
+
+
@Model.Name
+
+ @Model.Specs +
+ +
+
\ No newline at end of file diff --git a/wwwroot/css/site.css b/wwwroot/css/site.css index 1e03abf..54572d4 100644 --- a/wwwroot/css/site.css +++ b/wwwroot/css/site.css @@ -999,6 +999,110 @@ html[data-theme="dark"] .btn-primary:hover { 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) { .header-main {