| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- <template>
- <div class="account-info-page">
- <h1 class="page-title">结算账户</h1>
- <section class="form-block">
- <h2 class="section-head">
- <span class="section-bar" aria-hidden="true" />
- 结算账户
- </h2>
- <el-form
- ref="formRef"
- class="account-form"
- :model="form"
- :rules="rules"
- label-width="120px"
- require-asterisk-position="right"
- label-position="right"
- >
- <el-form-item label="账户户名" prop="accountName">
- <el-input v-model="form.accountName" placeholder="请输入账户户名" clearable maxlength="64" style="max-width: 480px" />
- </el-form-item>
- <el-form-item label="账户类型" prop="accountType">
- <el-radio-group v-model="form.accountType" class="account-type-radio">
- <el-radio :value="BANK_ACCOUNT_TYPE_CORPORATE"> 对公账户 </el-radio>
- <el-radio :value="BANK_ACCOUNT_TYPE_PERSONAL"> 个人储蓄卡 </el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="银行账号" prop="bankAccountNo">
- <div class="field-stack">
- <el-input
- v-model="form.bankAccountNo"
- placeholder="请输入开户银行账号"
- clearable
- maxlength="32"
- style="max-width: 480px"
- />
- <p class="field-tip">
- {{ bankAccountTip }}
- </p>
- </div>
- </el-form-item>
- <el-form-item label="开户银行" prop="bankCode">
- <div class="field-stack">
- <el-popover
- v-model:visible="bankPickerVisible"
- placement="bottom-start"
- :width="580"
- trigger="click"
- popper-class="bank-picker-popper"
- >
- <template #reference>
- <el-input
- readonly
- :model-value="selectedBankLabel"
- placeholder="请选择开户银行"
- style="max-width: 480px"
- class="bank-trigger-input"
- :class="{ 'is-focus-like': bankPickerVisible }"
- >
- <template #suffix>
- <el-icon class="el-select__caret">
- <ArrowDown />
- </el-icon>
- </template>
- </el-input>
- </template>
- <div class="bank-picker" @click.stop>
- <el-radio-group v-model="bankTab" size="small" class="bank-tab-group">
- <el-radio-button value="common"> 常用银行 </el-radio-button>
- <el-radio-button value="A-F"> ABCDEF </el-radio-button>
- <el-radio-button value="G-L"> GHJKL </el-radio-button>
- <el-radio-button value="M-P"> MNOP </el-radio-button>
- <el-radio-button value="Q-U"> QRSTU </el-radio-button>
- <el-radio-button value="V-Z"> VWXYZ </el-radio-button>
- </el-radio-group>
- <div class="bank-grid-wrap">
- <div class="bank-grid">
- <button
- v-for="b in filteredBanks"
- :key="b.code"
- type="button"
- class="bank-cell"
- :class="{ 'is-active': form.bankCode === b.code }"
- @click="selectBank(b)"
- >
- <span class="bank-logo" :style="{ background: b.color }">{{ b.logoText }}</span>
- <span class="bank-name">{{ b.shortName }}</span>
- </button>
- </div>
- </div>
- </div>
- </el-popover>
- </div>
- </el-form-item>
- </el-form>
- </section>
- <div class="footer-actions">
- <el-button @click="onBack"> 返回 </el-button>
- <el-button type="primary" class="btn-next" :loading="submitLoading" @click="onSubmit"> 确定 </el-button>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
- import { useRoute, useRouter } from "vue-router";
- import { ElMessage } from "element-plus";
- import type { FormInstance, FormRules } from "element-plus";
- import { ArrowDown } from "@element-plus/icons-vue";
- import { localGet, localSet } from "@/utils/index";
- const BUSINESS_DATA_CACHE_KEY = "businessData";
- const BANK_ACCOUNT_TYPE_CORPORATE = "BANK_ACCOUNT_TYPE_CORPORATE" as const;
- const BANK_ACCOUNT_TYPE_PERSONAL = "BANK_ACCOUNT_TYPE_PERSONAL" as const;
- const formRef = ref<FormInstance>();
- const submitLoading = ref(false);
- const bankPickerVisible = ref(false);
- const bankTab = ref("common");
- const route = useRoute();
- const router = useRouter();
- export interface BankItem {
- code: string;
- shortName: string;
- tabKey: "common" | "A-F" | "G-L" | "M-P" | "Q-U" | "V-Z";
- logoText: string;
- color: string;
- }
- /** 演示数据:常用 + 按首字母分组(与示意图「标签 + 网格」一致) */
- const BANK_LIST: BankItem[] = [
- { code: "ICBC", shortName: "工商银行", tabKey: "common", logoText: "工", color: "#c62828" },
- { code: "ABC", shortName: "农业银行", tabKey: "common", logoText: "农", color: "#2e7d32" },
- { code: "BOC", shortName: "中国银行", tabKey: "common", logoText: "中", color: "#b71c1c" },
- { code: "CCB", shortName: "建设银行", tabKey: "common", logoText: "建", color: "#1565c0" },
- { code: "COMM", shortName: "交通银行", tabKey: "common", logoText: "交", color: "#283593" },
- { code: "PSBC", shortName: "邮储银行", tabKey: "common", logoText: "邮", color: "#00695c" },
- { code: "CMB", shortName: "招商银行", tabKey: "common", logoText: "招", color: "#c62828" },
- { code: "SPDB", shortName: "浦发银行", tabKey: "common", logoText: "浦", color: "#0d47a1" },
- { code: "CIB", shortName: "兴业银行", tabKey: "common", logoText: "兴", color: "#1565c0" },
- { code: "CMBC", shortName: "民生银行", tabKey: "common", logoText: "民", color: "#4e342e" },
- { code: "HXB", shortName: "华夏银行", tabKey: "G-L", logoText: "华", color: "#d84315" },
- { code: "GDB", shortName: "广发银行", tabKey: "G-L", logoText: "广", color: "#c62828" },
- { code: "PAB", shortName: "平安银行", tabKey: "M-P", logoText: "平", color: "#f57f17" },
- { code: "CITIC", shortName: "中信银行", tabKey: "M-P", logoText: "信", color: "#c62828" },
- { code: "CEB", shortName: "光大银行", tabKey: "G-L", logoText: "光", color: "#6a1b9a" },
- { code: "BOSH", shortName: "上海银行", tabKey: "G-L", logoText: "上", color: "#283593" },
- { code: "BRCB", shortName: "北京银行", tabKey: "A-F", logoText: "京", color: "#c62828" },
- { code: "NJCB", shortName: "南京银行", tabKey: "M-P", logoText: "宁", color: "#c62828" },
- { code: "HZCB", shortName: "杭州银行", tabKey: "G-L", logoText: "杭", color: "#1565c0" },
- { code: "ASB", shortName: "鞍山银行", tabKey: "A-F", logoText: "鞍", color: "#37474f" },
- { code: "BSB", shortName: "包商银行", tabKey: "A-F", logoText: "包", color: "#455a64" },
- { code: "QDCCB", shortName: "青岛银行", tabKey: "M-P", logoText: "青", color: "#0d47a1" },
- { code: "WFCB", shortName: "潍坊银行", tabKey: "V-Z", logoText: "潍", color: "#1565c0" },
- { code: "WZCB", shortName: "温州银行", tabKey: "V-Z", logoText: "温", color: "#c62828" },
- { code: "XMBANK", shortName: "厦门银行", tabKey: "Q-U", logoText: "厦", color: "#00695c" },
- { code: "YTBANK", shortName: "烟台银行", tabKey: "V-Z", logoText: "烟", color: "#37474f" },
- { code: "ZZBANK", shortName: "郑州银行", tabKey: "V-Z", logoText: "郑", color: "#c62828" }
- ];
- const form = reactive({
- accountName: "",
- accountType: "BANK_ACCOUNT_TYPE_CORPORATE" as "BANK_ACCOUNT_TYPE_CORPORATE" | "BANK_ACCOUNT_TYPE_PERSONAL",
- bankAccountNo: "",
- bankCode: ""
- });
- const selectedBankLabel = computed(() => {
- if (!form.bankCode) return "";
- const b = BANK_LIST.find(x => x.code === form.bankCode);
- return b ? b.shortName : "";
- });
- const bankAccountTip = computed(() => {
- if (form.accountType === "BANK_ACCOUNT_TYPE_CORPORATE") {
- return "请务必填写与账户户名一致的对公银行账户。";
- }
- return "请务必填写与账户户名一致的个人储蓄卡账号。";
- });
- const tabToKey = computed<BankItem["tabKey"] | "common">(() => {
- const m: Record<string, BankItem["tabKey"] | "common"> = {
- common: "common",
- "A-F": "A-F",
- "G-L": "G-L",
- "M-P": "M-P",
- "Q-U": "Q-U",
- "V-Z": "V-Z"
- };
- return m[bankTab.value] ?? "common";
- });
- const filteredBanks = computed(() => {
- const k = tabToKey.value;
- if (k === "common") return BANK_LIST.filter(b => b.tabKey === "common");
- return BANK_LIST.filter(b => b.tabKey === k);
- });
- const rules: FormRules = {
- accountName: [{ required: true, message: "请输入账户户名", trigger: "blur" }],
- accountType: [{ required: true, message: "请选择账户类型", trigger: "change" }],
- bankAccountNo: [
- { required: true, message: "请输入银行账号", trigger: "blur" },
- { pattern: /^[\d\s-]{8,40}$/, message: "请输入有效的银行账号", trigger: "blur" }
- ],
- bankCode: [{ required: true, message: "请选择开户银行", trigger: "change" }]
- };
- function selectBank(b: BankItem) {
- form.bankCode = b.code;
- bankPickerVisible.value = false;
- formRef.value?.validateField("bankCode").catch(() => {});
- }
- function onBack() {
- router.push({ path: "/businessInfo/industryQualifications", query: { ...route.query } });
- }
- function onSaveDraft() {
- ElMessage.success("草稿已保存(可对接接口)");
- }
- /** 账户户名:subject_info.business_license_info.merchant_name */
- function readMerchantNameFromSubjectInfo(): string {
- const raw = localGet(BUSINESS_DATA_CACHE_KEY) as Record<string, unknown> | null | undefined;
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return "";
- const si = raw.subject_info as Record<string, unknown> | undefined;
- if (!si || typeof si !== "object" || Array.isArray(si)) return "";
- const bli = si.business_license_info as Record<string, unknown> | undefined;
- if (!bli || typeof bli !== "object" || Array.isArray(bli)) return "";
- return String(bli.merchant_name ?? "").trim();
- }
- function resolveBankCodeFromAccountBank(accountBank: string): string {
- const s = accountBank.trim();
- if (!s) return "";
- const byCode = BANK_LIST.find(b => b.code === s);
- if (byCode) return byCode.code;
- const byShort = BANK_LIST.find(b => b.shortName === s);
- if (byShort) return byShort.code;
- return "";
- }
- /** 从缓存 bank_account_info 反显;无户名时用 subject_info 营业执照商户名 */
- function hydrateAccountFormFromBusinessDataCache() {
- const raw = localGet(BUSINESS_DATA_CACHE_KEY) as Record<string, unknown> | null | undefined;
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
- const m = readMerchantNameFromSubjectInfo();
- if (m) form.accountName = m;
- return;
- }
- const ba = raw.bank_account_info as Record<string, unknown> | undefined;
- if (!ba || typeof ba !== "object" || Array.isArray(ba)) {
- const m = readMerchantNameFromSubjectInfo();
- if (m) form.accountName = m;
- return;
- }
- const accName = String(ba.account_name ?? "").trim();
- form.accountName = accName || readMerchantNameFromSubjectInfo();
- const t = String(ba.bank_account_type ?? "").trim();
- if (t === BANK_ACCOUNT_TYPE_CORPORATE || t === BANK_ACCOUNT_TYPE_PERSONAL) {
- form.accountType = t;
- }
- form.bankAccountNo = String(ba.account_number ?? "").trim();
- const bankLabel = String(ba.account_bank ?? "").trim();
- form.bankCode = resolveBankCodeFromAccountBank(bankLabel);
- }
- onMounted(() => {
- hydrateAccountFormFromBusinessDataCache();
- nextTick(() => formRef.value?.clearValidate());
- });
- function mergeBankAccountIntoBusinessDataCache() {
- const prev = localGet(BUSINESS_DATA_CACHE_KEY);
- const base = prev && typeof prev === "object" && !Array.isArray(prev) ? { ...(prev as Record<string, unknown>) } : {};
- const prevBa =
- base.bank_account_info && typeof base.bank_account_info === "object" && !Array.isArray(base.bank_account_info)
- ? { ...(base.bank_account_info as Record<string, unknown>) }
- : {};
- const accountBankName = selectedBankLabel.value || String(form.bankCode || "").trim();
- base.bank_account_info = {
- ...prevBa,
- bank_account_type: form.accountType,
- account_bank: accountBankName,
- account_number: String(form.bankAccountNo || "")
- .replace(/\s+/g, "")
- .trim(),
- account_name: String(form.accountName || "").trim()
- };
- localSet(BUSINESS_DATA_CACHE_KEY, base);
- }
- async function onSubmit() {
- if (!formRef.value) return;
- submitLoading.value = true;
- try {
- await formRef.value.validate();
- mergeBankAccountIntoBusinessDataCache();
- await router.push({
- path: "/businessInfo/adminInfo",
- query: { ...route.query }
- });
- } catch {
- ElMessage.warning("请完善必填信息");
- } finally {
- submitLoading.value = false;
- }
- }
- watch(
- () => form.accountType,
- () => {
- formRef.value?.validateField("bankAccountNo").catch(() => {});
- }
- );
- </script>
- <style scoped lang="scss">
- $wechat-green: #07c160;
- .account-info-page {
- box-sizing: border-box;
- min-height: 100%;
- padding: 24px 32px 40px;
- margin: 0 auto;
- background: #ffffff;
- }
- .page-title {
- margin: 0 0 24px;
- font-size: 20px;
- font-weight: 600;
- color: #303133;
- }
- .form-block {
- margin-bottom: 24px;
- }
- .section-head {
- display: flex;
- align-items: center;
- margin: 0 0 20px;
- font-size: 16px;
- font-weight: 600;
- color: #303133;
- }
- .section-bar {
- display: inline-block;
- flex-shrink: 0;
- width: 4px;
- height: 16px;
- margin-right: 8px;
- background: $wechat-green;
- border-radius: 2px;
- }
- .account-form {
- :deep(.el-form-item__label) {
- font-weight: 500;
- color: #606266;
- }
- }
- .field-stack {
- width: 100%;
- }
- .field-tip {
- margin: 8px 0 0;
- font-size: 12px;
- line-height: 1.65;
- color: #909399;
- }
- .account-type-radio {
- :deep(.el-radio__input.is-checked .el-radio__inner) {
- background: $wechat-green;
- border-color: $wechat-green;
- }
- :deep(.el-radio__input.is-checked + .el-radio__label) {
- color: #303133;
- }
- }
- .bank-trigger-input {
- cursor: pointer;
- &.is-focus-like {
- :deep(.el-input__wrapper) {
- box-shadow: 0 0 0 1px $wechat-green inset;
- }
- }
- }
- .footer-actions {
- display: flex;
- align-items: center;
- justify-content: center;
- padding-top: 24px;
- margin-top: 16px;
- border-top: 1px solid #ebeef5;
- }
- .footer-center {
- display: flex;
- flex: 1;
- justify-content: center;
- }
- .btn-next {
- min-width: 100px;
- background: $wechat-green;
- border-color: $wechat-green;
- &:hover {
- background: #06ad56;
- border-color: #06ad56;
- }
- }
- </style>
- <style lang="scss">
- $wechat-green: #07c160;
- .bank-picker-popper {
- padding: 0 !important;
- }
- .bank-picker {
- .bank-tab-group {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- padding: 8px 4px 4px;
- :deep(.el-radio-button__inner) {
- padding: 6px 10px;
- font-size: 12px;
- }
- :deep(.el-radio-button.is-active .el-radio-button__inner) {
- color: #ffffff;
- background: $wechat-green;
- border-color: $wechat-green;
- box-shadow: -1px 0 0 0 $wechat-green;
- }
- }
- .bank-grid-wrap {
- max-height: 280px;
- padding: 8px 4px 12px;
- overflow: auto;
- }
- .bank-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 8px 12px;
- }
- .bank-cell {
- display: flex;
- gap: 8px;
- align-items: center;
- padding: 8px 10px;
- margin: 0;
- text-align: left;
- cursor: pointer;
- background: #ffffff;
- border: 1px solid transparent;
- border-radius: 6px;
- transition: background 0.15s;
- &:hover {
- background: #f5f7fa;
- }
- &.is-active {
- background: #e8f5e9;
- border-color: $wechat-green;
- }
- }
- .bank-logo {
- display: inline-flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- font-size: 12px;
- font-weight: 600;
- color: #ffffff;
- border-radius: 4px;
- }
- .bank-name {
- font-size: 13px;
- line-height: 1.3;
- color: #303133;
- }
- }
- </style>
|