活动公告

系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

AngularJS购物车项目实战教程详解数据绑定与状态管理构建专业电商应用从基础到高级完整实现购物车功能

SunJu_FaceMall

3万

主题

2720

科技点

3万

积分

执行版主

碾压王

积分
32881

塔罗立华奏

执行版主 发表于 2025-8-27 16:00:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. 引言

AngularJS作为一款由Google维护的前端JavaScript框架,以其强大的数据绑定和依赖注入特性,在构建单页应用(SPA)方面表现出色。电商应用是AngularJS的经典应用场景之一,而购物车功能则是电商应用的核心。本教程将带你从零开始,通过一个完整的购物车项目,深入理解AngularJS的数据绑定机制与状态管理方法,构建一个专业的电商应用购物车功能。

在开始之前,请确保你已经具备了基本的HTML、CSS和JavaScript知识,并对AngularJS有初步了解。我们将逐步构建这个项目,从基础功能到高级特性,让你全面掌握AngularJS在电商应用中的实际应用。

2. AngularJS基础与项目准备

2.1 AngularJS核心概念

在开始我们的购物车项目之前,让我们先回顾一些AngularJS的核心概念:

• 数据绑定:AngularJS的双向数据绑定是其最强大的特性之一。它允许模型(model)和视图(view)之间的自动同步,当模型数据发生变化时,视图会自动更新,反之亦然。
• 控制器(Controller):控制器是AngularJS应用中的JavaScript构造函数,用于设置作用域($scope)的初始状态并向作用域添加行为。
• 作用域(Scope):作用域是连接控制器和视图的桥梁,它是一个对象,包含应用的数据和函数。
• 服务(Service):服务是用于组织应用中可复用代码的单例对象,可以用于在控制器之间共享数据或功能。

数据绑定:AngularJS的双向数据绑定是其最强大的特性之一。它允许模型(model)和视图(view)之间的自动同步,当模型数据发生变化时,视图会自动更新,反之亦然。

控制器(Controller):控制器是AngularJS应用中的JavaScript构造函数,用于设置作用域($scope)的初始状态并向作用域添加行为。

作用域(Scope):作用域是连接控制器和视图的桥梁,它是一个对象,包含应用的数据和函数。

服务(Service):服务是用于组织应用中可复用代码的单例对象,可以用于在控制器之间共享数据或功能。

2.2 项目设置

首先,让我们创建项目的基本结构:
  1. angularjs-shopping-cart/
  2. ├── index.html
  3. ├── css/
  4. │   └── style.css
  5. ├── js/
  6. │   ├── app.js
  7. │   ├── controllers/
  8. │   │   ├── productController.js
  9. │   │   └── cartController.js
  10. │   ├── services/
  11. │   │   ├── productService.js
  12. │   │   └── cartService.js
  13. │   └── directives/
  14. │       └── productDirective.js
  15. └── data/
  16.     └── products.json
复制代码

在index.html中,我们需要引入AngularJS库和我们自己的JavaScript文件:
  1. <!DOCTYPE html>
  2. <html lang="zh-CN" ng-app="shoppingCartApp">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>AngularJS 购物车</title>
  7.     <link rel="stylesheet" href="css/style.css">
  8.     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
  9.     <script src="js/app.js"></script>
  10.     <script src="js/services/productService.js"></script>
  11.     <script src="js/services/cartService.js"></script>
  12.     <script src="js/controllers/productController.js"></script>
  13.     <script src="js/controllers/cartController.js"></script>
  14.     <script src="js/directives/productDirective.js"></script>
  15. </head>
  16. <body>
  17.     <div class="container">
  18.         <header>
  19.             <h1>AngularJS 购物车示例</h1>
  20.         </header>
  21.         
  22.         <div class="content">
  23.             <!-- 商品列表区域 -->
  24.             <div ng-controller="ProductController as productCtrl">
  25.                 <h2>商品列表</h2>
  26.                 <div class="product-list">
  27.                     <!-- 商品将通过AngularJS动态加载 -->
  28.                 </div>
  29.             </div>
  30.             
  31.             <!-- 购物车区域 -->
  32.             <div ng-controller="CartController as cartCtrl">
  33.                 <h2>购物车</h2>
  34.                 <div class="cart">
  35.                     <!-- 购物车内容将通过AngularJS动态加载 -->
  36.                 </div>
  37.             </div>
  38.         </div>
  39.     </div>
  40. </body>
  41. </html>
复制代码

2.3 创建AngularJS应用模块

在js/app.js中,我们创建AngularJS应用模块:
  1. // 创建AngularJS应用模块
  2. var app = angular.module('shoppingCartApp', []);
  3. // 应用配置
  4. app.config(['$httpProvider', function($httpProvider) {
  5.     // 配置HTTP请求
  6.     $httpProvider.defaults.headers.common['Accept'] = 'application/json';
  7.     $httpProvider.defaults.headers.post['Content-Type'] = 'application/json';
  8. }]);
  9. // 应用运行
  10. app.run(['$rootScope', function($rootScope) {
  11.     // 初始化应用
  12.     console.log('购物车应用已启动');
  13.    
  14.     // 全局错误处理
  15.     $rootScope.$on('$routeChangeError', function(event, current, previous, rejection) {
  16.         console.error('路由变更错误:', rejection);
  17.     });
  18. }]);
复制代码

3. 商品展示功能

3.1 创建商品数据

首先,我们创建一些示例商品数据。在data/products.json文件中:
  1. [
  2.     {
  3.         "id": 1,
  4.         "name": "iPhone 13 Pro",
  5.         "description": "苹果最新款智能手机,配备A15仿生芯片",
  6.         "price": 7999,
  7.         "image": "images/iphone13pro.jpg",
  8.         "category": "手机",
  9.         "stock": 10
  10.     },
  11.     {
  12.         "id": 2,
  13.         "name": "MacBook Pro 14寸",
  14.         "description": "配备M1 Pro芯片的专业级笔记本电脑",
  15.         "price": 14999,
  16.         "image": "images/macbookpro14.jpg",
  17.         "category": "电脑",
  18.         "stock": 5
  19.     },
  20.     {
  21.         "id": 3,
  22.         "name": "AirPods Pro",
  23.         "description": "主动降噪无线耳机",
  24.         "price": 1999,
  25.         "image": "images/airpodspro.jpg",
  26.         "category": "配件",
  27.         "stock": 20
  28.     },
  29.     {
  30.         "id": 4,
  31.         "name": "iPad Air",
  32.         "description": "轻薄强大的平板电脑",
  33.         "price": 4399,
  34.         "image": "images/ipadair.jpg",
  35.         "category": "平板",
  36.         "stock": 8
  37.     },
  38.     {
  39.         "id": 5,
  40.         "name": "Apple Watch Series 7",
  41.         "description": "全天候健康监测智能手表",
  42.         "price": 2999,
  43.         "image": "images/applewatch7.jpg",
  44.         "category": "穿戴设备",
  45.         "stock": 15
  46.     }
  47. ]
复制代码

3.2 创建商品服务

在js/services/productService.js中,我们创建一个服务来获取商品数据:
  1. // 商品服务
  2. app.service('ProductService', ['$http', '$q', function($http, $q) {
  3.     var self = this;
  4.    
  5.     // 获取所有商品
  6.     self.getProducts = function() {
  7.         var deferred = $q.defer();
  8.         
  9.         $http.get('data/products.json')
  10.             .then(function(response) {
  11.                 deferred.resolve(response.data);
  12.             })
  13.             .catch(function(error) {
  14.                 deferred.reject(error);
  15.                 console.error('获取商品数据失败:', error);
  16.             });
  17.         
  18.         return deferred.promise;
  19.     };
  20.    
  21.     // 根据ID获取商品
  22.     self.getProductById = function(id) {
  23.         var deferred = $q.defer();
  24.         
  25.         self.getProducts().then(function(products) {
  26.             var product = products.find(function(p) {
  27.                 return p.id === id;
  28.             });
  29.             
  30.             if (product) {
  31.                 deferred.resolve(product);
  32.             } else {
  33.                 deferred.reject('未找到ID为 ' + id + ' 的商品');
  34.             }
  35.         }).catch(function(error) {
  36.             deferred.reject(error);
  37.         });
  38.         
  39.         return deferred.promise;
  40.     };
  41.    
  42.     // 根据类别获取商品
  43.     self.getProductsByCategory = function(category) {
  44.         var deferred = $q.defer();
  45.         
  46.         self.getProducts().then(function(products) {
  47.             var filteredProducts = products.filter(function(p) {
  48.                 return p.category === category;
  49.             });
  50.             
  51.             deferred.resolve(filteredProducts);
  52.         }).catch(function(error) {
  53.             deferred.reject(error);
  54.         });
  55.         
  56.         return deferred.promise;
  57.     };
  58. }]);
