在掌握 AngularJS 进阶开发技术(自定义指令、拦截器、路由)后,进阶应用的核心是 “将技术融入实际业务,解决企业级复杂需求”—— 比如处理多步骤表单、实现可编辑列表、解决跨组件通信、优化大数据渲染性能等。本文围绕企业项目高频核心场景,提供 “需求拆解 - 技术选型 - 完整实现” 的全流程方案,帮你具备独立开发中大型 AngularJS 应用的能力。
一、场景一:企业级多步骤表单(分步提交 + 数据回显)
后台管理系统中,“多步骤表单”(如用户注册、商品发布)是常见需求,需实现 “步骤导航、数据暂存、校验联动、提交回显” 功能。结合 AngularJS 的表单验证、服务封装、本地存储技术,可构建稳定易用的多步骤表单方案。
1. 需求拆解
分 3 步完成 “商品发布”:基础信息(名称、分类)→ 规格配置(价格、库存)→ 内容设置(描述、图片);
每步表单单独校验,校验通过才能进入下一步;
支持 “上一步 / 下一步” 导航,暂存每步数据(刷新页面不丢失);
最终提交时,整合所有步骤数据,提交后清空暂存并重置表单。
2. 技术选型
表单校验:AngularJS 内置表单验证(ng-required/ng-pattern)+ 自定义指令(如规格动态字段);
数据暂存:localStorage存储每步数据,服务封装存储逻辑;
步骤管理:控制器维护步骤状态,通过ng-show/ng-hide控制步骤切换;
数据提交:拦截器统一处理请求,服务封装提交 API。
3. 完整实现代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AngularJS进阶应用 - 多步骤商品发布表单</title>
<script src="https://apps.bdimg.com/libs/angular.js/1.8.2/angular.min.js"></script>
<style>
.form-container {
width: 800px;
margin: 50px auto;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
}
.step-nav {
display: flex;
margin-bottom: 30px;
position: relative;
}
.step-nav::before {
content: '';
position: absolute;
top: 15px;
left: 0;
width: 100%;
height: 2px;
background: #eee;
z-index: 1;
}
.step-item {
flex: 1;
text-align: center;
position: relative;
z-index: 2;
}
.step-icon {
width: 32px;
height: 32px;
line-height: 32px;
border-radius: 50%;
background: #eee;
color: #999;
margin: 0 auto 8px;
font-weight: bold;
}
.step-item.active .step-icon {
background: #4285f4;
color: white;
}
.step-item.completed .step-icon {
background: #43a047;
color: white;
}
.step-item.completed .step-text {
color: #43a047;
}
.step-content {
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #666;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.form-control.ng-valid.ng-dirty {
border-color: #43a047;
}
.form-control.ng-invalid.ng-dirty {
border-color: #e53935;
}
.error-message {
color: #e53935;
font-size: 12px;
margin-top: 5px;
height: 16px;
}
.spec-group {
display: flex;
gap: 10px;
margin-bottom: 10px;
padding: 10px;
background: #f9fafb;
border-radius: 4px;
}
.spec-group input {
flex: 1;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-prev {
background: #f5f5f5;
color: #333;
margin-right: 10px;
}
.btn-next, .btn-submit {
background: #4285f4;
color: white;
}
.btn:disabled {
background: #90caf9;
cursor: not-allowed;
}
.submit-success {
padding: 20px;
background: #e8f5e9;
color: #2e7d32;
border-radius: 4px;
text-align: center;
margin-top: 20px;
}
</style>
</head>
<body ng-app="multiStepFormApp">
<div class="form-container" ng-controller="ProductFormCtrl">
<!-- 步骤导航 -->
<div class="step-nav">
<div class="step-item" ng-class="{ active: currentStep === 1, completed: currentStep > 1 }">
<div class="step-icon">1</div>
<div class="step-text">基础信息</div>
</div>
<div class="step-item" ng-class="{ active: currentStep === 2, completed: currentStep > 2 }">
<div class="step-icon">2</div>
<div class="step-text">规格配置</div>
</div>
<div class="step-item" ng-class="{ active: currentStep === 3, completed: currentStep > 3 }">
<div class="step-icon">3</div>
<div class="step-text">内容设置</div>
</div>
</div>
<!-- 步骤内容:基础信息 -->
<div class="step-content" ng-show="currentStep === 1">
<form name="basicForm" novalidate>
<div class="form-group">
<label for="productName">商品名称 *</label>
<"zhiq.zhaopin.com/question/11229716">
<"zhiq.zhaopin.com/question/11229718">
<"zhiq.zhaopin.com/question/11229720">
<"zhiq.zhaopin.com/question/11229723">
<"zhiq.zhaopin.com/question/11229726">
<"zhiq.zhaopin.com/question/11229727">
<"zhiq.zhaopin.com/question/11229728">
<"zhiq.zhaopin.com/question/11229742">
<"zhiq.zhaopin.com/question/11229779">
<"zhiq.zhaopin.com/question/11229840">
<"zhiq.zhaopin.com/question/11229876">
<"zhiq.zhaopin.com/question/11229893">
<"zhiq.zhaopin.com/question/11229927">
<"zhiq.zhaopin.com/question/11229944">
<"zhiq.zhaopin.com/question/11229958">
<"zhiq.zhaopin.com/question/11229966">
<"zhiq.zhaopin.com/question/11229969">
<"zhiq.zhaopin.com/question/11229973">
<"zhiq.zhaopin.com/question/11229980">
<"zhiq.zhaopin.com/question/11229981">
<input type="text" id="productName" name="productName" class="form-control"
ng-model="formData.basic.name" ng-required="true" ng-maxlength="50">
<div class="error-message">
<span ng-show="basicForm.productName.$dirty && basicForm.productName.$error.required">商品名称不能为空</span>
<span ng-show="basicForm.productName.$dirty && basicForm.productName.$error.maxlength">商品名称最多50个字符</span>
</div>
</div>
<div class="form-group">
<label for="productCategory">商品分类 *</label>
<select id="productCategory" name="productCategory" class="form-control"
ng-model="formData.basic.categoryId" ng-required="true">
<option value="">请选择分类</option>
<option value="1">服装</option>
<option value="2">电子产品</option>
<option value="3">食品</option>
</select>
<div class="error-message">
<span ng-show="basicForm.productCategory.$dirty && basicForm.productCategory.$error.required">请选择商品分类</span>
</div>
</div>
</form>
</div>
<!-- 步骤内容:规格配置(动态字段) -->
<div class="step-content" ng-show="currentStep === 2">
<form name="specForm" novalidate>
<div class="form-group">
<label>规格配置 *</label>
<div id="specGroups" ng-repeat="spec in formData.specs">
<div class="spec-group">
<input type="text" class="form-control" placeholder="规格名称(如红色-XL)"
ng-model="spec.name" ng-required="true">
<input type="number" class="form-control" placeholder="单价"
ng-model="spec.price" ng-required="true" min="0" step="0.01">
<input type="number" class="form-control" placeholder="库存"
ng-model="spec.stock" ng-required="true" min="0">
</div>
</div>
<button type="button" class="btn btn-prev" ng-click="addSpec()">+ 添加规格</button>
<div class="error-message" ng-show="specForm.$submitted || isSpecCheck">
<span ng-if="formData.specs.length === 0">至少添加1组规格</span>
<span ng-if="formData.specs.length > 0 && !isAllSpecValid()">请完善所有规格的必填项</span>
</div>
</div>
</form>
</div>
<!-- 步骤内容:内容设置 -->
<div class="step-content" ng-show="currentStep === 3">
<form name="contentForm" novalidate>
<div class="form-group">
<label for="productDesc">商品描述 *</label>
<textarea id="productDesc" name="productDesc" class="form-control" rows="5"
ng-model="formData.content.desc" ng-required="true" ng-minlength="20"></textarea>
<div class="error-message">
<span ng-show="contentForm.productDesc.$dirty && contentForm.productDesc.$error.required">商品描述不能为空</span>
<span ng-show="contentForm.productDesc.$dirty && contentForm.productDesc.$error.minlength">商品描述至少20个字符</span>
</div>
</div>
<div class="form-group">
<label for="productImage">商品主图 *</label>
<input type="text" id="productImage" name="productImage" class="form-control"
ng-model="formData.content.imageUrl" ng-required="true" ng-pattern="/^(http|https):\/\/.+\.(jpg|png|jpeg)$/">
<div class="error-message">
<span ng-show="contentForm.productImage.$dirty && contentForm.productImage.$error.required">请输入商品主图URL</span>
<span ng-show="contentForm.productImage.$dirty && contentForm.productImage.$error.pattern">请输入合法的图片URL(http/https开头,jpg/png格式)</span>
</div>
</div>
</form>
</div>
<!-- 操作按钮 -->
<div class="form-actions" ng-hide="submitSuccess">
<button type="button" class="btn btn-prev" ng-click="goToPrevStep()" ng-disabled="currentStep === 1">上一步</button>
<button type="button" class="btn btn-next" ng-click="goToNextStep()" ng-disabled="currentStep === 3">下一步</button>
<button type="button" class="btn btn-submit" ng-show="currentStep === 3" ng-click="submitForm()">提交商品</button>
</div>
<!-- 提交成功提示 -->
<div class="submit-success" ng-show="submitSuccess">
<h3>商品发布成功!</h3>
<p>商品名称:{{ formData.basic.name }}</p>
<p>规格数量:{{ formData.specs.length }} 组</p>
<button type="button" class="btn btn-prev" ng-click="resetForm()">重新发布</button>
</div>
</div>
<script>
// 1. 创建应用模块
const multiStepFormApp = angular.module('multiStepFormApp', []);
// 2. 封装本地存储服务:暂存表单数据
multiStepFormApp.service('StorageService', function() {
const STORAGE_KEY = 'productFormData';
// 保存数据到localStorage
this.save = function(data) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
};
// 从localStorage获取数据
this.get = function() {
const data = localStorage.getItem(STORAGE_KEY);
return data ? JSON.parse(data) : null;
};
// 清空数据
this.clear = function() {
localStorage.removeItem(STORAGE_KEY);
};
});
// 3. 控制器:多步骤表单逻辑
multiStepFormApp.controller('ProductFormCtrl', function($scope, StorageService) {
// 初始化表单数据(优先从本地存储获取,无则初始化)
const initFormData = function() {
const savedData = StorageService.get();
if (savedData) {
$scope.formData = savedData;
} else {
$scope.formData = {
basic: { name: '', categoryId: '' }, // 基础信息
specs: [{ name: '', price: '', stock: '' }], // 规格配置(默认1组)
content: { desc: '', imageUrl: '' } // 内容设置
};
}
};
// 初始化步骤状态
$scope.currentStep = 1; // 当前步骤(1-3)
$scope.isSpecCheck = false; // 规格校验标记
$scope.submitSuccess = false; // 提交成功标记
// 初始化
initFormData();
// 添加规格(动态字段)
$scope.addSpec = function() {
$scope.formData.specs.push({ name: '', price: '', stock: '' });
// 保存数据到本地存储
StorageService.save($scope.formData);
};
// 校验当前步骤表单
$scope.validateCurrentStep = function() {
let isValid = true;
switch ($scope.currentStep) {
case 1:
// 基础信息表单校验
$scope.basicForm.$submitted = true;
isValid = $scope.basicForm.$valid;
break;
case 2:
// 规格表单校验(动态字段需手动校验)
$scope.isSpecCheck = true;
isValid = $scope.formData.specs.length > 0 && $scope.isAllSpecValid();
break;
case 3:
// 内容设置表单校验
$scope.contentForm.$submitted = true;
isValid = $scope.contentForm.$valid;
break;
}
return isValid;
};
// 校验所有规格是否填写完整(动态字段)
$scope.isAllSpecValid = function() {
return $scope.formData.specs.every(spec => {
return spec.name.trim() && !isNaN(spec.price) && spec.price >= 0 && !isNaN(spec.stock) && spec.stock >= 0;
});
};
// 下一步
$scope.goToNextStep = function() {
if ($scope.validateCurrentStep()) {
// 保存当前步骤数据到本地存储
StorageService.save($scope.formData);
$scope.currentStep++;
// 重置当前步骤的校验标记
$scope.basicForm.$submitted = false;
$scope.isSpecCheck = false;
$scope.contentForm.$submitted = false;
}
};
// 上一步
$scope.goToPrevStep = function() {
$scope.currentStep--;
// 重置当前步骤的校验标记
$scope.basicForm.$submitted = false;
$scope.isSpecCheck = false;
$scope.contentForm.$submitted = false;
};
// 提交表单
$scope.submitForm = function() {
if ($</doubaocanvas>