使用ASP.NETCore MVC設計電子商務網站 - 2
這一篇文章中,將延續《使用ASP.NETCore MVC設計電子商務網站 - 1》一文的專案情境,說明如何使用ASP.NET Core MVC這個框架,來設計一個簡易的電子商務購物網站。前一期的文章中我們建立了網站所需的「CartItem」與「Product」模型 ,同時設計好控制器的程式碼,現在我們來看看檢視(View)的設計。
設計「Cart」檢視
「Cart」檢視用於顯示電子商務應用程式中的購物車內容。我們要以表格形式顯示購物車中的商品列表,包括商品資訊、價格、數量和每項商品的小計。此外也要提供調整數量和移除商品的選項。頁面還得計算並顯示購物車中所有商品的總計。
建立「Cart」檢視。將游標停留在「HomeController」控制器程式設計畫面「Cart」方法之中,按滑鼠右鍵,從快捷選單選取「Add View」,請參考下圖所示。
圖 1:加入檢視
在「Add New Scaffolded Item」視窗選取「Razor View」,請參考下圖所示:
圖 2:「Add New Scaffolded Item」視窗
在「Add Razor View」對話盒中,設定:
- View name:「Cart」
- Template:「Empty (without model)」
- 勾選「Use a layout page」核取方塊
然後按下「Add」按鈕。Visual Studio 2022便會在「Views\Home」資料夾下,新增一個「Cart.cshtml」檔案,請參考下圖所示:
圖 3:「Add Razor View」對話盒
在「Cart 」檢視加入以下程式碼:
Cart.cshtml@model IEnumerable<MVCShopping.Models.CartItem>
@{
ViewData[ "Title" ] = "購物車";
}
<div class = "container mt-4">
<!-- 購物車圖示和標題文字 -->
<div class = "row mb-4">
<div class = "col">
<h2 class = "text-center">
<i class = "bi bi-cart3 text-primary me-2"> </i> 購物車內容
</h2>
</div>
</div>
<!-- 如果購物車是空的 -->
@if ( !Model.Any( ) ) {
<div class = "text-center py-5">
<!-- 顯示空購物車圖示 -->
<i class = "bi bi-cart-x text-muted" style = "font-size: 4rem;"></i>
<!-- 顯示購物車是空的訊息 -->
<h3 class = "mt-3 text-muted"> 購物車是空的 </h3>
<a href = "@Url.Action("Index")" class = "btn btn-primary mt-3">
<!-- 繼續購物按鈕 -->
<i class = "bi bi-arrow-left me-2"> </i> 繼續購物
</a>
</div>
}
else {
<!-- 如果購物車有商品 , 使用卡片顯示商品-->
<div class = "card shadow-sm border-0">
<div class = "card-body">
<!-- 卡片內容 -->
<div class = "table-responsive">
<!-- 建立一個表格 -->
<table class = "table align-middle">
<!-- 表格標題 -->
<thead class = "table-light">
<tr>
<th>商品資訊</th>
<th class = "text-center"> 單價 </th>
<th class = "text-center"> 數量 </th>
<th class = "text-center"> 動作 </th>
<th class = "text-end"> 小計 </th>
</tr>
</thead>
<tbody>
<!-- 表格內容 -->
@foreach ( var item in Model ) {
<!-- 每個商品的<tr>,包含商品ID -->
<tr data-product-id = "@item.Product.Id">
<!-- 商品資訊欄 -->
<td>
<div class = "d-flex align-items-center">
<!-- 商品圖片 -->
<img src = "@item.Product.ImageUrl" alt = "@item.Product.Name"
class = "rounded me-3" style = "width: 80px; height: 60px; object-fit: cover;">
<div>
<!-- 商品名稱 -->
<h6 class = "mb-1"> @item.Product.Name </h6>
<!-- 商品描述 -->
<small class = "text-muted"> @item.Product.Description </small>
</div>
</div>
</td>
<!-- 單價欄 -->
<td class = "text-center"> NT$ @item.Product.Price.ToString( "N0" ) </td>
<!-- 數量欄-->
<td class = "text-center" style = "width: 200px;">
<!-- 數量輸入方塊 -->
<div class = "input-group" style = "width: 150px; margin: 0 auto;">
<!-- 減少數量按鈕 -->
<button class = "btn btn-outline-secondary quantity-minus" type = "button">
<!-- 減少數量圖示 -->
<i class = "bi bi-dash"> </i>
</button>
<!-- 數量輸入方塊 -->
<input type = "number" class = "form-control text-center quantity-input"
value = "@item.Quantity" min = "1" max = "99"
data-product-id = "@item.Product.Id">
<!-- 增加數量按鈕 -->
<button class = "btn btn-outline-secondary quantity-plus" type = "button">
<!-- 增加數量圖示 -->
<i class = "bi bi-plus"> </i>
</button>
</div>
</td>
<!-- 動作欄 -->
<td class="text-center" style = "width: 200px;">
<!-- 移除商品按鈕 -->
<button class = "btn btn-danger btn-sm remove-item" data-product-id = "@item.Product.Id" type = "button">
<!-- 移除圖示和文字 -->
<i class = "bi bi-trash"></i> 移除
</button>
</td>
<!-- 小計欄 -->
<td class = "text-end"> NT$ @(( item.Product.Price * item.Quantity ).ToString( "N0" )) </td>
</tr>
}
</tbody>
<!-- 表格底部 -->
<tfoot class = "table-light">
<tr>
<!-- 總計欄 -->
<td colspan = "3" class = "text-end"> <strong> 總計: </strong> </td>
<td class = "text-end">
<!-- 計算總計 -->
<strong class = "text-primary fs-4">
NT$ @Model.Sum( i => i.Product.Price * i.Quantity ).ToString( "N0" )
</strong>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class = "d-flex justify-content-between mt-4">
<!-- 繼續購物按鈕 -->
<a href = "@Url.Action("Index")" class = "btn btn-outline-primary">
<i class = "bi bi-arrow-left me-2"> </i> 繼續購物
</a>
<!-- 結帳按鈕 -->
<button class = "btn btn-primary">
<i class = "bi bi-credit-card me-2"> </i >結帳
</button>
</div>
}
</div>
@section Scripts {
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.quantity-minus, .quantity-plus').forEach(button => {
button.addEventListener('click', function() {
const input = this.parentElement.querySelector('.quantity-input');
let value = parseInt(input.value);
if ( this.classList.contains('quantity-minus') ) {
value = Math.max( 1, value - 1 );
} else {
value = Math.min( 99, value + 1 );
}
input.value = value;
updateQuantity(input);
});
});
document.querySelectorAll('.quantity-input').forEach(input => {
input.addEventListener('change', function() {
updateQuantity( this );
});
});
document.querySelectorAll('.remove-item').forEach(button => {
button.addEventListener('click', function() {
const productId = this.dataset.productId;
removeItem( productId );
});
});
function updateQuantity(input) {
const productId = input.dataset.productId;
let quantity = parseInt( input.value );
if (isNaN(quantity) || quantity < 1) quantity = 1;
if (quantity > 99) quantity = 99;
input.value = quantity;
fetch('/Home/UpdateCartQuantity', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `id=${productId}&quantity=${quantity}`
})
.then( response => response.json())
.then( data => {
if ( data.success ) {
location.reload();
} else {
alert( data.message );
}
})
.catch( error => {
alert( '操作失敗,請稍後再試' );
});
}
function removeItem(productId) {
fetch('/Home/RemoveFromCart', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `id=${productId}`
})
.then( response => response.json() )
.then( data => {
if ( data.success ) {
location.reload();
} else {
alert( data.message );
}
})
.catch( error => {
alert( '操作失敗,請稍後再試' );
});
}
});
</script>
}
設計「Index」檢視
「Index」檢視是網站的首頁,用於顯示電子商務應用程式中的商品清單。頁面需要展示每個商品的圖片、名稱、描述和價格,並提供一個按鈕讓使用者將商品加入購物車。當使用者點擊加入購物車按鈕時,會發送 POST 請求到伺服器以更新購物車,並顯示一個對話框通知使用者操作結果。這個頁面旨在提供一個直觀的介面,讓使用者可以輕鬆瀏覽和購買商品。
修改「Index」檢視加入以下程式碼:
Index
@model IEnumerable<MVCShopping.Models.Product>
@{
ViewData[ "Title" ] = "電子產品清單";
}
<div class = "container mt-4">
<div class = "row mb-4">
<div class = "col">
<h2 class = "text-center mb-4">
<i class = "bi bi-laptop text-primary"> </i> 精選商品
</h2>
</div>
</div>
<div class = "row row-cols-1 row-cols-md-3 g-4">
@foreach ( var product in Model ) {
<div class = "col">
<div class = "card h-100 border-0 rounded-3">
<div class = "position-relative">
<!-- 商品圖片 -->
<img src = "@product.ImageUrl" class = "card-img-top rounded-top product-image" alt = "@product.Name" style = "width: 400px; height: 300px;">
<div class = "position-absolute top-0 end-0 m-2">
<!-- 熱銷標籤 -->
<span class = "badge bg-primary rounded-pill">
<i class = "bi bi-tag-fill"> </i> 熱銷
</span>
</div>
</div>
<div class = "card-body">
<!-- 商品名稱 -->
<h5 class = "card-title">
<i class = "bi bi-phone me-2 text-primary"> </i> @product.Name
</h5>
<!-- 商品描述 -->
<p class = "card-text text-muted">
<i class = "bi bi-info-circle me-2"> </i> @product.Description
</p>
<div class = "d-flex justify-content-between align-items-center">
<!-- 商品價格 -->
<h4 class = "text-primary mb-0">
<i class = "bi bi-currency-dollar"> </i> @product.Price.ToString( "N0" )
</h4>
<!-- 加入購物車按鈕 -->
<form class = "add-to-cart-form" data-product-id = "@product.Id">
<button type = "submit" class = "btn btn-primary">
<i class = "bi bi-cart-plus me-2"> </i> 加入購物車
</button>
</form>
</div>
</div>
</div>
</div>
}
</div>
</div>
<!-- Bootstrap 對話盒 -->
<div class = "modal fade" id = "cartModal" tabindex = "-1" aria-labelledby = "cartModalLabel" aria-hidden = "true">
<div class = "modal-dialog">
<div class = "modal-content">
<div class = "modal-header bg-primary text-white">
<h5 class = "modal-title" id = "cartModalLabel">
<i class = "bi bi-cart-check me-2"> </i> 購物車訊息
</h5>
<button type = "button" class = "btn-close btn-close-white" data-bs-dismiss = "modal" aria-label = "Close"> </button>
</div>
<div class = "modal-body">
<p id = "modalMessage" class = "mb-0"> </p>
</div>
<div class = "modal-footer">
<button type = "button" class = "btn btn-primary" data-bs-dismiss = "modal">
<i class = "bi bi-check2 me-2"> </i> 確定
</button>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
document.addEventListener('DOMContentLoaded', function () {
const cartModal = new bootstrap.Modal(document.getElementById('cartModal'));
document.querySelectorAll('.add-to-cart-form').forEach( form => {
form.addEventListener('submit', function (e) {
e.preventDefault();
const productId = this.getAttribute('data-product-id');
fetch('/Home/AddToCart', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `id=${productId}`
})
.then( response => response.json() )
.then( data => {
document.getElementById('modalMessage').innerHTML = `
<i class="bi bi-check-circle-fill text-success me-2"></i>${data.message}
`;
cartModal.show();
updateCartBadge(data.cartQuantity);
})
.catch( error => {
document.getElementById('modalMessage').innerHTML = `
<i class="bi bi-exclamation-circle-fill text-danger me-2"></i>操作失敗,請稍後再試
`;
cartModal.show();
});
});
});
});
function updateCartBadge( quantity ) {
const cartBadge = document.querySelector('.navbar .badge');
if ( cartBadge ) {
cartBadge.textContent = quantity;
}
}
</script>
}
設計版面配置
「_Layout」檢視定義電子商務應用程式的整體頁面結構和樣式,如導覽列。這個檔案引用了「Bootstrap」、「bootstrap-icons」的 CSS 和 JavaScript 檔案,以確保頁面具有一致的外觀和互動性。
_Layout.cshtml
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "utf-8" />
<meta name = "viewport" content = "width = device-width, initial-scale = 1.0" />
<title> @ViewData[ "Title" ] – MVCShopping </title>
<!--引用Bootstrap CSS檔案-->
<link rel = "stylesheet" href = "~/lib/bootstrap/dist/css/bootstrap.min.css" />
<!--引用Bootstrap icons 檔案-->
<link href = "~/lib/bootstrap-icons/font/bootstrap-icons.min.css" rel = "stylesheet" />
<link rel = "stylesheet" href = "~/css/site.css" asp-append-version = "true" />
<link rel = "stylesheet" href = "~/MVCShopping.styles.css" asp-append-version = "true" />
<style>
.navbar-custom {
background: linear-gradient(135deg, #13547a 0%, #80d0c7 100%);
}
.footer {
background: linear-gradient(135deg, #13547a 0%, #80d0c7 100%);
color: white !important;
}
.card {
transition: transform 0.2s;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.card:hover {
transform: translateY(-5px);
}
</style>
</head>
<body class = "bg-light">
<header>
<!-- 導覽列 -->
<nav class = "navbar navbar-expand-sm navbar-dark navbar-custom">
<div class = "container">
<a class = "navbar-brand" asp-area = "" asp-controller = "Home" asp-action = "Index">
<i class = "bi bi-shop me-2"> </i> EShop
</a>
<button class = "navbar-toggler" type = "button" data-bs-toggle = "collapse" data-bs-target = ".navbar-collapse">
<span class = "navbar-toggler-icon"></span>
</button>
<div class = "navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class = "navbar-nav flex-grow-1">
<li class = "nav-item">
<!--首頁連結-->
<a class = "nav-link text-white" asp-controller = "Home" asp-action = "Index">
<i class = "bi bi-house-door me-1"> </i> 首頁
</a>
</li>
</ul>
<ul class = "navbar-nav">
<li class = "nav-item">
<!--購物車連結-->
<a class = "nav-link text-white position-relative" asp-controller = "Home" asp-action = "Cart">
<i class = "bi bi-cart3 fs-5"></i>
<!--顯示購物車數量-->
<span class = "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" id = "cart-badge">
@(ViewBag.CartQuantity ?? 0)
</span>
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<!--View的內容-->
<div class = "container mt-4">
<main role = "main" class = "pb-3">
@RenderBody( )
</main>
</div>
<!--頁尾-->
<footer class = "footer border-top text-muted py-3 mt-4">
<div class = "container text-center text-white">
<i class = "bi bi-heart-fill text-danger"> </i> @DateTime.Now.Year - EShop - 您的線上購物首選
</div>
</footer>
<script src = "~/lib/jquery/dist/jquery.min.js"> </script>
<!--引用Bootstrap JavaScript檔案-->
<script src = "~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"> </script>
<script src = "~/js/site.js" asp-append-version = "true"> </script>
@await RenderSectionAsync( "Scripts" , required: false )
</body>
</html>
網站測試
在Visual Studio 開發工具執行這個專案,首頁可以看到商品清單,點選其中一個商品的「加入購物車」按鈕,網頁上方就會跳出確認對話盒,請參考下圖所示:
圖 4:「加入購物車」
您可以點選畫面右上方購物車圖示,便可看到目前購物車的內容,請參考下圖所示:
圖 5:購物車的內容
0 意見:
張貼留言