复制代码

3.3 创建商品控制器

在js/controllers/productController.js中,我们创建一个控制器来管理商品展示:
  1. // 商品控制器
  2. app.controller('ProductController', ['ProductService', 'CartService', '$scope', function(ProductService, CartService, $scope) {
  3.     var self = this;
  4.    
  5.     // 初始化变量
  6.     self.products = [];
  7.     self.loading = true;
  8.     self.error = null;
  9.     self.categories = [];
  10.     self.selectedCategory = null;
  11.    
  12.     // 获取商品数据
  13.     self.loadProducts = function() {
  14.         self.loading = true;
  15.         self.error = null;
  16.         
  17.         ProductService.getProducts()
  18.             .then(function(products) {
  19.                 self.products = products;
  20.                 self.extractCategories();
  21.                 self.loading = false;
  22.             })
  23.             .catch(function(error) {
  24.                 self.error = '加载商品失败: ' + error;
  25.                 self.loading = false;
  26.             });
  27.     };
  28.    
  29.     // 提取商品类别
  30.     self.extractCategories = function() {
  31.         var categoriesMap = {};
  32.         
  33.         self.products.forEach(function(product) {
  34.             categoriesMap[product.category] = true;
  35.         });
  36.         
  37.         self.categories = Object.keys(categoriesMap);
  38.     };
  39.    
  40.     // 根据类别筛选商品
  41.     self.filterByCategory = function(category) {
  42.         self.selectedCategory = category;
  43.     };
  44.    
  45.     // 获取筛选后的商品
  46.     self.getFilteredProducts = function() {
  47.         if (!self.selectedCategory) {
  48.             return self.products;
  49.         }
  50.         
  51.         return self.products.filter(function(product) {
  52.             return product.category === self.selectedCategory;
  53.         });
  54.     };
  55.    
  56.     // 添加商品到购物车
  57.     self.addToCart = function(product) {
  58.         CartService.addToCart(product, 1)
  59.             .then(function() {
  60.                 // 显示成功消息
  61.                 self.showMessage(product.name + ' 已添加到购物车', 'success');
  62.             })
  63.             .catch(function(error) {
  64.                 // 显示错误消息
  65.                 self.showMessage('添加失败: ' + error, 'error');
  66.             });
  67.     };
  68.    
  69.     // 显示消息
  70.     self.showMessage = function(message, type) {
  71.         self.message = message;
  72.         self.messageType = type;
  73.         
  74.         // 3秒后清除消息
  75.         $timeout(function() {
  76.             self.message = null;
  77.             self.messageType = null;
  78.         }, 3000);
  79.     };
  80.    
  81.     // 初始化
  82.     self.loadProducts();
  83. }]);
复制代码

3.4 创建商品指令

为了更好地展示商品,我们创建一个自定义指令。在js/directives/productDirective.js中:
  1. // 商品指令
  2. app.directive('productCard', function() {
  3.     return {
  4.         restrict: 'E', // 作为元素使用
  5.         scope: {
  6.             product: '=', // 双向绑定
  7.             onAddToCart: '&' // 父作用域的方法
  8.         },
  9.         templateUrl: 'templates/product-card.html', // 模板URL
  10.         controller: function($scope) {
  11.             // 添加商品到购物车
  12.             $scope.addToCart = function() {
  13.                 $scope.onAddToCart({product: $scope.product});
  14.             };
  15.         }
  16.     };
  17. });
复制代码

创建模板文件templates/product-card.html:
  1. <div class="product-card">
  2.     <div class="product-image">
  3.         <img ng-src="{{product.image}}" alt="{{product.name}}" onerror="this.src='images/placeholder.jpg'">
  4.     </div>
  5.     <div class="product-info">
  6.         <h3 class="product-name">{{product.name}}</h3>
  7.         <p class="product-description">{{product.description}}</p>
  8.         <div class="product-meta">
  9.             <span class="product-category">{{product.category}}</span>
  10.             <span class="product-stock">库存: {{product.stock}}</span>
  11.         </div>
  12.         <div class="product-price">
  13.             ¥{{product.price | number:2}}
  14.         </div>
  15.         <button class="add-to-cart-btn" ng-click="addToCart()" ng-disabled="product.stock <= 0">
  16.             {{product.stock > 0 ? '加入购物车' : '缺货'}}
  17.         </button>
  18.     </div>
  19. </div>
复制代码

3.5 更新商品列表视图

现在,我们更新index.html中的商品列表区域:
  1. <!-- 商品列表区域 -->
  2. <div ng-controller="ProductController as productCtrl">
  3.     <h2>商品列表</h2>
  4.    
  5.     <!-- 加载状态 -->
  6.     <div ng-if="productCtrl.loading" class="loading">
  7.         <p>正在加载商品...</p>
  8.     </div>
  9.    
  10.     <!-- 错误状态 -->
  11.     <div ng-if="productCtrl.error" class="error">
  12.         <p>{{productCtrl.error}}</p>
  13.     </div>
  14.    
  15.     <!-- 消息提示 -->
  16.     <div ng-if="productCtrl.message" class="message {{productCtrl.messageType}}">
  17.         <p>{{productCtrl.message}}</p>
  18.     </div>
  19.    
  20.     <!-- 类别筛选 -->
  21.     <div class="category-filter">
  22.         <button ng-class="{active: !productCtrl.selectedCategory}"
  23.                 ng-click="productCtrl.filterByCategory(null)">
  24.             全部商品
  25.         </button>
  26.         <button ng-repeat="category in productCtrl.categories"
  27.                 ng-class="{active: productCtrl.selectedCategory === category}"
  28.                 ng-click="productCtrl.filterByCategory(category)">
  29.             {{category}}
  30.         </button>
  31.     </div>
  32.    
  33.     <!-- 商品列表 -->
  34.     <div class="product-list">
  35.         <product-card ng-repeat="product in productCtrl.getFilteredProducts()"
  36.                       product="product"
  37.                       on-add-to-cart="productCtrl.addToCart(product)">
  38.         </product-card>
  39.     </div>
  40. </div>
复制代码

3.6 添加样式

