在完成AngularJS項(xiàng)目初始化與基礎(chǔ)功能搭建后,實(shí)戰(zhàn)應(yīng)用的核心是“解決業(yè)務(wù)場景中的具體問題”——比如電商后臺的商品數(shù)據(jù)表格需支持篩選、排序、批量操作,商品編輯表單需處理動態(tài)規(guī)格字段,不同角色用戶需看到不同功能菜單。本文基于已搭建的電商后臺框架,深入三大核心實(shí)戰(zhàn)場景,提供“需求分析-技術(shù)實(shí)現(xiàn)-優(yōu)化避坑”的全流程方案,幫你掌握AngularJS在實(shí)際業(yè)務(wù)中的應(yīng)用技巧。
一、實(shí)戰(zhàn)場景1:商品數(shù)據(jù)表格——支持篩選、排序與批量操作
商品管理是電商后臺的核心模塊,數(shù)據(jù)表格作為“數(shù)據(jù)展示與操作入口”,需滿足“高效查詢、便捷操作、性能穩(wěn)定”三大需求。本場景將基于AngularJS Material的md-table組件,實(shí)現(xiàn)支持多條件篩選、字段排序、批量刪除/上架的商品表格。
1. 需求拆解
數(shù)據(jù)展示:展示商品ID、名稱、分類、價(jià)格、庫存、狀態(tài)(上架/下架)、操作按鈕;
篩選功能:支持按商品名稱模糊搜索、按分類下拉篩選、按狀態(tài)(上架/下架)篩選;
排序功能:支持按價(jià)格(升序/降序)、庫存(升序/降序)點(diǎn)擊表頭排序;
批量操作:支持勾選商品后批量刪除、批量上架/下架;
性能優(yōu)化:表格數(shù)據(jù)分頁加載(默認(rèn)10條/頁),避免大數(shù)據(jù)量渲染卡頓。
2. 技術(shù)實(shí)現(xiàn)方案
(1)核心服務(wù)封裝:商品數(shù)據(jù)服務(wù)(ProductService)
負(fù)責(zé)商品數(shù)據(jù)的獲取、篩選、排序、批量操作,隔離數(shù)據(jù)邏輯與視圖邏輯,便于后續(xù)維護(hù)。
// src/business/product/services/ProductService.js
import angular from 'angular';
export default angular.module('productModule.productService', [])
? .factory('ProductService', ['$http', function($http) {
? ? // 私有工具方法:處理數(shù)據(jù)篩選(多條件組合)
? ? const filterProducts = (products, filters) => {
? ? ? return products.filter(product => {
? ? ? ? // 商品名稱模糊匹配(忽略大小寫)
? ? ? ? const nameMatch = !filters.name ||
? ? ? ? ? product.name.toLowerCase().includes(filters.name.toLowerCase());
? ? ? ? // 分類精確匹配(未選擇分類則不篩選)
? ? ? ? const categoryMatch = !filters.categoryId ||
? ? ? ? ? product.categoryId === filters.categoryId;
? ? ? ? // 狀態(tài)精確匹配(未選擇狀態(tài)則不篩選)
? ? ? ? const statusMatch = filters.status === undefined ||
? ? ? ? ? product.status === filters.status;
? ? ? ? return nameMatch && categoryMatch && statusMatch;
? ? ? });
? ? };
? ? // 私有工具方法:處理數(shù)據(jù)排序
? ? const sortProducts = (products, sortConfig) => {
? ? ? if (!sortConfig.field) return products; // 未指定排序字段則不排序
? ? ? return [...products].sort((a, b) => {
? ? ? ? // 按指定字段排序(數(shù)字類型直接比較,字符串類型按ASCII排序)
? ? ? ? if (a[sortConfig.field] < b[sortConfig.field]) {
? ? ? ? ? return sortConfig.direction === 'asc' ? -1 : 1;
? ? ? ? }
? ? ? ? if (a[sortConfig.field] > b[sortConfig.field]) {
? ? ? ? ? return sortConfig.direction === 'asc' ? 1 : -1;
? ? ? ? }
? ? ? ? return 0;
? ? ? });
? ? };
? ? return {
? ? ? // 獲取商品列表(支持分頁、篩選、排序參數(shù))
? ? ? getProductList: (params = {}) => {
? ? ? ? const { page = 1, pageSize = 10, filters = {}, sortConfig = {} } = params;
? ? ? ? // 實(shí)際項(xiàng)目中調(diào)用后端API,此處模擬數(shù)據(jù)(實(shí)際需替換為$http請求)
? ? ? ? return $http.get('/api/products', {
? ? ? ? ? params: { page, pageSize, ...filters, sortField: sortConfig.field, sortDir: sortConfig.direction }
? ? ? ? }).then(response => {
? ? ? ? ? const { list: products, total } = response.data;
? ? ? ? ? // 前端二次篩選與排序(若后端未支持則需處理,建議優(yōu)先由后端實(shí)現(xiàn))
? ? ? ? ? const filtered = filterProducts(products, filters);
? ? ? ? ? const sorted = sortProducts(filtered, sortConfig);
? ? ? ? ? return {
? ? ? ? ? ? list: sorted,
? ? ? ? ? ? total: total, // 總條數(shù)(用于分頁計(jì)算)
? ? ? ? ? ? page: page,
? ? ? ? ? ? pageSize: pageSize
? ? ? ? ? };
? ? ? ? });
? ? ? },
? ? ? // 批量操作商品(批量刪除/上架/下架)
? ? ? batchOperateProducts: (productIds, operateType) => {
? ? ? ? const operateMap = {
? ? ? ? ? delete: '/api/products/batch/delete',
? ? ? ? 上架: '/api/products/batch/up',
? ? ? ? 下架: '/api/products/batch/down'
? ? ? ? };
? ? ? ? const url = operateMap[operateType];
? ? ? ? if (!url) return Promise.reject('不支持的操作類型');
zhiq.zhaopin.com/question/11491006
zhiq.zhaopin.com/question/11491008
zhiq.zhaopin.com/question/11491009
zhiq.zhaopin.com/question/11491010
zhiq.zhaopin.com/question/11491012
zhiq.zhaopin.com/question/11491014
zhiq.zhaopin.com/question/11494132
zhiq.zhaopin.com/question/11494191
zhiq.zhaopin.com/question/11494207
zhiq.zhaopin.com/question/11494212
zhiq.zhaopin.com/question/11494220
zhiq.zhaopin.com/question/11494226
zhiq.zhaopin.com/question/11494237
zhiq.zhaopin.com/question/11494249
zhiq.zhaopin.com/question/11494252
zhiq.zhaopin.com/question/11494261
zhiq.zhaopin.com/question/11494269
zhiq.zhaopin.com/question/11494295
zhiq.zhaopin.com/question/11494299
zhiq.zhaopin.com/question/11494303
? ? ? ? return $http.post(url, { productIds });
? ? ? },
? ? ? // 單個(gè)商品操作(編輯/刪除/上架/下架)
? ? ? operateProduct: (productId, operateType, data = {}) => {
? ? ? ? const operateMap = {
? ? ? ? ? delete: { method: 'DELETE', url: `/api/products/${productId}` },
? ? ? ? ? up: { method: 'PUT', url: `/api/products/${productId}/up` },
? ? ? ? ? down: { method: 'PUT', url: `/api/products/${productId}/down` },
? ? ? ? ? edit: { method: 'PUT', url: `/api/products/${productId}`, data: data }
? ? ? ? };
? ? ? ? const { method, url, data: reqData } = operateMap[operateType];
? ? ? ? if (!method || !url) return Promise.reject('不支持的操作類型');
? ? ? ? return $http({ method, url, data: reqData });
? ? ? }
? ? };
? }])
? .name;
(2)商品列表控制器(ProductListCtrl)
處理表格的視圖交互邏輯,如篩選條件變更、排序切換、分頁切換、批量操作觸發(fā)。
// src/business/product/controllers/ProductListCtrl.js
import angular from 'angular';
import productListTemplate from '../views/productList.html';
export default angular.module('productModule.productListCtrl', [])
? .controller('ProductListCtrl', ['$scope', '$state', 'ProductService', 'NotificationService', function($scope, $state, ProductService, NotificationService) {
? ? // 1. 初始化狀態(tài):篩選條件、排序配置、分頁配置、選中商品ID
? ? $scope.filters = {
? ? ? name: '', // 商品名稱篩選
? ? ? categoryId: '', // 分類篩選(默認(rèn)空,即不篩選)
? ? ? status: undefined // 狀態(tài)篩選(undefined:不篩選,1:上架,0:下架)
? ? };
? ? $scope.sortConfig = {
? ? ? field: 'price', // 默認(rèn)排序字段(價(jià)格)
? ? ? direction: 'asc' // 默認(rèn)排序方向(升序)
? ? };
? ? $scope.pagination = {
? ? ? page: 1, // 當(dāng)前頁
? ? ? pageSize: 10, // 每頁條數(shù)
? ? ? total: 0 // 總條數(shù)(從接口獲取)
? ? };
? ? $scope.productList = []; // 商品列表數(shù)據(jù)
? ? $scope.selectedProductIds = []; // 選中的商品ID(用于批量操作)
? ? $scope.isLoading = false; // 加載狀態(tài)
? ? // 2. 獲取商品分類列表(用于篩選下拉框)
? ? const getProductCategories = () => {
? ? ? // 實(shí)際項(xiàng)目中調(diào)用后端API獲取分類,此處模擬數(shù)據(jù)
? ? ? $http.get('/api/categories').then(response => {
? ? ? ? $scope.categories = response.data; // 格式:[{ id: 1, name: '服裝' }, ...]
? ? ? });
? ? };
? ? // 3. 加載商品列表數(shù)據(jù)(核心方法,支持篩選、排序、分頁)
? ? const loadProductList = () => {
? ? ? $scope.isLoading = true;
? ? ? ProductService.getProductList({
? ? ? ? page: $scope.pagination.page,
? ? ? ? pageSize: $scope.pagination.pageSize,
? ? ? ? filters: $scope.filters,
? ? ? ? sortConfig: $scope.sortConfig
? ? ? }).then(result => {
? ? ? ? $scope.productList = result.list;
? ? ? ? $scope.pagination.total = result.total;
? ? ? ? $scope.isLoading = false;
? ? ? ? // 重置選中的商品ID(分頁后選中狀態(tài)清空)
? ? ? ? $scope.selectedProductIds = [];
? ? ? }).catch(error => {
? ? ? ? $scope.isLoading = false;
? ? ? ? NotificationService.error(error.data?.errorMsg || '加載商品列表失敗');
? ? ? });
? ? };
? ? // 4. 篩選條件變更:點(diǎn)擊“搜索”按鈕觸發(fā)
? ? $scope.onSearch = () => {
? ? ? $scope.pagination.page = 1; // 篩選后重置為第一頁
? ? ? loadProductList();
? ? };
? ? // 5. 排序切換:點(diǎn)擊表頭觸發(fā)(切換升序/降序)
? ? $scope.onSort = (field) => {
? ? ? if ($scope.sortConfig.field === field) {
? ? ? ? // 同一字段:切換排序方向
? ? ? ? $scope.sortConfig.direction = $scope.sortConfig.direction === 'asc' ? 'desc' : 'asc';
? ? ? } else {
? ? ? ? // 不同字段:默認(rèn)升序
? ? ? ? $scope.sortConfig.field = field;
? ? ? ? $scope.sortConfig.direction = 'asc';
? ? ? }
? ? ? loadProductList();
? ? };
? ? // 6. 分頁切換:頁碼或每頁條數(shù)變更觸發(fā)
? ? $scope.onPageChange = (page) => {
? ? ? $scope.pagination.page = page;
? ? ? loadProductList();
? ? };
? ? // 7. 選中商品變更:勾選/取消勾選商品觸發(fā)
? ? $scope.onSelectProduct = (productId, isSelected) => {
? ? ? if (isSelected) {
? ? ? ? // 勾選:添加到選中列表
? ? ? ? $scope.selectedProductIds.push(productId);
? ? ? } else {
? ? ? ? // 取消勾選:從選中列表移除
? ? ? ? $scope.selectedProductIds = $scope.selectedProductIds.filter(id => id !== productId);
? ? ? }
? ? };
? ? // 8. 批量操作商品:觸發(fā)批量刪除/上架/下架
? ? $scope.batchOperate = (operateType) => {
? ? ? if ($scope.selectedProductIds.length === 0) {
? ? ? ? NotificationService.warning('請先選擇要操作的商品');
? ? ? ? return;
? ? ? }
? ? ? // 確認(rèn)操作(使用之前封裝的modal指令)
? ? ? $scope.batchOperateModal = {
? ? ? ? show: true,
? ? ? ? config: {
? ? ? ? ? title: `批量${operateType}商品`,
? ? ? ? ? content: `確定要${operateType}選中的${$scope.selectedProductIds.length}個(gè)商品嗎?`,
? ? ? ? ? type: 'confirm',
? ? ? ? ? onButtonClick: (result) => {
? ? ? ? ? ? if (result.buttonType === 'confirm') {
? ? ? ? ? ? ? // 確認(rèn)操作:調(diào)用服務(wù)批量處理
? ? ? ? ? ? ? ProductService.batchOperateProducts($scope.selectedProductIds, operateType)
? ? ? ? ? ? ? ? .then(() => {
? ? ? ? ? ? ? ? ? NotificationService.success(`批量${operateType}商品成功`);
? ? ? ? ? ? ? ? ? loadProductList(); // 重新加載列表
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? .catch(error => {
? ? ? ? ? ? ? ? ? NotificationService.error(error.data?.errorMsg || `批量${operateType}商品失敗`);
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }
? ? ? ? ? ? $scope.batchOperateModal.show = false;
? ? ? ? ? }
? ? ? ? }
? ? ? };
? ? };
? ? // 9. 單個(gè)商品操作:編輯/刪除/上架/下架
? ? $scope.operateProduct = (productId, operateType, productData = {}) => {
? ? ? if (operateType === 'edit') {
? ? ? ? // 編輯操作:跳轉(zhuǎn)到編輯頁(攜帶商品ID)
? ? ? ? $state.go('app.product.edit', { productId: productId });
? ? ? ? return;
? ? ? }
? ? ? // 其他操作(刪除/上架/下架):確認(rèn)后執(zhí)行
? ? ? const operateName = { delete: '刪除', up: '上架', down: '下架' }[operateType];
? ? ? NotificationService.confirm(`確定要${operateName}該商品嗎?`, () => {
? ? ? ? ProductService.operateProduct(productId, operateType, productData)
? ? ? ? ? .then(() => {
? ? ? ? ? ? NotificationService.success(`${operateName}商品成功`);
? ? ? ? ? ? loadProductList(); // 重新加載列表
? ? ? ? ? })
? ? ? ? ? .catch(error => {
? ? ? ? ? ? NotificationService.error(error.data?.errorMsg || `${operateName}商品失敗`);
? ? ? ? ? });
? ? ? });
? ? };
? ? // 10. 初始化:加載分類與商品列表
? ? const init = () => {
? ? ? getProductCategories();
? ? ? loadProductList();
? ? };
? ? init();
? }])
? // 配置商品列表路由
? .config(['$stateProvider', function($stateProvider) {
? ? $stateProvider.state('app.product.list', {
? ? ? url: '/list',
? ? ? template: productListTemplate,
? ? ? controller: 'ProductListCtrl',
? ? ? data: {
? ? ? ? requireLogin: true, // 需要登錄權(quán)限
? ? ? ? menuName: '商品管理' // 用于導(dǎo)航欄高亮
? ? ? }
? ? });
? }])
? .name;
(3)商品列表視圖(productList.html)
基于AngularJS Material的md-table實(shí)現(xiàn)表格布局,集成篩選、排序、分頁組件。
<md-card>
? <!-- 卡片頭部:標(biāo)題 + 篩選區(qū)域 -->
? <md-card-header>
? ? <md-card-header-text>
? ? ? <h2>商品列表</h2>
? ? </md-card-header-text>
? ? <md-card-actions layout="row" layout-align="end center">
? ? ? <!-- 新增商品按鈕 -->
? ? ? <md-button class="md-primary md-raised" ng-click="$state.go('app.product.add')">
? ? ? ? <i class="fa fa-plus"></i> 新增商品
? ? ? </md-button>
? ? </md-card-actions>
? </md-card-header>
? <!-- 篩選區(qū)域 -->
? <md-card-content>
? ? <div layout="row" layout-wrap gap="16px" class="filter-container">
? ? ? <!-- 商品名稱篩選 -->
? ? ? <md-input-container flex="25">
? ? ? ? <label>商品名稱</label>
? ? ? ? <input type="text" ng-model="filters.name" placeholder="請輸入商品名稱搜索">
? ? ? </md-input-container>
? ? ? <!-- 分類篩選 -->
? ? ? <md-input-container flex="25">
? ? ? ? <label>商品分類</label>
? ? ? ? <md-select ng-model="filters.categoryId" placeholder="請選擇分類">
? ? ? ? ? <md-option value="">全部分類</md-option>
? ? ? ? ? <md-option ng-repeat="category in categories" value="{{category.id}}">
? ? ? ? ? ? {{category.name}}
? ? ? ? ? </md-option>
? ? ? ? </md-select>
? ? ? </md-input-container>
? ? ? <!-- 狀態(tài)篩選 -->
? ? ? <md-input-container flex="25">
? ? ? ? <label>商品狀態(tài)</label>
? ? ? ? <md-select ng-model="filters.status" placeholder="請選擇狀態(tài)">
? ? ? ? ? <md-option value="">全部狀態(tài)</md-option>
? ? ? ? ? <md-option value="1">上架</md-option>
? ? ? ? ? <md-option value="0">下架</md-option>
? ? ? ? </md-select>
? ? ? </md-input-container>
? ? ? <!-- 搜索按鈕 -->
? ? ? <md-input-container flex="20" layout-align="end center">
? ? ? ? <md-button class="md-primary md-raised" ng-click="onSearch()">
? ? ? ? ? <i class="fa fa-search"></i> 搜索
? ? ? ? </md-button>
? ? ? </md-input-container>
? ? </div>
? ? <!-- 批量操作按鈕 -->
? ? <div class="batch-operate-container" ng-if="productList.length > 0">
? ? ? <md-button class="md-warn" ng-click="batchOperate('delete')" ng-disabled="selectedProductIds.length === 0">
? ? ? ? <i class="fa fa-trash"></i> 批量刪除
? ? ? </md-button>
? ? ? <md-button class="md-primary" ng-click="batchOperate('上架')" ng-disabled="selectedProductIds.length === 0">
? ? ? ? <i class="fa fa-arrow-up"></i> 批量上架
? ? ? </md-button>
? ? ? <md-button class="md-accent" ng-click="batchOperate('下架')" ng-disabled="selectedProductIds.length === 0">
? ? ? ? <i class="fa fa-arrow-down"></i> 批量下架
? ? ? </md-button>
? ? </div>
? ? <!-- 商品表格 -->
? ? <md-table-container ng-if="productList.length > 0">
? ? ? <table md-table md-row-select="false" ng-model="selectedProductIds">
? ? ? ? <!-- 復(fù)選框列 -->
? ? ? ? <thead md-head>
? ? ? ? ? <tr md-row>
? ? ? ? ? ? <th md-column width="50px">
? ? ? ? ? ? ? <md-checkbox ng-model="selectAll" ng-click="selectAllProducts(selectAll)"></md-checkbox>
? ? ? ? ? ? </th>
? ? ? ? ? ? <!-- 表頭:支持點(diǎn)擊排序 -->
? ? ? ? ? ? <th md-column md-sort-header="id" ng-click="onSort('id')">商品ID</th>
? ? ? ? ? ? <th md-column md-sort-header="name" ng-click="onSort('name')">商品名稱</th>
? ? ? ? ? ? <th md-column md-sort-header="categoryName" ng-click="onSort('categoryName')">分類</th>
? ? ? ? ? ? <th md-column md-sort-header="price" ng-click="onSort('price')">
</doubaocanvas>