From b5be5b44486beb02bfb84ed319e4a7015109c78e Mon Sep 17 00:00:00 2001 From: skytek_xinliang Date: Mon, 25 May 2026 13:55:47 +0800 Subject: [PATCH] feat(auth): support JSON and form-data login requests Split the auth login API into format-specific methods and add request format selection in the auth store. Build a shared login request body so captcha fields can be sent consistently as either JSON or FormData.feat(auth): support JSON and form-data login requests Split the auth login API into format-specific methods and add request format selection in the auth store. Build a shared login request body so captcha fields can be sent consistently as either JSON or FormData. --- src/services/modules/auth.ts | 14 ++++++-- src/stores/auth.ts | 65 +++++++++++++++++++++++++++--------- src/types/api.ts | 10 ++++++ 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/services/modules/auth.ts b/src/services/modules/auth.ts index 00ee9b5..36e7538 100644 --- a/src/services/modules/auth.ts +++ b/src/services/modules/auth.ts @@ -1,4 +1,4 @@ -import type { CaptchaResponse } from '@/types/api' +import type { CaptchaResponse, LoginRequestBody } from '@/types/api' import { httpClient } from '../client' export interface RequestOptions { @@ -10,11 +10,19 @@ export const authApi = { getCaptcha: async () => ({ data: await httpClient.get('Auth/get-captcha').json(), }), - login: async (payload: FormData, options?: RequestOptions) => ({ + loginWithFormData: async (payload: FormData, options?: RequestOptions) => ({ data: await httpClient .post('Auth/login', { body: payload, - signal: options?.signal, + signal: options?.signal, + }) + .json(), + }), + loginWithJson: async (payload: LoginRequestBody, options?: RequestOptions) => ({ + data: await httpClient + .post('Auth/login', { + json: payload, + signal: options?.signal, }) .json(), }), diff --git a/src/stores/auth.ts b/src/stores/auth.ts index 1655603..5084eba 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -1,4 +1,4 @@ -import type { LoginPayload, User } from '@/types/api' +import type { LoginPayload, LoginRequestBody, LoginRequestFormat, User } from '@/types/api' import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { normalizeError } from '@/services/error' @@ -6,6 +6,46 @@ import { authApi } from '@/services/modules/auth' import { tokenService } from '@/services/token' import { useMenuStore } from '@/stores/menu' +const defaultLoginRequestFormat: LoginRequestFormat = 'formData' + +interface LoginOptions { + requestFormat?: LoginRequestFormat +} + +function createLoginRequestBody(payload: LoginPayload): LoginRequestBody { + return { + UserID: payload.UserID, + Password: payload.Password, + ...(payload.captcha + ? { + DNTCaptchaInputText: payload.captcha.DNTCaptchaInputText, + DNTCaptchaText: payload.captcha.DNTCaptchaText, + DNTCaptchaToken: payload.captcha.DNTCaptchaToken, + } + : {}), + } +} + +function createLoginFormData(payload: LoginRequestBody) { + const formData = new FormData() + formData.append('UserID', payload.UserID) + formData.append('Password', payload.Password) + + if (payload.DNTCaptchaInputText) { + formData.append('DNTCaptchaInputText', payload.DNTCaptchaInputText) + } + + if (payload.DNTCaptchaText) { + formData.append('DNTCaptchaText', payload.DNTCaptchaText) + } + + if (payload.DNTCaptchaToken) { + formData.append('DNTCaptchaToken', payload.DNTCaptchaToken) + } + + return formData +} + // - 只在 store 管理登入狀態:user/token/loading/error // - Component 不直接呼叫 API,避免狀態散落 // - token 單一來源:透過 tokenService 同步 ref + localStorage @@ -22,26 +62,20 @@ export const useAuthStore = defineStore('auth', () => { const isAuthenticated = computed(() => !!token.value) const roles = computed(() => (user.value?.role ? [user.value.role] : [])) - const login = async (payload: LoginPayload) => { + const login = async (payload: LoginPayload, options: LoginOptions = {}) => { loginController.value?.abort() loginController.value = new AbortController() loading.value = true error.value = null try { - const formData = new FormData() - formData.append('UserID', payload.UserID) - formData.append('Password', payload.Password) - - if (payload.captcha) { - formData.append('DNTCaptchaInputText', payload.captcha.DNTCaptchaInputText) - formData.append('DNTCaptchaText', payload.captcha.DNTCaptchaText) - formData.append('DNTCaptchaToken', payload.captcha.DNTCaptchaToken) - } - - const { data } = await authApi.login(formData, { - signal: loginController.value.signal, - }) + const requestBody = createLoginRequestBody(payload) + const requestFormat = options.requestFormat ?? defaultLoginRequestFormat + const requestOptions = { signal: loginController.value.signal } + const { data } = + requestFormat === 'json' + ? await authApi.loginWithJson(requestBody, requestOptions) + : await authApi.loginWithFormData(createLoginFormData(requestBody), requestOptions) const parseUser = (val: unknown): User | undefined => { if (!val || typeof val !== 'object') return @@ -81,6 +115,7 @@ export const useAuthStore = defineStore('auth', () => { } user.value = result.user ?? null + // 使用者token寫入 tokenService.setToken(result.accessToken) } catch (error_) { const normalizedError = normalizeError(error_) diff --git a/src/types/api.ts b/src/types/api.ts index 9ccb14d..a776f82 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -27,3 +27,13 @@ export interface LoginPayload { Password: string captcha?: LoginCaptchaPayload } + +export interface LoginRequestBody { + UserID: string + Password: string + DNTCaptchaInputText?: string + DNTCaptchaText?: string + DNTCaptchaToken?: string +} + +export type LoginRequestFormat = 'formData' | 'json'