在css/style.css中添加一些基本样式:
  1. /* 基本样式 */
  2. * {
  3.     box-sizing: border-box;
  4.     margin: 0;
  5.     padding: 0;
  6. }
  7. body {
  8.     font-family: 'Arial', sans-serif;
  9.     line-height: 1.6;
  10.     color: #333;
  11.     background-color: #f8f8f8;
  12. }
  13. .container {
  14.     max-width: 1200px;
  15.     margin: 0 auto;
  16.     padding: 20px;
  17. }
  18. header {
  19.     text-align: center;
  20.     margin-bottom: 30px;
  21.     padding-bottom: 20px;
  22.     border-bottom: 1px solid #ddd;
  23. }
  24. .content {
  25.     display: flex;
  26.     flex-wrap: wrap;
  27.     gap: 30px;
  28. }
  29. .content > div {
  30.     flex: 1;
  31.     min-width: 300px;
  32. }
  33. /* 商品列表样式 */
  34. .product-list {
  35.     display: grid;
  36.     grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  37.     gap: 20px;
  38. }
  39. .product-card {
  40.     background: white;
  41.     border-radius: 8px;
  42.     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  43.     overflow: hidden;
  44.     transition: transform 0.3s ease;
  45. }
  46. .product-card:hover {
  47.     transform: translateY(-5px);
  48. }
  49. .product-image {
  50.     height: 200px;
  51.     overflow: hidden;
  52. }
  53. .product-image img {
  54.     width: 100%;
  55.     height: 100%;
  56.     object-fit: cover;
  57. }
  58. .product-info {
  59.     padding: 15px;
  60. }
  61. .product-name {
  62.     font-size: 18px;
  63.     margin-bottom: 10px;
  64. }
  65. .product-description {
  66.     font-size: 14px;
  67.     color: #666;
  68.     margin-bottom: 10px;
  69.     height: 40px;
  70.     overflow: hidden;
  71. }
  72. .product-meta {
  73.     display: flex;
  74.     justify-content: space-between;
  75.     margin-bottom: 10px;
  76.     font-size: 12px;
  77.     color: #888;
  78. }
  79. .product-price {
  80.     font-size: 20px;
  81.     font-weight: bold;
  82.     color: #e74c3c;
  83.     margin-bottom: 15px;
  84. }
  85. .add-to-cart-btn {
  86.     width: 100%;
  87.     padding: 10px;
  88.     background-color: #3498db;
  89.     color: white;
  90.     border: none;
  91.     border-radius: 4px;
  92.     cursor: pointer;
  93.     font-size: 16px;
  94.     transition: background-color 0.3s;
  95. }
  96. .add-to-cart-btn:hover {
  97.     background-color: #2980b9;
  98. }
  99. .add-to-cart-btn:disabled {
  100.     background-color: #95a5a6;
  101.     cursor: not-allowed;
  102. }
  103. /* 类别筛选样式 */
  104. .category-filter {
  105.     margin-bottom: 20px;
  106.     display: flex;
  107.     flex-wrap: wrap;
  108.     gap: 10px;
  109. }
  110. .category-filter button {
  111.     padding: 8px 15px;
  112.     background-color: #ecf0f1;
  113.     border: 1px solid #bdc3c7;
  114.     border-radius: 20px;
  115.     cursor: pointer;
  116.     transition: all 0.3s;
  117. }
  118. .category-filter button.active {
  119.     background-color: #3498db;
  120.     color: white;
  121.     border-color: #3498db;
  122. }
  123. /* 消息样式 */
  124. .message {
  125.     padding: 10px 15px;
  126.     margin-bottom: 20px;
  127.     border-radius: 4px;
  128. }
  129. .message.success {
  130.     background-color: #d4edda;
  131.     color: #155724;
  132.     border: 1px solid #c3e6cb;
  133. }
  134. .message.error {
  135.     background-color: #f8d7da;
  136.     color: #721c24;
  137.     border: 1px solid #f5c6cb;
  138. }
  139. /* 加载和错误状态样式 */
  140. .loading, .error {
  141.     text-align: center;
  142.     padding: 20px;
  143.     background-color: #f8f9fa;
  144.     border-radius: 4px;
  145.     margin-bottom: 20px;
  146. }
  147. .error {
  148.     background-color: #f8d7da;
  149.     color: #721c24;
  150. }
复制代码

现在,我们已经完成了商品展示功能。用户可以看到商品列表,按类别筛选商品,并将商品添加到购物车。接下来,我们将实现购物车功能。

4. 购物车功能实现

4.1 创建购物车服务

在js/services/cartService.js中,我们创建一个服务来管理购物车状态:
  1. // 购物车服务
  2. app.service('CartService', ['$q', '$rootScope', function($q, $rootScope) {
  3.     var self = this;
  4.    
  5.     // 购物车数据
  6.     self.cartItems = [];
  7.     self.cartId = 'shopping_cart_' + Date.now(); // 简单的购物车ID
  8.    
  9.     // 初始化购物车
  10.     self.initCart = function() {
  11.         // 尝试从本地存储加载购物车数据
  12.         var savedCart = localStorage.getItem(self.cartId);
  13.         
  14.         if (savedCart) {
  15.             try {
  16.                 self.cartItems = JSON.parse(savedCart);
  17.             } catch (e) {
  18.                 console.error('解析购物车数据失败:', e);
  19.                 self.cartItems = [];
  20.             }
  21.         }
  22.         
  23.         // 保存购物车到本地存储
  24.         self.saveCart();
  25.     };
  26.    
  27.     // 保存购物车到本地存储
  28.     self.saveCart = function() {
  29.         localStorage.setItem(self.cartId, JSON.stringify(self.cartItems));
  30.         
  31.         // 广播购物车更新事件
  32.         $rootScope.$broadcast('cartUpdated', {
  33.             count: self.getItemCount(),
  34.             total: self.getTotal()
  35.         });
  36.     };
  37.    
  38.     // 添加商品到购物车
  39.     self.addToCart = function(product, quantity) {
  40.         var deferred = $q.defer();
  41.         
  42.         // 验证数量
  43.         quantity = parseInt(quantity) || 1;
  44.         if (quantity <= 0) {
  45.             deferred.reject('数量必须大于0');
  46.             return deferred.promise;
  47.         }
  48.         
  49.         // 检查商品库存
  50.         if (product.stock < quantity) {
  51.             deferred.reject('库存不足');
  52.             return deferred.promise;
  53.         }
  54.         
  55.         // 检查购物车中是否已有该商品
  56.         var existingItem = self.cartItems.find(function(item) {
  57.             return item.product.id === product.id;
  58.         });
  59.         
  60.         if (existingItem) {
  61.             // 如果商品已在购物车中,增加数量
  62.             var newQuantity = existingItem.quantity + quantity;
  63.             
  64.             // 再次检查库存
  65.             if (product.stock < newQuantity) {
  66.                 deferred.reject('库存不足');
  67.                 return deferred.promise;
  68.             }
  69.             
  70.             existingItem.quantity = newQuantity;
  71.         } else {
  72.             // 如果商品不在购物车中,添加新项
  73.             self.cartItems.push({
  74.                 product: product,
  75.                 quantity: quantity,
  76.                 addedAt: new Date()
  77.             });
  78.         }
  79.         
  80.         // 保存购物车
  81.         self.saveCart();
  82.         
  83.         deferred.resolve({
  84.             message: '商品已添加到购物车',
  85.             item: existingItem || self.cartItems[self.cartItems.length - 1]
  86.         });
  87.         
  88.         return deferred.promise;
  89.     };
  90.    
  91.     // 从购物车中移除商品
  92.     self.removeFromCart = function(productId) {
  93.         var deferred = $q.defer();
  94.         
  95.         var index = self.cartItems.findIndex(function(item) {
  96.             return item.product.id === productId;
  97.         });
  98.         
  99.         if (index !== -1) {
  100.             var removedItem = self.cartItems.splice(index, 1)[0];
  101.             self.saveCart();
  102.             
  103.             deferred.resolve({
  104.                 message: '商品已从购物车中移除',
  105.                 item: removedItem
  106.             });
  107.         } else {
  108.             deferred.reject('购物车中找不到该商品');
  109.         }
  110.         
  111.         return deferred.promise;
  112.     };
  113.    
  114.     // 更新购物车中商品的数量
  115.     self.updateQuantity = function(productId, newQuantity) {
  116.         var deferred = $q.defer();
  117.         
  118.         // 验证数量
  119.         newQuantity = parseInt(newQuantity) || 1;
  120.         if (newQuantity <= 0) {
  121.             // 如果数量小于等于0,则移除商品
  122.             return self.removeFromCart(productId);
  123.         }
  124.         
  125.         var item = self.cartItems.find(function(item) {
  126.             return item.product.id === productId;
  127.         });
  128.         
  129.         if (item) {
  130.             // 检查库存
  131.             if (item.product.stock < newQuantity) {
  132.                 deferred.reject('库存不足');
  133.                 return deferred.promise;
  134.             }
  135.             
  136.             item.quantity = newQuantity;
  137.             self.saveCart();
  138.             
  139.             deferred.resolve({
  140.                 message: '数量已更新',
  141.                 item: item
  142.             });
  143.         } else {
  144.             deferred.reject('购物车中找不到该商品');
  145.         }
  146.         
  147.         return deferred.promise;
  148.     };
  149.    
  150.     // 清空购物车
  151.     self.clearCart = function() {
  152.         var deferred = $q.defer();
  153.         
  154.         self.cartItems = [];
  155.         self.saveCart();
  156.         
  157.         deferred.resolve({
  158.             message: '购物车已清空'
  159.         });
  160.         
  161.         return deferred.promise;
  162.     };
  163.    
  164.     // 获取购物车中的所有商品
  165.     self.getCartItems = function() {
  166.         return self.cartItems;
  167.     };
  168.    
  169.     // 获取购物车中商品的总数量
  170.     self.getItemCount = function() {
  171.         return self.cartItems.reduce(function(count, item) {
  172.             return count + item.quantity;
  173.         }, 0);
  174.     };
  175.    
  176.     // 计算购物车总价
  177.     self.getTotal = function() {
  178.         return self.cartItems.reduce(function(total, item) {
  179.             return total + (item.product.price * item.quantity);
  180.         }, 0);
  181.     };
  182.    
  183.     // 应用优惠券
  184.     self.applyCoupon = function(couponCode) {
  185.         var deferred = $q.defer();
  186.         
  187.         // 这里应该有一个验证优惠券的逻辑
  188.         // 为了演示,我们假设有一个固定的优惠券
  189.         if (couponCode === 'DISCOUNT10') {
  190.             // 10%折扣
  191.             var discount = self.getTotal() * 0.1;
  192.             deferred.resolve({
  193.                 code: couponCode,
  194.                 type: 'percentage',
  195.                 value: 10,
  196.                 amount: discount
  197.             });
  198.         } else if (couponCode === 'SAVE50') {
  199.             // 固定折扣50元
  200.             deferred.resolve({
  201.                 code: couponCode,
  202.                 type: 'fixed',
  203.                 value: 50,
  204.                 amount: 50
  205.             });
  206.         } else {
  207.             deferred.reject('无效的优惠券代码');
  208.         }
  209.         
  210.         return deferred.promise;
  211.     };
  212.    
  213.     // 初始化购物车
  214.     self.initCart();
  215. }]);
