feat(login): implement mobile SMS login flow

Add utility function for form URL encoded data serialization, update the oauthToken API to use proper form data and basic authentication, add sendCode API for sending mobile verification codes, and rewrite the phone login flow with input validation and correct OAuth parameters.
This commit is contained in:
DEV_DSW
2026-06-02 16:55:53 +08:00
parent 0c94c8f798
commit 4ef1113c2c
2 changed files with 56 additions and 12 deletions

View File

@@ -6,23 +6,51 @@ export interface OauthTokenRequest {
grant_type?: string;
openIdCode?: string[];
scope?: string;
mobile?: string;
code?: string;
[property: string]: any;
}
export function oauthToken(args: OauthTokenRequest) {
export function buildFormUrlEncodedParams(
data: Record<string, unknown>,
): URLSearchParams {
const params = new URLSearchParams();
Object.entries(data).forEach(([key, value]) => {
if (value === undefined || value === null) return;
if (Array.isArray(value)) {
value.forEach((item) => {
if (item === undefined || item === null) return;
params.append(key, String(item));
});
return;
}
params.append(key, String(value));
});
return params;
}
export function oauthToken(data: OauthTokenRequest) {
const params = buildFormUrlEncodedParams(data as Record<string, unknown>);
return request({
url: "/auth/oauth2/token",
method: "post",
data: args,
data: params,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "Basic Y3VzdG9tOmN1c3RvbQ==",
},
});
}
// 绑定用户手机号
export function bindUserPhone(args: any) {
// 发送手机验证码
export function sendCode(mobile: string) {
const value = (mobile ?? "").trim();
const encoded = encodeURIComponent(value);
return request({
url: "/hotelBiz/user/bindUserPhone",
method: "post",
data: args,
url: `/admin/mobile/${encoded}`,
method: "get",
});
}

View File

@@ -79,7 +79,8 @@
import { computed, nextTick, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { oauthToken } from "@/api/login";
import { showToast } from "vant";
import { oauthToken, sendCode } from "@/api/login";
import { COUNTRY_CALLING_CODES, findCountryCallingCode } from "@/constants/countryCallingCodes";
import { getCurrentLocale, setLocale } from "@/i18n";
import { Globe } from '@lucide/vue'
@@ -148,8 +149,20 @@ function handleSelectCountry(item: { name: string; iso2: string; dialCode: strin
countrySearch.value = "";
}
function handleSendCode() {
showToast(t("common.login.tips.smsApiMissing"));
async function handleSendCode() {
const phoneDigits = phone.value.replace(/\D/g, "");
if (!phoneDigits) {
showToast(t("common.login.errors.missingPhone"));
return;
}
try {
await sendCode(`${selectedCountry.value.dialCode}${phoneDigits}`);
} catch (e: unknown) {
console.error(e);
showToast(t("common.errors.network"));
}
}
async function waitForGoogleButtonContainer(): Promise<HTMLElement> {
@@ -197,11 +210,14 @@ async function handlePhoneLogin() {
}
phoneSubmitting.value = true;
try {
await oauthToken({
clientId: import.meta.env.VITE_CLIENT_ID,
openIdCode: [`${selectedCountry.value.dialCode}${phoneDigits}`, codeValue],
grant_type: "sms",
scope: "server",
mobile: phoneDigits,
code: codeValue,
grant_type: "mobile",
});
router.go(-1);
} catch (e: unknown) {