fix: allow optional digital RMB code and inline payment password setup

This commit is contained in:
richjohnrambo
2026-10-15 23:03:59 +08:00
parent 6e223e537b
commit 66121f48e9
8 changed files with 184 additions and 36 deletions
+10
View File
@@ -2744,6 +2744,16 @@ body {
padding: 0 12px;
outline: 0;
}
.m-withdraw-password-notice {
margin: 16px 0;
color: var(--m-muted);
line-height: 1.5;
text-align: center;
}
.m-withdraw-confirm .m-withdraw-setup-btn,
.m-withdraw-confirm .m-withdraw-back-btn {
margin: 8px 0 14px;
}
.m-finance-records {
padding-inline: 0;
+21 -11
View File
@@ -65,7 +65,7 @@
v-model.trim="bank.digital_rmb_code"
inputmode="numeric"
maxlength="32"
placeholder="请输入数字编码"
placeholder="请输入数字编码(选填)"
/></label>
<section class="m-qr-field">
<h3>微信二维码</h3>
@@ -276,16 +276,26 @@
}
async function saveBank() {
error.value = '';
if (
!bank.holder_name ||
!/^[1-9]\d{14}(?:\d{2}[\dXx])?$/.test(bank.identity_no.replace(/[ -]/g, '')) ||
!/^[0-9 -]{8,36}$/.test(bank.account_no) ||
!bank.bank_name ||
!/^\d{6,32}$/.test(bank.digital_rmb_code.replace(/[ -]/g, '')) ||
!bank.wechat_qr_attachment_id ||
!bank.alipay_qr_attachment_id
) {
error.value = '请完整填写绑卡信息并上传微信、支付宝二维码';
const identityNo = bank.identity_no.replace(/[ -]/g, '');
const accountNo = bank.account_no.replace(/[ -]/g, '');
const digitalRMBCode = bank.digital_rmb_code.replace(/[ -]/g, '');
const validationError =
bank.holder_name.length < 2
? '请输入至少 2 个字符的姓名'
: !/^[1-9]\d{14}(?:\d{2}[\dXx])?$/.test(identityNo)
? '请输入正确的 15 位或 18 位身份证号'
: !/^\d{8,32}$/.test(accountNo)
? '请输入 8 至 32 位银行卡号'
: bank.bank_name.length < 2
? '请输入开户行'
: digitalRMBCode && !/^\d{6,32}$/.test(digitalRMBCode)
? '数字人民币编码须为 6 至 32 位数字,也可留空'
: !bank.wechat_qr_attachment_id || !bank.alipay_qr_attachment_id
? '请上传微信和支付宝二维码'
: '';
if (validationError) {
error.value = validationError;
window.$message?.error(validationError);
return;
}
submitting.value = true;
+83 -5
View File
@@ -47,12 +47,39 @@
:trap-focus="false"
@after-enter="focusTradePassword"
>
<form class="m-withdraw-confirm m-card" @submit.prevent="submitWithdrawal">
<form v-if="showTradePasswordSetup" class="m-withdraw-confirm m-card" @submit.prevent="setTradePassword">
<h2>设置支付密码</h2>
<p class="m-withdraw-password-notice">设置完成后,请返回确认申请。</p>
<label>
<span>新支付密码</span>
<input
v-model="setupForm.newTradePassword"
type="password"
inputmode="numeric"
maxlength="6"
autocomplete="new-password"
placeholder="请输入 6 位数字"
/>
</label>
<label>
<span>登录密码确认</span>
<input
v-model="setupForm.loginPassword"
type="password"
autocomplete="current-password"
placeholder="请输入登录密码"
/>
</label>
<p v-if="setupError" class="m-form-error">{{ setupError }}</p>
<button class="m-primary-btn" :disabled="submitting">{{ submitting ? '设置中...' : '确认设置' }}</button>
<button class="m-outline-btn m-withdraw-back-btn" type="button" @click="showTradePasswordSetup = false">返回确认申请</button>
</form>
<form v-else class="m-withdraw-confirm m-card" @submit.prevent="submitWithdrawal">
<h2>确认取出</h2>
<div><span>申请金额</span><b>{{ money(quote?.request_amount) }}</b></div>
<div><span>手续费</span><b>{{ money(quote?.fee_amount) }}</b></div>
<div><span>预计到账</span><strong>{{ money(quote?.actual_amount) }}</strong></div>
<label>
<label v-if="hasTradePassword">
<span>支付密码</span>
<input
ref="tradePasswordInput"
@@ -65,8 +92,10 @@
placeholder="请输入 6 位支付密码"
/>
</label>
<p v-else class="m-withdraw-password-notice">尚未设置支付密码,请先完成设置。</p>
<button v-if="!hasTradePassword" class="m-outline-btn m-withdraw-setup-btn" type="button" @click="openTradePasswordSetup">去设置支付密码</button>
<p v-if="actionError" class="m-form-error">{{ actionError }}</p>
<button class="m-primary-btn" :disabled="submitting">确认申请</button>
<button class="m-primary-btn" :disabled="submitting || !hasTradePassword">确认申请</button>
</form>
</n-modal>
</section>
@@ -78,7 +107,7 @@
import { ChevronForwardOutline } from '@vicons/ionicons5';
import PageHeader from '../components/PageHeader.vue';
import AsyncState from '../components/AsyncState.vue';
import { errorText, idempotencyKey, money, request } from '../api';
import { MobileApiError, errorText, idempotencyKey, money, request } from '../api';
import { mobileAssets } from '../assets';
const router = useRouter();
@@ -90,9 +119,13 @@
const accounts = ref<any[]>([]);
const quote = ref<any>();
const showConfirm = ref(false);
const showTradePasswordSetup = ref(false);
const setupError = ref('');
const withdrawalKey = ref('');
const tradePasswordInput = ref<HTMLInputElement>();
const form = reactive({ paymentAccountId: 0, amountYuan: '', tradePassword: '' });
const setupForm = reactive({ newTradePassword: '', loginPassword: '' });
const hasTradePassword = computed(() => me.value?.has_trade_password === true);
const selectedAccount = computed(() =>
accounts.value.find((item) => item.id === form.paymentAccountId)
);
@@ -112,7 +145,44 @@
}
async function focusTradePassword() {
await nextTick();
tradePasswordInput.value?.focus();
if (hasTradePassword.value && !showTradePasswordSetup.value) tradePasswordInput.value?.focus();
}
function openTradePasswordSetup() {
setupError.value = '';
actionError.value = '';
setupForm.newTradePassword = '';
setupForm.loginPassword = '';
showTradePasswordSetup.value = true;
}
async function setTradePassword() {
if (submitting.value) return;
setupError.value = '';
if (!/^\d{6}$/.test(setupForm.newTradePassword) || setupForm.loginPassword.length < 6) {
setupError.value = '请输入 6 位数字支付密码及登录密码';
return;
}
submitting.value = true;
try {
await request({
url: '/v1/me/trade-password',
method: 'PUT',
data: {
new_trade_password: setupForm.newTradePassword,
login_password: setupForm.loginPassword,
},
});
me.value = { ...me.value, has_trade_password: true };
setupForm.newTradePassword = '';
setupForm.loginPassword = '';
form.tradePassword = '';
showTradePasswordSetup.value = false;
window.$message?.success('支付密码设置成功,请确认申请');
await focusTradePassword();
} catch (err) {
setupError.value = errorText(err);
} finally {
submitting.value = false;
}
}
async function load() {
loading.value = true;
@@ -152,6 +222,7 @@
});
withdrawalKey.value = idempotencyKey();
form.tradePassword = '';
showTradePasswordSetup.value = false;
showConfirm.value = true;
} catch (err) {
actionError.value = errorText(err);
@@ -161,6 +232,10 @@
}
async function submitWithdrawal() {
if (!quote.value || submitting.value) return;
if (!hasTradePassword.value) {
actionError.value = '请先设置支付密码';
return;
}
if (!/^\d{6}$/.test(form.tradePassword)) {
actionError.value = '请输入 6 位支付密码';
return;
@@ -184,6 +259,9 @@
await router.replace('/app/profile/records?tab=withdrawal');
} catch (err) {
actionError.value = errorText(err);
if (err instanceof MobileApiError && err.reason === 'TRADE_PASSWORD_NOT_SET') {
me.value = { ...me.value, has_trade_password: false };
}
} finally {
submitting.value = false;
}