复制代码

4.2 创建购物车控制器

在js/controllers/cartController.js中,我们创建一个控制器来管理购物车视图:
  1. // 购物车控制器
  2. app.controller('CartController', ['CartService', '$scope', '$timeout', function(CartService, $scope, $timeout) {
  3.     var self = this;
  4.    
  5.     // 初始化变量
  6.     self.cartItems = [];
  7.     self.itemCount = 0;
  8.     self.total = 0;
  9.     self.loading = false;
  10.     self.message = null;
  11.     self.messageType = null;
  12.     self.couponCode = '';
  13.     self.discount = null;
  14.     self.finalTotal = 0;
  15.    
  16.     // 加载购物车数据
  17.     self.loadCart = function() {
  18.         self.cartItems = CartService.getCartItems();
  19.         self.itemCount = CartService.getItemCount();
  20.         self.total = CartService.getTotal();
  21.         self.calculateFinalTotal();
  22.     };
  23.    
  24.     // 计算最终总价(应用折扣后)
  25.     self.calculateFinalTotal = function() {
  26.         if (self.discount) {
  27.             self.finalTotal = self.total - self.discount.amount;
  28.             
  29.             // 确保最终价格不低于0
  30.             if (self.finalTotal < 0) {
  31.                 self.finalTotal = 0;
  32.             }
  33.         } else {
  34.             self.finalTotal = self.total;
  35.         }
  36.     };
  37.    
  38.     // 从购物车中移除商品
  39.     self.removeItem = function(productId) {
  40.         self.loading = true;
  41.         
  42.         CartService.removeFromCart(productId)
  43.             .then(function(response) {
  44.                 self.loadCart();
  45.                 self.showMessage(response.message, 'success');
  46.             })
  47.             .catch(function(error) {
  48.                 self.showMessage('移除失败: ' + error, 'error');
  49.             })
  50.             .finally(function() {
  51.                 self.loading = false;
  52.             });
  53.     };
  54.    
  55.     // 更新商品数量
  56.     self.updateQuantity = function(productId, newQuantity) {
  57.         // 防抖动,避免频繁请求
  58.         if (self.updateTimeout) {
  59.             $timeout.cancel(self.updateTimeout);
  60.         }
  61.         
  62.         self.updateTimeout = $timeout(function() {
  63.             self.loading = true;
  64.             
  65.             CartService.updateQuantity(productId, newQuantity)
  66.                 .then(function(response) {
  67.                     self.loadCart();
  68.                     self.showMessage(response.message, 'success');
  69.                 })
  70.                 .catch(function(error) {
  71.                     self.showMessage('更新失败: ' + error, 'error');
  72.                     // 恢复原来的数量
  73.                     self.loadCart();
  74.                 })
  75.                 .finally(function() {
  76.                     self.loading = false;
  77.                 });
  78.         }, 500); // 500ms延迟
  79.     };
  80.    
  81.     // 清空购物车
  82.     self.clearCart = function() {
  83.         if (confirm('确定要清空购物车吗?')) {
  84.             self.loading = true;
  85.             
  86.             CartService.clearCart()
  87.                 .then(function(response) {
  88.                     self.loadCart();
  89.                     self.showMessage(response.message, 'success');
  90.                 })
  91.                 .catch(function(error) {
  92.                     self.showMessage('清空失败: ' + error, 'error');
  93.                 })
  94.                 .finally(function() {
  95.                     self.loading = false;
  96.                 });
  97.         }
  98.     };
  99.    
  100.     // 应用优惠券
  101.     self.applyCoupon = function() {
  102.         if (!self.couponCode) {
  103.             self.showMessage('请输入优惠券代码', 'error');
  104.             return;
  105.         }
  106.         
  107.         self.loading = true;
  108.         
  109.         CartService.applyCoupon(self.couponCode)
  110.             .then(function(discount) {
  111.                 self.discount = discount;
  112.                 self.calculateFinalTotal();
  113.                 self.showMessage('优惠券已应用', 'success');
  114.             })
  115.             .catch(function(error) {
  116.                 self.showMessage(error, 'error');
  117.                 self.discount = null;
  118.                 self.calculateFinalTotal();
  119.             })
  120.             .finally(function() {
  121.                 self.loading = false;
  122.             });
  123.     };
  124.    
  125.     // 移除优惠券
  126.     self.removeCoupon = function() {
  127.         self.discount = null;
  128.         self.couponCode = '';
  129.         self.calculateFinalTotal();
  130.         self.showMessage('优惠券已移除', 'success');
  131.     };
  132.    
  133.     // 结账
  134.     self.checkout = function() {
  135.         if (self.cartItems.length === 0) {
  136.             self.showMessage('购物车是空的,无法结账', 'error');
  137.             return;
  138.         }
  139.         
  140.         // 这里应该有一个结账的逻辑
  141.         // 为了演示,我们只是显示一个消息
  142.         self.showMessage('结账功能尚未实现', 'info');
  143.     };
  144.    
  145.     // 显示消息
  146.     self.showMessage = function(message, type) {
  147.         self.message = message;
  148.         self.messageType = type;
  149.         
  150.         // 3秒后清除消息
  151.         $timeout(function() {
  152.             self.message = null;
  153.             self.messageType = null;
  154.         }, 3000);
  155.     };
  156.    
  157.     // 监听购物车更新事件
  158.     $scope.$on('cartUpdated', function(event, data) {
  159.         self.itemCount = data.count;
  160.         self.total = data.total;
  161.         self.calculateFinalTotal();
  162.     });
  163.    
  164.     // 初始化
  165.     self.loadCart();
  166. }]);
复制代码

4.3 更新购物车视图

