如何用两个 radio button 和一个按钮合并两个 form
支付老李
结论
保留两个独立的 <form>,并把 radio button 和共用按钮放在表单外。两个 radio button 共用一个 ng-model,用于记录当前选择的支付网关。点击按钮后调用统一的处理函数,再提交对应的 PaymentForm 或 PaymentForm2。
两个 form 不能嵌套,公共按钮也不要使用默认的 submit 类型。否则浏览器可能直接提交其中一个表单,绕过 AngularJS 发起的预支付请求。
推荐实现
<div ng-controller="PaymentController">
<h3>选择支付网关</h3>
<label>
<input
type="radio"
name="paymentGateway"
ng-model="selectedGateway"
value="gateway1">
支付网关 1
</label>
<label>
<input
type="radio"
name="paymentGateway"
ng-model="selectedGateway"
value="gateway2">
支付网关 2
</label>
<button
type="button"
ng-click="submitPayment()"
ng-disabled="!selectedGateway || paymentSubmitting">
{{ paymentSubmitting ? '处理中...' : '支付' }}
</button>
<form
id="PaymentForm"
action="/gateway1/payment"
method="post"
style="display: none;">
<input type="hidden" name="token" value="{{ payment.token }}">
<!-- PaymentForm 的其他字段 -->
</form>
<form
id="PaymentForm2"
action="/gateway2/payment"
method="post"
style="display: none;">
<input type="hidden" name="token" value="{{ payment.token }}">
<!-- PaymentForm2 的其他字段 -->
</form>
</div>
在 AngularJS 中,可以用一份网关配置统一管理请求参数和目标 form:
$scope.paymentSubmitting = false;
$scope.submitPayment = function () {
var gatewayConfig = {
gateway1: {
formId: 'PaymentForm',
includeOrderId: false
},
gateway2: {
formId: 'PaymentForm2',
includeOrderId: true
}
};
var config = gatewayConfig[$scope.selectedGateway];
if (!config || $scope.paymentSubmitting) {
return;
}
var requestData = {
Amount: $scope.totalPrice,
Item: $scope.item.Id,
Description: $scope.item.Title,
Count: $scope.defaultQuantity
};
if (config.includeOrderId) {
requestData.OrderId = $scope.item.Id;
}
$scope.paymentSubmitting = true;
$http.post('/payment/happy_payment_register/', requestData)
.then(function (response) {
if (response.data === 'Fail') {
throw new Error('Payment registration failed');
}
$scope.payment = response.data;
// 等待 AngularJS 将 payment 更新到表单字段后再提交
$timeout(function () {
var form = document.getElementById(config.formId);
if (!form) {
$scope.paymentSubmitting = false;
throw new Error('Payment form not found: ' + config.formId);
}
form.submit();
});
})
.catch(function (error) {
$scope.paymentSubmitting = false;
console.error(error);
});
};
这里必须使用 type="button"。点击按钮时只会执行 submitPayment(),再由代码提交当前网关对应的表单。
如果希望保留两个现有函数
如果不想合并现有函数,可以增加一个分发函数:
<label>
<input
type="radio"
name="paymentGateway"
ng-model="selectedGateway"
value="gateway1">
支付网关 1
</label>
<label>
<input
type="radio"
name="paymentGateway"
ng-model="selectedGateway"
value="gateway2">
支付网关 2
</label>
<button
type="button"
ng-click="submitSelectedPayment()"
ng-disabled="!selectedGateway">
支付
</button>
$scope.submitSelectedPayment = function () {
if ($scope.selectedGateway === 'gateway1') {
$scope.SetPrePayment();
} else if ($scope.selectedGateway === 'gateway2') {
$scope.SetPrePayment2();
}
};
需要注意的是,原代码把两个函数都声明成了:
$scope.SetPrePayment = function () {
// ...
};
后面的声明会覆盖前面的声明,因此第二个函数必须改名:
$scope.SetPrePayment2 = function () {
// ...
};
注意事项
- 两个 form 必须并列放置,不能互相嵌套。
- 两个 radio button 应使用相同的
ng-model,但必须设置不同的value。 - 公共按钮应使用
type="button",以免意外触发表单的原生提交。 form.submit()会直接调用浏览器的原生提交,不会触发 AngularJS 的ng-submit。用于跳转到外部支付网关时,这种方式通常没有问题。$timeout用于等待$scope.payment更新到对应的隐藏字段,一般不需要像原代码那样嵌套两层$timeout。- 处理支付期间应禁用按钮,避免用户重复点击,创建多笔预支付记录。
- 支付金额、商品信息和订单号都必须由服务端校验,不能只信任前端提交的数据。
备注:内容仅供参考。
