feat: 完成前端业务闭环与后端联调

This commit is contained in:
2026-08-24 23:37:56 +08:00
parent c59e36f933
commit 9ad572907b
175 changed files with 10711 additions and 1740 deletions
+43 -12
View File
@@ -16,6 +16,22 @@
><text>完成安全验证和短信校验后即可更新你的登录手机号</text></view
>
<view class="form-panel">
<view class="field-block">
<view class="form-row">
<text>当前密码</text>
<input
v-model="currentPassword"
password
maxlength="32"
aria-label="当前密码"
:aria-invalid="Boolean(errors.currentPassword)"
:aria-describedby="errors.currentPassword ? 'change-phone-password-error' : undefined"
placeholder="请输入当前登录密码"
@input="errors.currentPassword = ''"
/>
</view>
<text v-if="errors.currentPassword" id="change-phone-password-error" class="field-error" role="alert">{{ errors.currentPassword }}</text>
</view>
<view class="field-block">
<view class="form-row">
<text>新手机号</text>
@@ -24,11 +40,13 @@
type="number"
maxlength="11"
aria-label="新手机号"
:aria-invalid="Boolean(errors.phone)"
:aria-describedby="errors.phone ? 'change-phone-error' : undefined"
placeholder="请输入新手机号"
@input="handlePhoneInput"
/>
</view>
<text v-if="errors.phone" class="field-error">{{ errors.phone }}</text>
<text v-if="errors.phone" id="change-phone-error" class="field-error" role="alert">{{ errors.phone }}</text>
</view>
<view class="field-block">
<view class="form-row form-row--code">
@@ -38,6 +56,8 @@
type="number"
maxlength="4"
aria-label="短信验证码"
:aria-invalid="Boolean(errors.smsCode)"
:aria-describedby="errors.smsCode ? 'change-phone-code-error' : undefined"
placeholder="4 位验证码"
@input="errors.smsCode = ''"
/>
@@ -48,7 +68,7 @@
@click="prepareGetCode"
>{{ cooldownSeconds > 0 ? `${cooldownSeconds}s 后重试` : '获取验证码' }}</button>
</view>
<text v-if="errors.smsCode" class="field-error">{{ errors.smsCode }}</text>
<text v-if="errors.smsCode" id="change-phone-code-error" class="field-error" role="alert">{{ errors.smsCode }}</text>
</view>
</view>
<AppButton
@@ -104,16 +124,19 @@ import {
} from "@/utils/auth/verification.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import { createNonIdempotentWriteGuard } from "@/utils/non-idempotent-write-guard.js";
import { handleBackPress, runBackGuard } from "@/utils/navigation/gateway.js";
import { goRoot, handleBackPress, runBackGuard } from "@/utils/navigation/gateway.js";
import { calcMD5 } from "@/utils/md5.js";
import { session } from "@/utils/session.js";
const currentPassword = ref("");
const phone = ref("");
const smsCode = ref("");
const errors = reactive({ phone: "", smsCode: "" });
const errors = reactive({ currentPassword: "", phone: "", smsCode: "" });
const submittingPhoneChange = ref(false);
const discardVisible = ref(false);
const toastVisible = ref(false);
const toastMessage = ref("");
const formSnapshot = computed(() => JSON.stringify({ phone: phone.value, smsCode: smsCode.value }));
const formSnapshot = computed(() => JSON.stringify({ currentPassword: currentPassword.value, phone: phone.value, smsCode: smsCode.value }));
const baseline = ref(formSnapshot.value);
const isDirty = computed(() => formSnapshot.value !== baseline.value);
const phoneChangeController = createRequestController();
@@ -185,19 +208,24 @@ const prepareGetCode = async () => {
};
const validateForm = () => {
errors.currentPassword = currentPassword.value ? "" : "请输入当前密码";
errors.phone = isAuthPhone(phone.value) ? "" : "请输入正确手机号";
errors.smsCode =
sentPhone.value !== phone.value
? "请先获取当前手机号的验证码"
? "请先获取手机号的验证码"
: /^\d{4}$/.test(smsCode.value)
? ""
: "请输入 4 位验证码";
return !errors.phone && !errors.smsCode;
return !errors.currentPassword && !errors.phone && !errors.smsCode;
};
const submitPhoneChange = async () => {
if (phoneState.value !== "ready" || !validateForm()) return;
const phoneChangePayload = { phone: phone.value, smsCode: smsCode.value };
const phoneChangePayload = {
phone: phone.value,
smsCode: smsCode.value,
currentPasswordHash: calcMD5(currentPassword.value),
};
const phoneChangeAttempt = phoneChangeGuard.begin(phoneChangePayload);
if (phoneChangeAttempt === null) {
showToast("上次换绑结果暂时无法确认,请重新登录确认手机号,不要重复提交");
@@ -210,11 +238,14 @@ const submitPhoneChange = async () => {
{ requestController: phoneChangeController },
);
if (!isPageActive) return;
session.clear();
await goRoot("A01");
if (!isPageActive) return;
currentPassword.value = "";
phone.value = "";
smsCode.value = "";
sentPhone.value = "";
baseline.value = formSnapshot.value;
showToast("手机号换绑成功");
} catch (error) {
if (!isPageActive) return;
if (phoneChangeGuard.recordFailure(phoneChangeAttempt, error)) {
@@ -262,7 +293,7 @@ onUnload(() => {
}
.page-content {
flex: 1;
padding: 28rpx 30rpx 72rpx;
padding: 28rpx 30rpx calc(72rpx + env(safe-area-inset-bottom));
}
.security-tip,
.form-panel {
@@ -314,7 +345,7 @@ onUnload(() => {
.form-row input {
width: auto;
min-width: 0;
min-height: 68rpx;
min-height: 80rpx;
color: $ink;
font-size: clamp(14px, 23rpx, 17px);
}
@@ -324,7 +355,7 @@ onUnload(() => {
justify-content: center;
justify-self: end;
width: 198rpx;
min-height: 68rpx;
min-height: 80rpx;
margin: 0;
padding: 0 14rpx;
box-sizing: border-box;