现在,我们更新index.html中的购物车区域:
  1. <!-- 购物车区域 -->
  2. <div ng-controller="CartController as cartCtrl">
  3.     <h2>购物车 ({{cartCtrl.itemCount}} 件商品)</h2>
  4.    
  5.     <!-- 加载状态 -->
  6.     <div ng-if="cartCtrl.loading" class="loading">
  7.         <p>正在处理...</p>
  8.     </div>
  9.    
  10.     <!-- 消息提示 -->
  11.     <div ng-if="cartCtrl.message" class="message {{cartCtrl.messageType}}">
  12.         <p>{{cartCtrl.message}}</p>
  13.     </div>
  14.    
  15.     <!-- 购物车为空 -->
  16.     <div ng-if="cartCtrl.cartItems.length === 0 && !cartCtrl.loading" class="empty-cart">
  17.         <p>购物车是空的,去<a href="#/products">添加一些商品</a>吧!</p>
  18.     </div>
  19.    
  20.     <!-- 购物车内容 -->
  21.     <div ng-if="cartCtrl.cartItems.length > 0" class="cart-content">
  22.         <!-- 购物车商品列表 -->
  23.         <div class="cart-items">
  24.             <div class="cart-item" ng-repeat="item in cartCtrl.cartItems">
  25.                 <div class="cart-item-image">
  26.                     <img ng-src="{{item.product.image}}" alt="{{item.product.name}}" onerror="this.src='images/placeholder.jpg'">
  27.                 </div>
  28.                 <div class="cart-item-info">
  29.                     <h3 class="cart-item-name">{{item.product.name}}</h3>
  30.                     <p class="cart-item-description">{{item.product.description}}</p>
  31.                     <div class="cart-item-price">
  32.                         ¥{{item.product.price | number:2}}
  33.                     </div>
  34.                 </div>
  35.                 <div class="cart-item-quantity">
  36.                     <label>数量:</label>
  37.                     <input type="number" min="1" max="{{item.product.stock}}"
  38.                            ng-model="item.quantity"
  39.                            ng-change="cartCtrl.updateQuantity(item.product.id, item.quantity)">
  40.                     <span class="stock-info">库存: {{item.product.stock}}</span>
  41.                 </div>
  42.                 <div class="cart-item-total">
  43.                     ¥{{(item.product.price * item.quantity) | number:2}}
  44.                 </div>
  45.                 <div class="cart-item-actions">
  46.                     <button class="remove-btn" ng-click="cartCtrl.removeItem(item.product.id)">
  47.                         移除
  48.                     </button>
  49.                 </div>
  50.             </div>
  51.         </div>
  52.         
  53.         <!-- 购物车摘要 -->
  54.         <div class="cart-summary">
  55.             <div class="summary-row">
  56.                 <span>商品总数:</span>
  57.                 <span>{{cartCtrl.itemCount}} 件</span>
  58.             </div>
  59.             <div class="summary-row">
  60.                 <span>商品总价:</span>
  61.                 <span>¥{{cartCtrl.total | number:2}}</span>
  62.             </div>
  63.             
  64.             <!-- 优惠券部分 -->
  65.             <div class="coupon-section">
  66.                 <div class="summary-row" ng-if="!cartCtrl.discount">
  67.                     <input type="text" ng-model="cartCtrl.couponCode" placeholder="输入优惠券代码">
  68.                     <button ng-click="cartCtrl.applyCoupon()">应用</button>
  69.                 </div>
  70.                 <div class="summary-row" ng-if="cartCtrl.discount">
  71.                     <span>优惠券 ({{cartCtrl.discount.code}}):</span>
  72.                     <span>-¥{{cartCtrl.discount.amount | number:2}}</span>
  73.                     <button class="remove-coupon-btn" ng-click="cartCtrl.removeCoupon()">移除</button>
  74.                 </div>
  75.             </div>
  76.             
  77.             <div class="summary-row total">
  78.                 <span>总计:</span>
  79.                 <span>¥{{cartCtrl.finalTotal | number:2}}</span>
  80.             </div>
  81.             
  82.             <div class="cart-actions">
  83.                 <button class="clear-cart-btn" ng-click="cartCtrl.clearCart()">
  84.                     清空购物车
  85.                 </button>
  86.                 <button class="checkout-btn" ng-click="cartCtrl.checkout()">
  87.                     结账
  88.                 </button>
  89.             </div>
  90.         </div>
  91.     </div>
  92. </div>
复制代码

4.4 添加购物车样式

在css/style.css中添加购物车相关样式:
  1. /* 购物车样式 */
  2. .cart-content {
  3.     display: flex;
  4.     flex-direction: column;
  5.     gap: 20px;
  6. }
  7. .cart-items {
  8.     background: white;
  9.     border-radius: 8px;
  10.     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  11.     overflow: hidden;
  12. }
  13. .cart-item {
  14.     display: flex;
  15.     align-items: center;
  16.     padding: 15px;
  17.     border-bottom: 1px solid #eee;
  18. }
  19. .cart-item:last-child {
  20.     border-bottom: none;
  21. }
  22. .cart-item-image {
  23.     width: 80px;
  24.     height: 80px;
  25.     margin-right: 15px;
  26. }
  27. .cart-item-image img {
  28.     width: 100%;
  29.     height: 100%;
  30.     object-fit: cover;
  31.     border-radius: 4px;
  32. }
  33. .cart-item-info {
  34.     flex: 1;
  35.     margin-right: 15px;
  36. }
  37. .cart-item-name {
  38.     font-size: 16px;
  39.     margin-bottom: 5px;
  40. }
  41. .cart-item-description {
  42.     font-size: 14px;
  43.     color: #666;
  44.     margin-bottom: 5px;
  45. }
  46. .cart-item-price {
  47.     font-size: 16px;
  48.     font-weight: bold;
  49.     color: #e74c3c;
  50. }
  51. .cart-item-quantity {
  52.     width: 120px;
  53.     margin-right: 15px;
  54. }
  55. .cart-item-quantity input {
  56.     width: 60px;
  57.     padding: 5px;
  58.     border: 1px solid #ddd;
  59.     border-radius: 4px;
  60.     text-align: center;
  61. }
  62. .stock-info {
  63.     display: block;
  64.     font-size: 12px;
  65.     color: #888;
  66.     margin-top: 5px;
  67. }
  68. .cart-item-total {
  69.     width: 100px;
  70.     margin-right: 15px;
  71.     font-weight: bold;
  72.     text-align: right;
  73. }
  74. .cart-item-actions {
  75.     width: 80px;
  76. }
  77. .remove-btn {
  78.     padding: 5px 10px;
  79.     background-color: #e74c3c;
  80.     color: white;
  81.     border: none;
  82.     border-radius: 4px;
  83.     cursor: pointer;
  84.     font-size: 14px;
  85.     transition: background-color 0.3s;
  86. }
  87. .remove-btn:hover {
  88.     background-color: #c0392b;
  89. }
  90. .cart-summary {
  91.     background: white;
  92.     border-radius: 8px;
  93.     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  94.     padding: 20px;
  95.     align-self: flex-end;
  96.     width: 300px;
  97. }
  98. .summary-row {
  99.     display: flex;
  100.     justify-content: space-between;
  101.     margin-bottom: 10px;
  102. }
  103. .summary-row.total {
  104.     font-size: 18px;
  105.     font-weight: bold;
  106.     padding-top: 10px;
  107.     border-top: 1px solid #eee;
  108.     margin-top: 10px;
  109. }
  110. .coupon-section {
  111.     margin: 15px 0;
  112. }
  113. .coupon-section input {
  114.     width: 150px;
  115.     padding: 5px;
  116.     border: 1px solid #ddd;
  117.     border-radius: 4px;
  118.     margin-right: 5px;
  119. }
  120. .coupon-section button {
  121.     padding: 5px 10px;
  122.     background-color: #f39c12;
  123.     color: white;
  124.     border: none;
  125.     border-radius: 4px;
  126.     cursor: pointer;
  127. }
  128. .coupon-section button:hover {
  129.     background-color: #e67e22;
  130. }
  131. .remove-coupon-btn {
  132.     padding: 2px 5px;
  133.     background-color: #95a5a6;
  134.     color: white;
  135.     border: none;
  136.     border-radius: 4px;
  137.     cursor: pointer;
  138.     font-size: 12px;
  139.     margin-left: 5px;
  140. }
  141. .cart-actions {
  142.     display: flex;
  143.     justify-content: space-between;
  144.     margin-top: 20px;
  145. }
  146. .clear-cart-btn {
  147.     padding: 10px 15px;
  148.     background-color: #95a5a6;
  149.     color: white;
  150.     border: none;
  151.     border-radius: 4px;
  152.     cursor: pointer;
  153.     transition: background-color 0.3s;
  154. }
  155. .clear-cart-btn:hover {
  156.     background-color: #7f8c8d;
  157. }
  158. .checkout-btn {
  159.     padding: 10px 15px;
  160.     background-color: #27ae60;
  161.     color: white;
  162.     border: none;
  163.     border-radius: 4px;
  164.     cursor: pointer;
  165.     transition: background-color 0.3s;
  166. }
  167. .checkout-btn:hover {
  168.     background-color: #229954;
  169. }
  170. .empty-cart {
  171.     text-align: center;
  172.     padding: 40px;
  173.     background: white;
  174.     border-radius: 8px;
  175.     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  176. }
  177. .empty-cart a {
  178.     color: #3498db;
  179.     text-decoration: none;
  180. }
  181. .empty-cart a:hover {
  182.     text-decoration: underline;
  183. }
