531 lines
17 KiB
Vue
531 lines
17 KiB
Vue
<template>
|
||
<el-dialog
|
||
title="绑定银行卡"
|
||
:visible.sync="dialogVisible"
|
||
width="40%"
|
||
:close-on-click-modal="false"
|
||
:before-close="handleCloseDialog"
|
||
>
|
||
<div class="bind-bank-form-container">
|
||
<el-form
|
||
ref="bankForm"
|
||
:rules="rules"
|
||
:model="form"
|
||
label-position="right"
|
||
label-width="100px"
|
||
>
|
||
<el-form-item label="银行卡号:" prop="cardNumber">
|
||
<el-input
|
||
v-model="form.cardNumber"
|
||
placeholder="请输入银行卡号"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="银行选择:" prop="bankId">
|
||
<el-select v-model="form.bankId" placeholder="请选择银行名称">
|
||
<el-option
|
||
v-for="(item, index) in bankCardChioceList"
|
||
:key="index"
|
||
:label="item.bankName"
|
||
:value="item.pkId"
|
||
></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="开户支行:" prop="subBankName">
|
||
<el-input
|
||
v-model="form.subBankName"
|
||
placeholder="请输入开户支行"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="姓名:" prop="accountName">
|
||
<el-input
|
||
v-model="form.accountName"
|
||
placeholder="请输入姓名"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="证件类型:" prop="idType">
|
||
<el-select v-model="form.idType" placeholder="请选择证件类型">
|
||
<el-option
|
||
v-for="(item, index) in cardTypeList"
|
||
:key="index"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="证件号码:" prop="idCard">
|
||
<el-input
|
||
v-model="form.idCard"
|
||
placeholder="请输入证件号码"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item v-if="isMainlandChinaUser" label="手机号:" prop="phone">
|
||
<el-input
|
||
v-model="form.phone"
|
||
placeholder="银行卡预留手机号"
|
||
></el-input>
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="isMainlandChinaUser && needsVerificationCode"
|
||
label="验证码:"
|
||
prop="verificationCode"
|
||
>
|
||
<el-input
|
||
v-model="form.verificationCode"
|
||
placeholder="请输入验证码"
|
||
></el-input>
|
||
<el-button
|
||
style="margin-left: 10px"
|
||
:class="{ 'is-disabled': isSendingCode }"
|
||
:disabled="isSendingCode"
|
||
type="primary"
|
||
@click="sendVerificationCode"
|
||
>
|
||
{{ verificationCodeButtonText }}
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<div class="dialog-footer">
|
||
<!-- <div class="btn cancel-btn" @click="handleCloseDialog">取消</div> -->
|
||
<el-button @click="handleCloseDialog">取消</el-button>
|
||
<el-button type="primary" @click="submitBankForm('bankForm')"
|
||
>确定
|
||
</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script>
|
||
import * as walletApi from "@/api/wallet.js";
|
||
import { mapGetters } from "vuex";
|
||
export default {
|
||
data() {
|
||
// 自定义验证码校验逻辑
|
||
const validateVerificationCode = (rule, value, callback) => {
|
||
if (this.isMainlandChinaUser && this.needsVerificationCode && !value) {
|
||
callback(new Error("请输入验证码"));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
// 自定义手机号校验逻辑
|
||
const validatePhoneNumber = (rule, value, callback) => {
|
||
if (this.isMainlandChinaUser && !value) {
|
||
callback(new Error("请输入正确的手机号"));
|
||
} else if (this.isMainlandChinaUser && value && value.length < 11) {
|
||
callback(new Error("请输入正确的手机号"));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
|
||
return {
|
||
cardTypeList: [], // 证件类型列表
|
||
dialogVisible: false, // 控制对话框显示
|
||
form: {
|
||
cardNumber: "", // 银行卡号
|
||
accountName: "", // 姓名
|
||
idCard: "", // 证件号码
|
||
phone: "", // 手机号
|
||
bankId: "", // 银行ID (原 pkBank)
|
||
verificationCode: "", // 验证码
|
||
subBankName: "", // 开户支行
|
||
idType: "", // 证件类型
|
||
},
|
||
isSendingCode: false, // 是否正在发送验证码
|
||
countdownSeconds: 60, // 倒计时秒数
|
||
verificationCodeButtonText: "获取验证码", // 验证码按钮文字
|
||
countdownTimer: null, // 倒计时定时器
|
||
rules: {
|
||
cardNumber: [
|
||
{ required: true, message: "请输入银行卡号", trigger: "blur" },
|
||
],
|
||
verificationCode: [
|
||
{ required: true, message: "请输入验证码", trigger: "blur" },
|
||
{ validator: validateVerificationCode, trigger: "blur" },
|
||
],
|
||
idType: [
|
||
{ required: true, message: "请选择证件类型", trigger: "change" },
|
||
],
|
||
accountName: [
|
||
{ required: true, message: "请输入姓名", trigger: "blur" },
|
||
],
|
||
subBankName: [
|
||
{ required: true, message: "请输入开户支行", trigger: "blur" },
|
||
],
|
||
// bankNo 验证似乎是重复的,已移除
|
||
// name 验证似乎是重复的,已移除
|
||
idCard: [
|
||
{ required: true, message: "请输入证件号码", trigger: "blur" },
|
||
],
|
||
// smsCode 似乎未使用,已移除
|
||
phone: [
|
||
{
|
||
required: true,
|
||
message: "请输入银行卡预留手机号",
|
||
trigger: "blur",
|
||
},
|
||
// { min: 11, message: '请输入正确的手机号', trigger: "blur" }, // 合并到 validator
|
||
{ validator: validatePhoneNumber, trigger: "blur" },
|
||
],
|
||
bankId: [
|
||
// 原 pkBank
|
||
{ required: true, message: "请选择银行卡", trigger: "change" },
|
||
],
|
||
},
|
||
isBankCardVerified: false, // 银行卡是否已验证通过 (原 ifpass)
|
||
bankCardChioceList: [], // 可选银行列表
|
||
// pkCountry: "", // 由 isMainlandChinaUser 计算属性替代
|
||
needsVerificationCode: true, // 是否需要验证码 (原 cancode)
|
||
};
|
||
},
|
||
props: {
|
||
// isAdd 更名为 visible,更符合对话框的常用属性名
|
||
visible: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
},
|
||
computed: {
|
||
...mapGetters(["userInfo"]),
|
||
// 计算用户是否为中国大陆用户
|
||
isMainlandChinaUser() {
|
||
return this.userInfo?.pkCountry === 1;
|
||
},
|
||
},
|
||
watch: {
|
||
// 监听 prop 变化来控制对话框显示
|
||
visible(newVal) {
|
||
this.dialogVisible = newVal;
|
||
if (newVal) {
|
||
// 对话框打开时重置状态(如果需要)
|
||
this.resetFormState();
|
||
}
|
||
},
|
||
// 监听对话框内部状态变化,并通知父组件
|
||
dialogVisible(newVal) {
|
||
if (!newVal) {
|
||
this.$emit("update:visible", false); // 使用 .sync 修饰符更新父组件状态
|
||
this.$emit("closeBind", this.isBankCardVerified ? 1 : 0); // 保留原有逻辑,根据验证状态发送不同值
|
||
this.resetFormAndValidation(); // 关闭时重置表单和状态
|
||
}
|
||
},
|
||
},
|
||
created() {
|
||
this.fetchBankCardChoices();
|
||
this.checkVerificationRequirement();
|
||
// this.pkCountry = this.userInfo.pkCountry; // 不再需要单独存储,使用计算属性
|
||
},
|
||
beforeDestroy() {
|
||
// 组件销毁前清除定时器
|
||
if (this.countdownTimer) {
|
||
clearInterval(this.countdownTimer);
|
||
}
|
||
},
|
||
methods: {
|
||
// 检查是否需要验证码
|
||
checkVerificationRequirement() {
|
||
walletApi
|
||
.checkIfWhite()
|
||
.then((res) => {
|
||
// 后端接口 flag 为 'Y' 表示在白名单,不需要验证码
|
||
this.needsVerificationCode = !(res.code == 200 && res.flag === "Y");
|
||
})
|
||
.catch((_err) => {
|
||
// 处理获取白名单状态失败的情况,可以设置默认值或提示用户
|
||
console.error("获取白名单状态失败:", _err);
|
||
this.needsVerificationCode = true; // 默认为需要验证码
|
||
});
|
||
},
|
||
// 获取银行卡选项和证件类型
|
||
fetchBankCardChoices() {
|
||
walletApi
|
||
.getBankCardChoiceList()
|
||
.then((res) => {
|
||
this.bankCardChioceList = res.data || [];
|
||
})
|
||
.catch((_err) => {
|
||
console.error("获取银行列表失败:", _err);
|
||
});
|
||
|
||
walletApi
|
||
.getCardType()
|
||
.then((res) => {
|
||
this.cardTypeList = res.data || [];
|
||
})
|
||
.catch((_err) => {
|
||
console.error("获取证件类型失败:", _err);
|
||
});
|
||
},
|
||
// 关闭对话框处理
|
||
handleCloseDialog() {
|
||
this.dialogVisible = false;
|
||
// 关闭时已在 watch 中处理了 resetFields 和 emit 事件
|
||
},
|
||
// 重置表单和相关状态
|
||
resetFormAndValidation() {
|
||
this.$refs.bankForm?.resetFields();
|
||
this.isBankCardVerified = false;
|
||
this.clearCountdown(); // 清除倒计时状态
|
||
},
|
||
// 清除倒计时状态
|
||
clearCountdown() {
|
||
if (this.countdownTimer) {
|
||
clearInterval(this.countdownTimer);
|
||
this.countdownTimer = null;
|
||
}
|
||
this.isSendingCode = false;
|
||
this.verificationCodeButtonText = "获取验证码";
|
||
this.countdownSeconds = 60;
|
||
},
|
||
// 重置表单外的状态(如果需要)
|
||
resetFormState() {
|
||
this.isBankCardVerified = false;
|
||
this.clearCountdown();
|
||
},
|
||
// 实际执行绑卡操作
|
||
performBindBank() {
|
||
// 对于非中国大陆用户,或不需要验证码的情况,直接标记为已验证
|
||
if (!this.isMainlandChinaUser || !this.needsVerificationCode) {
|
||
this.isBankCardVerified = true;
|
||
}
|
||
|
||
if (!this.isBankCardVerified) {
|
||
this.$message({
|
||
message: "请先完成银行卡验证", // 或 '银行卡验证不一致'
|
||
type: "warning",
|
||
});
|
||
return;
|
||
}
|
||
|
||
// let that = this; // 不再需要
|
||
|
||
walletApi
|
||
.bindWalletBankAdd(this.form)
|
||
.then(() => {
|
||
this.$message({
|
||
message: "银行卡绑定成功",
|
||
type: "success",
|
||
});
|
||
// 成功后关闭对话框,并触发成功回调
|
||
setTimeout(() => {
|
||
// this.resetFormAndValidation(); // 已移至 watch 中处理关闭
|
||
// this.$emit("closeBind", 1); // 已移至 watch 中处理关闭
|
||
this.dialogVisible = false; // 关闭对话框
|
||
}, 1500);
|
||
})
|
||
.catch((_err) => {
|
||
// 添加错误处理
|
||
console.error("绑定银行卡失败:", _err);
|
||
this.$message({
|
||
message: "银行卡绑定失败,请稍后重试",
|
||
type: "error",
|
||
});
|
||
});
|
||
},
|
||
// 提交表单(验证 + 绑卡)
|
||
submitBankForm(formName) {
|
||
this.$refs[formName].validate((valid) => {
|
||
if (valid) {
|
||
// 如果是中国大陆用户且需要验证码
|
||
if (this.isMainlandChinaUser && this.needsVerificationCode) {
|
||
walletApi
|
||
.verifyBankCard(this.form)
|
||
.then(() => {
|
||
// 验证成功,标记状态并执行绑卡
|
||
this.isBankCardVerified = true;
|
||
this.performBindBank();
|
||
})
|
||
.catch((_err) => {
|
||
// 使用 _err 表示未使用
|
||
// 验证失败
|
||
this.isBankCardVerified = false;
|
||
// 提示后端返回的错误信息,或通用错误信息
|
||
this.$message({
|
||
message: _err?.msg || "银行卡验证失败", // 优先使用后端消息
|
||
type: "warning",
|
||
});
|
||
// this.countdownSeconds = 0; // 不应在此重置倒计时,应在 clearCountdown 中处理
|
||
this.clearCountdown(); // 验证失败时也应该能重新获取验证码
|
||
});
|
||
} else {
|
||
// 非中国大陆用户或不需要验证码,直接尝试绑卡
|
||
// isBankCardVerified 在 performBindBank 开始时会设置为 true
|
||
this.performBindBank();
|
||
}
|
||
} else {
|
||
console.log("表单验证失败!");
|
||
return false;
|
||
}
|
||
});
|
||
},
|
||
// 发送验证码并启动倒计时
|
||
sendVerificationCode() {
|
||
if (!this.form.phone) {
|
||
this.$message({
|
||
message: "请先输入手机号",
|
||
type: "warning",
|
||
});
|
||
return;
|
||
}
|
||
if (this.isSendingCode) return; // 防止重复点击
|
||
|
||
this.isSendingCode = true;
|
||
this.verificationCodeButtonText = `${this.countdownSeconds}s后重新发送`;
|
||
|
||
this.countdownTimer = setInterval(() => {
|
||
this.countdownSeconds--;
|
||
if (this.countdownSeconds <= 0) {
|
||
this.clearCountdown(); // 使用统一的清理函数
|
||
} else {
|
||
this.verificationCodeButtonText = `${this.countdownSeconds}s后重新发送`;
|
||
}
|
||
}, 1000);
|
||
|
||
// 调用发送验证码接口
|
||
walletApi
|
||
.getVerification({ phone: this.form.phone })
|
||
.then(() => {
|
||
this.$message({
|
||
message: "验证码已发送",
|
||
type: "success",
|
||
});
|
||
})
|
||
.catch((_err) => {
|
||
// 添加错误处理
|
||
console.error("发送验证码失败:", _err);
|
||
// this.$message({
|
||
// message: "验证码发送失败,请稍后重试",
|
||
// type: "error",
|
||
// });
|
||
this.clearCountdown(); // 发送失败时重置按钮状态
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
// 统一管理表单容器样式
|
||
.bind-bank-form-container {
|
||
padding: 20px 30px; // 调整内边距
|
||
|
||
// 使用深度选择器影响 Element UI 内部元素
|
||
::v-deep .el-form-item {
|
||
margin-bottom: 22px; // 统一表单项间距
|
||
padding-bottom: 8px; // 增加底部内边距
|
||
display: flex; // 确保 label 和 content 在一行
|
||
align-items: center; // 垂直居中对齐
|
||
|
||
.el-form-item__label {
|
||
padding-right: 12px; // 标签右侧留空
|
||
color: #333; // 标签颜色
|
||
}
|
||
|
||
.el-form-item__content {
|
||
flex: 1; // 内容区域占据剩余空间
|
||
margin-left: 0 !important; // 覆盖 Element UI 默认的 margin
|
||
display: flex; // 使 input 和按钮等可以在一行显示
|
||
align-items: center; // 垂直居中对齐内部元素
|
||
}
|
||
|
||
.el-input,
|
||
.el-select {
|
||
flex: 1; // 输入框/选择框占据主要空间
|
||
width: auto; // 覆盖固定宽度
|
||
}
|
||
|
||
// .el-input__inner, .el-select .el-input__inner {
|
||
// border: none; // 移除内部输入框边框
|
||
// padding-left: 0; // 移除左侧内边距
|
||
// &:focus {
|
||
// box-shadow: none; // 移除聚焦时的阴影
|
||
// }
|
||
// }
|
||
// // 移除选择框外层边框(如果需要完全无边框)
|
||
// .el-select .el-input.is-focus .el-input__inner {
|
||
// border: none !important;
|
||
// }
|
||
// .el-select .el-input__inner:focus {
|
||
// border-color: transparent !important; // Element UI 可能有特定样式,尝试覆盖
|
||
// }
|
||
}
|
||
// 最后一个表单项移除下边框
|
||
::v-deep .el-form-item:last-child {
|
||
border-bottom: none;
|
||
margin-bottom: 0;
|
||
padding-bottom: 0;
|
||
}
|
||
}
|
||
|
||
// 验证码按钮样式
|
||
.verification-code-button {
|
||
background-color: #d5251d; // 主题色
|
||
color: #ffffff;
|
||
border-radius: 4px; // 圆角调整
|
||
padding: 6px 12px; // 内边距调整
|
||
font-size: 12px; // 字体大小调整
|
||
cursor: pointer;
|
||
white-space: nowrap; // 防止文字换行
|
||
margin-left: 10px; // 与输入框的间距
|
||
transition: background-color 0.3s, opacity 0.3s; // 过渡效果
|
||
|
||
&:hover {
|
||
background-color: #c02018; // Hover 颜色加深
|
||
}
|
||
|
||
// 禁用状态样式
|
||
&.is-disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
background-color: #fab6b6; // 禁用时颜色变浅
|
||
&:hover {
|
||
background-color: #fab6b6; // 禁用时 Hover 颜色不变
|
||
}
|
||
}
|
||
}
|
||
|
||
// 对话框底部按钮区域样式
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: space-around; // 按钮均匀分布
|
||
padding: 20px 30px 30px; // 底部区域内边距
|
||
border-top: 1px solid #f0f0f0; // 添加顶部边框分隔
|
||
|
||
.btn {
|
||
width: 160px; // 按钮宽度调整
|
||
height: 45px; // 按钮高度调整
|
||
border-radius: 6px;
|
||
display: flex; // 使用 flex 居中文字
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s; // 添加过渡效果
|
||
}
|
||
|
||
.confirm-btn {
|
||
background-color: #d5251d; // 主题色
|
||
color: #ffffff;
|
||
&:hover {
|
||
background-color: #c02018;
|
||
}
|
||
}
|
||
|
||
.cancel-btn {
|
||
background-color: #f5f5f5; // 取消按钮背景色
|
||
color: #666; // 取消按钮文字颜色
|
||
border: 1px solid #e0e0e0; // 添加边框
|
||
&:hover {
|
||
background-color: #eeeeee;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 对话框标题加粗(Element UI 默认可能已处理)
|
||
::v-deep .el-dialog__title {
|
||
font-weight: 500;
|
||
}
|
||
</style>
|