复制代码

现在,我们已经完成了购物车的基本功能。用户可以将商品添加到购物车,更新商品数量,移除商品,应用优惠券,以及查看总价。接下来,我们将实现一些高级功能。

5. 高级功能实现

5.1 本地存储与持久化

我们已经在购物车服务中实现了本地存储功能,使用localStorage来保存购物车数据。这样即使用户刷新页面或关闭浏览器,购物车数据也不会丢失。

5.2 优惠券系统

我们已经实现了一个简单的优惠券系统,支持两种类型的优惠券:

• 百分比折扣(例如:DISCOUNT10 提供10%的折扣)
• 固定金额折扣(例如:SAVE50 提供50元的折扣)

5.3 购物车动画效果

让我们为购物车添加一些动画效果,使用户体验更加流畅。首先,我们需要在HTML中引入AngularJS的动画模块:
  1. <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular-animate.min.js"></script>
复制代码

然后,在js/app.js中添加动画模块依赖:
  1. var app = angular.module('shoppingCartApp', ['ngAnimate']);
复制代码

现在,我们可以为购物车添加一些动画效果。在css/style.css中添加:
  1. /* 动画效果 */
  2. .cart-item.ng-enter, .cart-item.ng-leave {
  3.     transition: all 0.5s ease;
  4. }
  5. .cart-item.ng-enter, .cart-item.ng-leave.ng-leave-active {
  6.     opacity: 0;
  7.     height: 0;
  8.     margin: 0;
  9.     padding: 0;
  10. }
  11. .cart-item.ng-leave, .cart-item.ng-enter.ng-enter-active {
  12.     opacity: 1;
  13.     height: auto;
  14.     margin: inherit;
  15.     padding: inherit;
  16. }
  17. .product-card.ng-enter {
  18.     animation: fadeIn 0.5s;
  19. }
  20. @keyframes fadeIn {
  21.     from {
  22.         opacity: 0;
  23.         transform: translateY(20px);
  24.     }
  25.     to {
  26.         opacity: 1;
  27.         transform: translateY(0);
  28.     }
  29. }
  30. .message.ng-enter {
  31.     animation: slideIn 0.3s;
  32. }
  33. @keyframes slideIn {
  34.     from {
  35.         transform: translateY(-20px);
  36.         opacity: 0;
  37.     }
  38.     to {
  39.         transform: translateY(0);
  40.         opacity: 1;
  41.     }
  42. }
复制代码

5.4 购物车实时更新

我们已经实现了购物车的实时更新,通过使用AngularJS的事件系统($broadcast和$on)来通知控制器购物车状态的变化。这样,当用户在商品页面添加商品到购物车时,购物车区域会自动更新。

5.5 购物车验证

我们已经在购物车服务中添加了一些验证逻辑,例如:

• 检查商品库存
• 验证商品数量
• 验证优惠券代码

5.6 购物车响应式设计

我们的购物车已经具有响应式设计,可以适应不同的屏幕尺寸。在css/style.css中,我们可以添加一些媒体查询来进一步优化移动设备上的显示效果:
  1. /* 响应式设计 */
  2. @media (max-width: 768px) {
  3.     .content {
  4.         flex-direction: column;
  5.     }
  6.    
  7.     .product-list {
  8.         grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  9.     }
  10.    
  11.     .cart-item {
  12.         flex-wrap: wrap;
  13.     }
  14.    
  15.     .cart-item-image {
  16.         width: 100%;
  17.         height: 200px;
  18.         margin-right: 0;
  19.         margin-bottom: 10px;
  20.     }
  21.    
  22.     .cart-item-info {
  23.         width: 100%;
  24.         margin-right: 0;
  25.         margin-bottom: 10px;
  26.     }
  27.    
  28.     .cart-item-quantity {
  29.         width: 100%;
  30.         margin-right: 0;
  31.         margin-bottom: 10px;
  32.     }
  33.    
  34.     .cart-item-total {
  35.         width: 100%;
  36.         margin-right: 0;
  37.         margin-bottom: 10px;
  38.         text-align: left;
  39.     }
  40.    
  41.     .cart-item-actions {
  42.         width: 100%;
  43.     }
  44.    
  45.     .cart-summary {
  46.         width: 100%;
  47.     }
  48. }
  49. @media (max-width: 480px) {
  50.     .product-list {
  51.         grid-template-columns: 1fr;
  52.     }
  53.    
  54.     .cart-summary {
  55.         padding: 15px;
  56.     }
  57.    
  58.     .coupon-section input {
  59.         width: 100%;
  60.         margin-bottom: 5px;
  61.     }
  62.    
  63.     .cart-actions {
  64.         flex-direction: column;
  65.         gap: 10px;
  66.     }
  67.    
  68.     .clear-cart-btn, .checkout-btn {
  69.         width: 100%;
  70.     }
  71. }
复制代码

6. 性能优化

6.1 防抖动处理

在购物车控制器中,我们已经实现了更新商品数量的防抖动处理,避免用户快速修改数量时发送过多请求:
  1. // 防抖动,避免频繁请求
  2. if (self.updateTimeout) {
  3.     $timeout.cancel(self.updateTimeout);
  4. }
  5. self.updateTimeout = $timeout(function() {
  6.     // 更新逻辑
  7. }, 500); // 500ms延迟
复制代码

6.2 懒加载

我们可以实现商品的懒加载,提高页面加载性能。修改ProductService中的getProducts方法:
  1. // 获取所有商品(支持分页)
  2. self.getProducts = function(page, limit) {
  3.     var deferred = $q.defer();
  4.    
  5.     page = page || 1;
  6.     limit = limit || 10;
  7.    
  8.     $http.get('data/products.json')
  9.         .then(function(response) {
  10.             var products = response.data;
  11.             var startIndex = (page - 1) * limit;
  12.             var endIndex = startIndex + limit;
  13.             var paginatedProducts = products.slice(startIndex, endIndex);
  14.             
  15.             deferred.resolve({
  16.                 products: paginatedProducts,
  17.                 total: products.length,
  18.                 page: page,
  19.                 totalPages: Math.ceil(products.length / limit)
  20.             });
  21.         })
  22.         .catch(function(error) {
  23.             deferred.reject(error);
  24.             console.error('获取商品数据失败:', error);
  25.         });
  26.    
  27.     return deferred.promise;
  28. };
复制代码

然后,在ProductController中添加分页逻辑:
  1. // 分页变量
  2. self.pagination = {
  3.     page: 1,
  4.     limit: 6,
  5.     totalPages: 0,
  6.     totalItems: 0
  7. };
  8. // 加载商品数据(支持分页)
  9. self.loadProducts = function() {
  10.     self.loading = true;
  11.     self.error = null;
  12.    
  13.     ProductService.getProducts(self.pagination.page, self.pagination.limit)
  14.         .then(function(response) {
  15.             self.products = response.products;
  16.             self.pagination.totalItems = response.total;
  17.             self.pagination.totalPages = response.totalPages;
  18.             self.pagination.page = response.page;
  19.             self.extractCategories();
  20.             self.loading = false;
  21.         })
  22.         .catch(function(error) {
  23.             self.error = '加载商品失败: ' + error;
  24.             self.loading = false;
  25.         });
  26. };
  27. // 上一页
  28. self.prevPage = function() {
  29.     if (self.pagination.page > 1) {
  30.         self.pagination.page--;
  31.         self.loadProducts();
  32.         // 滚动到顶部
  33.         window.scrollTo(0, 0);
  34.     }
  35. };
  36. // 下一页
  37. self.nextPage = function() {
  38.     if (self.pagination.page < self.pagination.totalPages) {
  39.         self.pagination.page++;
  40.         self.loadProducts();
  41.         // 滚动到顶部
  42.         window.scrollTo(0, 0);
  43.     }
  44. };
  45. // 跳转到指定页
  46. self.goToPage = function(page) {
  47.     if (page >= 1 && page <= self.pagination.totalPages && page !== self.pagination.page) {
  48.         self.pagination.page = page;
  49.         self.loadProducts();
  50.         // 滚动到顶部
  51.         window.scrollTo(0, 0);
  52.     }
  53. };
复制代码

在商品列表视图中添加分页控件:
  1. <!-- 分页控件 -->
  2. <div ng-if="productCtrl.pagination.totalPages > 1" class="pagination">
  3.     <button ng-click="productCtrl.prevPage()"
  4.             ng-disabled="productCtrl.pagination.page === 1">
  5.         上一页
  6.     </button>
  7.    
  8.     <span ng-repeat="page in [].constructor(productCtrl.pagination.totalPages) track by $index">
  9.         <button ng-class="{active: productCtrl.pagination.page === $index + 1}"
  10.                 ng-click="productCtrl.goToPage($index + 1)">
  11.             {{$index + 1}}
  12.         </button>
  13.     </span>
  14.    
  15.     <button ng-click="productCtrl.nextPage()"
  16.             ng-disabled="productCtrl.pagination.page === productCtrl.pagination.totalPages">
  17.         下一页
  18.     </button>
  19. </div>
复制代码

添加分页控件的样式:
  1. /* 分页控件样式 */
  2. .pagination {
  3.     display: flex;
  4.     justify-content: center;
  5.     margin-top: 20px;
  6.     gap: 5px;
  7. }
  8. .pagination button {
  9.     padding: 8px 12px;
  10.     background-color: #ecf0f1;
  11.     border: 1px solid #bdc3c7;
  12.     border-radius: 4px;
  13.     cursor: pointer;
  14.     transition: all 0.3s;
  15. }
  16. .pagination button.active {
  17.     background-color: #3498db;
  18.     color: white;
  19.     border-color: #3498db;
  20. }
  21. .pagination button:disabled {
  22.     background-color: #f8f9fa;
  23.     color: #6c757d;
  24.     cursor: not-allowed;
  25. }
复制代码

6.3 图片优化

我们可以使用懒加载来优化图片加载,提高页面性能。首先,创建一个图片懒加载的指令:
  1. // 图片懒加载指令
  2. app.directive('lazyLoad', function() {
  3.     return {
  4.         restrict: 'A',
  5.         link: function(scope, element, attrs) {
  6.             var observer = new IntersectionObserver(function(entries) {
  7.                 entries.forEach(function(entry) {
  8.                     if (entry.isIntersecting) {
  9.                         element.attr('src', attrs.lazyLoad);
  10.                         observer.unobserve(element[0]);
  11.                     }
  12.                 });
  13.             }, {
  14.                 rootMargin: '50px'
  15.             });
  16.             
  17.             observer.observe(element[0]);
  18.             
  19.             // 当元素被销毁时,停止观察
  20.             scope.$on('$destroy', function() {
  21.                 observer.unobserve(element[0]);
  22.             });
  23.         }
  24.     };
  25. });
复制代码

然后,在商品卡片模板中使用这个指令:
  1. <div class="product-image">
  2.     <img lazy-load="{{product.image}}" alt="{{product.name}}" src="images/placeholder.jpg">
  3. </div>
复制代码

7. 完整项目整合

现在,让我们整合所有功能,创建一个完整的购物车应用。以下是最终的index.html文件:
  1. <!DOCTYPE html>
  2. <html lang="zh-CN" ng-app="shoppingCartApp">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>AngularJS 购物车</title>
  7.     <link rel="stylesheet" href="css/style.css">
  8.     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
  9.     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular-animate.min.js"></script>
  10.     <script src="js/app.js"></script>
  11.     <script src="js/services/productService.js"></script>
  12.     <script src="js/services/cartService.js"></script>
  13.     <script src="js/controllers/productController.js"></script>
  14.     <script src="js/controllers/cartController.js"></script>
  15.     <script src="js/directives/productDirective.js"></script>
  16.     <script src="js/directives/lazyLoadDirective.js"></script>
  17. </head>
  18. <body>
  19.     <div class="container">
  20.         <header>
  21.             <h1>AngularJS 购物车示例</h1>
  22.             <div class="header-info">
  23.                 <div class="cart-summary-header">
  24.                     购物车: <span ng-controller="CartController as cartCtrl">{{cartCtrl.itemCount}} 件商品</span>
  25.                 </div>
  26.             </div>
  27.         </header>
  28.         
  29.         <div class="content">
  30.             <!-- 商品列表区域 -->
  31.             <div ng-controller="ProductController as productCtrl">
  32.                 <h2>商品列表</h2>
  33.                
  34.                 <!-- 加载状态 -->
  35.                 <div ng-if="productCtrl.loading" class="loading">
  36.                     <p>正在加载商品...</p>
  37.                 </div>
  38.                
  39.                 <!-- 错误状态 -->
  40.                 <div ng-if="productCtrl.error" class="error">
  41.                     <p>{{productCtrl.error}}</p>
  42.                 </div>
  43.                
  44.                 <!-- 消息提示 -->
  45.                 <div ng-if="productCtrl.message" class="message {{productCtrl.messageType}}">
  46.                     <p>{{productCtrl.message}}</p>
  47.                 </div>
  48.                
  49.                 <!-- 类别筛选 -->
  50.                 <div class="category-filter">
  51.                     <button ng-class="{active: !productCtrl.selectedCategory}"
  52.                             ng-click="productCtrl.filterByCategory(null)">
  53.                         全部商品
  54.                     </button>
  55.                     <button ng-repeat="category in productCtrl.categories"
  56.                             ng-class="{active: productCtrl.selectedCategory === category}"
  57.                             ng-click="productCtrl.filterByCategory(category)">
  58.                         {{category}}
  59.                     </button>
  60.                 </div>
  61.                
  62.                 <!-- 商品列表 -->
  63.                 <div class="product-list">
  64.                     <product-card ng-repeat="product in productCtrl.getFilteredProducts()"
  65.                                   product="product"
  66.                                   on-add-to-cart="productCtrl.addToCart(product)">
  67.                     </product-card>
  68.                 </div>
  69.                
  70.                 <!-- 分页控件 -->
  71.                 <div ng-if="productCtrl.pagination.totalPages > 1" class="pagination">
  72.                     <button ng-click="productCtrl.prevPage()"
  73.                             ng-disabled="productCtrl.pagination.page === 1">
  74.                         上一页
  75.                     </button>
  76.                     
  77.                     <span ng-repeat="page in [].constructor(productCtrl.pagination.totalPages) track by $index">
  78.                         <button ng-class="{active: productCtrl.pagination.page === $index + 1}"
  79.                                 ng-click="productCtrl.goToPage($index + 1)">
  80.                             {{$index + 1}}
  81.                         </button>
  82.                     </span>
  83.                     
  84.                     <button ng-click="productCtrl.nextPage()"
  85.                             ng-disabled="productCtrl.pagination.page === productCtrl.pagination.totalPages">
  86.                         下一页
  87.                     </button>
  88.                 </div>
  89.             </div>
  90.             
  91.             <!-- 购物车区域 -->
  92.             <div ng-controller="CartController as cartCtrl">
  93.                 <h2>购物车 ({{cartCtrl.itemCount}} 件商品)</h2>
  94.                
  95.                 <!-- 加载状态 -->
  96.                 <div ng-if="cartCtrl.loading" class="loading">
  97.                     <p>正在处理...</p>
  98.                 </div>
  99.                
  100.                 <!-- 消息提示 -->
  101.                 <div ng-if="cartCtrl.message" class="message {{cartCtrl.messageType}}">
  102.                     <p>{{cartCtrl.message}}</p>
  103.                 </div>
  104.                
  105.                 <!-- 购物车为空 -->
  106.                 <div ng-if="cartCtrl.cartItems.length === 0 && !cartCtrl.loading" class="empty-cart">
  107.                     <p>购物车是空的,去添加一些商品吧!</p>
  108.                 </div>
  109.                
  110.                 <!-- 购物车内容 -->
  111.                 <div ng-if="cartCtrl.cartItems.length > 0" class="cart-content">
  112.                     <!-- 购物车商品列表 -->
  113.                     <div class="cart-items">
  114.                         <div class="cart-item" ng-repeat="item in cartCtrl.cartItems">
  115.                             <div class="cart-item-image">
  116.                                 <img lazy-load="{{item.product.image}}" alt="{{item.product.name}}" src="images/placeholder.jpg">
  117.                             </div>
  118.                             <div class="cart-item-info">
  119.                                 <h3 class="cart-item-name">{{item.product.name}}</h3>
  120.                                 <p class="cart-item-description">{{item.product.description}}</p>
  121.                                 <div class="cart-item-price">
  122.                                     ¥{{item.product.price | number:2}}
  123.                                 </div>
  124.                             </div>
  125.                             <div class="cart-item-quantity">
  126.                                 <label>数量:</label>
  127.                                 <input type="number" min="1" max="{{item.product.stock}}"
  128.                                        ng-model="item.quantity"
  129.                                        ng-change="cartCtrl.updateQuantity(item.product.id, item.quantity)">
  130.                                 <span class="stock-info">库存: {{item.product.stock}}</span>
  131.                             </div>
  132.                             <div class="cart-item-total">
  133.                                 ¥{{(item.product.price * item.quantity) | number:2}}
  134.                             </div>
  135.                             <div class="cart-item-actions">
  136.                                 <button class="remove-btn" ng-click="cartCtrl.removeItem(item.product.id)">
  137.                                     移除
  138.                                 </button>
  139.                             </div>
  140.                         </div>
  141.                     </div>
  142.                     
  143.                     <!-- 购物车摘要 -->
  144.                     <div class="cart-summary">
  145.                         <div class="summary-row">
  146.                             <span>商品总数:</span>
  147.                             <span>{{cartCtrl.itemCount}} 件</span>
  148.                         </div>
  149.                         <div class="summary-row">
  150.                             <span>商品总价:</span>
  151.                             <span>¥{{cartCtrl.total | number:2}}</span>
  152.                         </div>
  153.                         
  154.                         <!-- 优惠券部分 -->
  155.                         <div class="coupon-section">
  156.                             <div class="summary-row" ng-if="!cartCtrl.discount">
  157.                                 <input type="text" ng-model="cartCtrl.couponCode" placeholder="输入优惠券代码">
  158.                                 <button ng-click="cartCtrl.applyCoupon()">应用</button>
  159.                             </div>
  160.                             <div class="summary-row" ng-if="cartCtrl.discount">
  161.                                 <span>优惠券 ({{cartCtrl.discount.code}}):</span>
  162.                                 <span>-¥{{cartCtrl.discount.amount | number:2}}</span>
  163.                                 <button class="remove-coupon-btn" ng-click="cartCtrl.removeCoupon()">移除</button>
  164.                             </div>
  165.                         </div>
  166.                         
  167.                         <div class="summary-row total">
  168.                             <span>总计:</span>
  169.                             <span>¥{{cartCtrl.finalTotal | number:2}}</span>
  170.                         </div>
  171.                         
  172.                         <div class="cart-actions">
  173.                             <button class="clear-cart-btn" ng-click="cartCtrl.clearCart()">
  174.                                 清空购物车
  175.                             </button>
  176.                             <button class="checkout-btn" ng-click="cartCtrl.checkout()">
  177.                                 结账
  178.                             </button>
  179.                         </div>
  180.                     </div>
  181.                 </div>
  182.             </div>
  183.         </div>
  184.     </div>
  185. </body>
  186. </html>
复制代码

8. 总结与展望

8.1 项目总结

在本教程中,我们使用AngularJS构建了一个完整的购物车应用,实现了以下功能:

1. 商品展示:展示商品列表,支持按类别筛选和分页。
2. 购物车管理:添加商品到购物车,更新商品数量,移除商品,清空购物车。
3. 优惠券系统:支持百分比折扣和固定金额折扣。
4. 数据持久化:使用localStorage保存购物车数据。
5. 动画效果:添加了商品添加/移除的动画效果。
6. 性能优化:实现了防抖动处理、懒加载和分页功能。
7. 响应式设计:适应不同屏幕尺寸的设备。

8.2 技术要点

通过这个项目,我们深入理解了AngularJS的核心概念和最佳实践:

1. 数据绑定:使用AngularJS的双向数据绑定,实现了模型和视图的自动同步。
2. 依赖注入:使用AngularJS的依赖注入系统,管理应用中的服务和控制器。
3. 自定义指令:创建了可复用的自定义指令,如商品卡片和图片懒加载。
4. 服务:使用服务封装业务逻辑,实现代码的模块化和可维护性。
5. 事件系统:使用AngularJS的事件系统,实现组件间的通信。
6. 动画:使用AngularJS的动画模块,提升用户体验。

8.3 未来展望

虽然这个购物车应用已经功能完善,但还有一些可以进一步优化的方向:

1. 后端集成:目前我们使用的是静态JSON数据,可以集成真实后端API,实现完整的电商功能。
2. 用户认证:添加用户登录和注册功能,实现用户专属购物车。
3. 支付集成:集成支付网关,实现完整的结账流程。
4. 订单管理:添加订单历史和状态跟踪功能。
5. 搜索功能:实现商品搜索和高级筛选。
6. 评价系统:添加商品评价和评分功能。
7. 推荐系统:基于用户行为实现商品推荐。
8. 国际化:支持多语言和货币。

8.4 AngularJS与现代框架

虽然AngularJS是一个强大的框架,但需要注意的是,AngularJS(1.x版本)已经进入了长期支持阶段,不再有新功能开发。对于新项目,可以考虑使用现代框架如Angular(2+)、React或Vue.js。

不过,AngularJS的许多核心概念,如数据绑定、依赖注入、服务和指令等,在现代框架中仍然有对应的概念。因此,通过学习AngularJS,可以为学习现代前端框架打下坚实的基础。

9. 结语

本教程通过一个完整的购物车项目,详细介绍了AngularJS的数据绑定与状态管理,以及如何构建专业的电商应用。我们从基础到高级,逐步实现了购物车的各项功能,并进行了性能优化和用户体验提升。

希望这个教程能够帮助你深入理解AngularJS的核心概念和最佳实践,为你的前端开发之路提供有力的支持。如果你有任何问题或建议,欢迎随时交流。祝你学习愉快!
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则