From 181b8902e15ef9ae6e11e44804ca7f29a4a9321c Mon Sep 17 00:00:00 2001 From: bestlee Date: Thu, 13 Aug 2026 13:34:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=A0=BC=E5=BC=8F=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 - packages/alova/package.json | 22 ++ packages/alova/src/client.ts | 1 + packages/alova/src/constant.ts | 2 + packages/alova/src/fetch.ts | 2 + packages/alova/src/index.ts | 77 ++++ packages/alova/src/mock.ts | 1 + packages/alova/src/type.ts | 52 +++ packages/alova/tsconfig.json | 19 + pnpm-lock.yaml | 9 - src/constants/app.ts | 4 - src/hooks/business/captcha.ts | 71 ---- .../global-header/components/user-avatar.vue | 17 +- src/locales/langs/en-us.ts | 86 +---- src/locales/langs/zh-cn.ts | 11 - src/router/elegant/imports.ts | 1 - src/router/elegant/routes.ts | 10 - src/router/elegant/transform.ts | 3 +- src/router/routes/index.ts | 2 +- src/store/modules/auth/index.ts | 5 +- src/typings/app.d.ts | 334 ------------------ src/typings/elegant-router.d.ts | 3 - src/views/home/index/index.vue | 5 +- src/views/home/initiateChange/index.vue | 24 +- src/views/home/modules/FlowRing.vue | 5 +- src/views/login/index.vue | 20 +- src/views/user-center/index.vue | 7 - 27 files changed, 220 insertions(+), 579 deletions(-) create mode 100644 packages/alova/package.json create mode 100644 packages/alova/src/client.ts create mode 100644 packages/alova/src/constant.ts create mode 100644 packages/alova/src/fetch.ts create mode 100644 packages/alova/src/index.ts create mode 100644 packages/alova/src/mock.ts create mode 100644 packages/alova/src/type.ts create mode 100644 packages/alova/tsconfig.json delete mode 100644 src/hooks/business/captcha.ts delete mode 100644 src/views/user-center/index.vue diff --git a/package.json b/package.json index a3699a7..66dba48 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "fmt": "oxfmt", "gen-route": "sa gen-route", "lint": "oxlint --fix && eslint --fix .", - "prepare": "simple-git-hooks", "preview": "vite preview", "release": "sa release", "typecheck": "vue-tsc --noEmit --skipLibCheck", @@ -108,7 +107,6 @@ "oxlint": "^1.64.0", "pro-naive-ui-resolver": "1.0.2", "sass": "1.99.0", - "simple-git-hooks": "2.13.1", "tsx": "4.21.0", "typescript": "6.0.3", "unocss": "^66.6.8", @@ -122,10 +120,6 @@ "vue-eslint-parser": "10.4.0", "vue-tsc": "3.2.8" }, - "simple-git-hooks": { - "commit-msg": "pnpm sa git-commit-verify", - "pre-commit": "pnpm typecheck && pnpm lint && pnpm fmt && git diff --exit-code" - }, "engines": { "node": ">=20.19.0", "pnpm": ">=10.5.0" diff --git a/packages/alova/package.json b/packages/alova/package.json new file mode 100644 index 0000000..0a9eb1c --- /dev/null +++ b/packages/alova/package.json @@ -0,0 +1,22 @@ +{ + "name": "@sa/alova", + "version": "2.2.0", + "typesVersions": { + "*": { + "*": [ + "./src/*" + ] + } + }, + "exports": { + ".": "./src/index.ts", + "./fetch": "./src/fetch.ts", + "./client": "./src/client.ts", + "./mock": "./src/mock.ts" + }, + "dependencies": { + "@alova/mock": "2.0.20", + "@sa/utils": "workspace:*", + "alova": "3.5.1" + } +} diff --git a/packages/alova/src/client.ts b/packages/alova/src/client.ts new file mode 100644 index 0000000..0d76ebb --- /dev/null +++ b/packages/alova/src/client.ts @@ -0,0 +1 @@ +export * from 'alova/client'; diff --git a/packages/alova/src/constant.ts b/packages/alova/src/constant.ts new file mode 100644 index 0000000..be5c43c --- /dev/null +++ b/packages/alova/src/constant.ts @@ -0,0 +1,2 @@ +/** the backend error code key */ +export const BACKEND_ERROR_CODE = 'BACKEND_ERROR'; diff --git a/packages/alova/src/fetch.ts b/packages/alova/src/fetch.ts new file mode 100644 index 0000000..8511ce4 --- /dev/null +++ b/packages/alova/src/fetch.ts @@ -0,0 +1,2 @@ +import adapterFetch from 'alova/fetch'; +export default adapterFetch; diff --git a/packages/alova/src/index.ts b/packages/alova/src/index.ts new file mode 100644 index 0000000..4264253 --- /dev/null +++ b/packages/alova/src/index.ts @@ -0,0 +1,77 @@ +import { createAlova } from 'alova'; +import type { AlovaDefaultCacheAdapter, AlovaGenerics, AlovaGlobalCacheAdapter, AlovaRequestAdapter } from 'alova'; +import VueHook from 'alova/vue'; +import type { VueHookType } from 'alova/vue'; +import adapterFetch from 'alova/fetch'; +import { createServerTokenAuthentication } from 'alova/client'; +import type { FetchRequestInit } from 'alova/fetch'; +import { BACKEND_ERROR_CODE } from './constant'; +import type { CustomAlovaConfig, RequestOptions } from './type'; + +export const createAlovaRequest = < + RequestConfig = FetchRequestInit, + ResponseType = Response, + ResponseHeader = Headers, + L1Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter, + L2Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter +>( + customConfig: CustomAlovaConfig< + AlovaGenerics + >, + options: RequestOptions> +) => { + const { tokenRefresher } = options; + const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication< + VueHookType, + AlovaRequestAdapter + >({ + refreshTokenOnSuccess: { + isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false, + handler: async (response, method) => tokenRefresher?.handler(response, method) + }, + refreshTokenOnError: { + isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false, + handler: async (response, method) => tokenRefresher?.handler(response, method) + } + }); + + const instance = createAlova({ + ...customConfig, + timeout: customConfig.timeout ?? 10 * 1000, + requestAdapter: (customConfig.requestAdapter as any) ?? adapterFetch(), + statesHook: VueHook, + beforeRequest: onAuthRequired(options.onRequest as any), + responded: onResponseRefreshToken({ + onSuccess: async (response, method) => { + // check if http status is success + let error: any = null; + let transformedData: any = null; + try { + if (await options.isBackendSuccess(response)) { + transformedData = await options.transformBackendResponse(response); + } else { + error = new Error('the backend request error'); + error.code = BACKEND_ERROR_CODE; + } + } catch (err) { + error = err; + } + + if (error) { + await options.onError?.(error, response, method); + throw error; + } + + return transformedData; + }, + onComplete: options.onComplete, + onError: (error, method) => options.onError?.(error, null, method) + }) + }); + + return instance; +}; + +export { BACKEND_ERROR_CODE }; +export type * from './type'; +export type * from 'alova'; diff --git a/packages/alova/src/mock.ts b/packages/alova/src/mock.ts new file mode 100644 index 0000000..f3aaf08 --- /dev/null +++ b/packages/alova/src/mock.ts @@ -0,0 +1 @@ +export * from '@alova/mock'; diff --git a/packages/alova/src/type.ts b/packages/alova/src/type.ts new file mode 100644 index 0000000..27b84b2 --- /dev/null +++ b/packages/alova/src/type.ts @@ -0,0 +1,52 @@ +import type { AlovaGenerics, AlovaOptions, AlovaRequestAdapter, Method, ResponseCompleteHandler } from 'alova'; + +export type CustomAlovaConfig = Omit< + AlovaOptions, + 'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter' +> & { + /** request adapter. all request of alova will be sent by it. */ + requestAdapter?: AlovaRequestAdapter; +}; + +export interface RequestOptions { + /** + * The hook before request + * + * For example: You can add header token in this hook + * + * @param method alova Method Instance + */ + onRequest?: AlovaOptions['beforeRequest']; + /** + * The hook to check backend response is success or not + * + * @param response alova response + */ + isBackendSuccess: (response: AG['Response']) => Promise; + + /** The config to refresh token */ + tokenRefresher?: { + /** detect the token is expired */ + isExpired(response: AG['Response'], Method: Method): Promise | boolean; + /** refresh token handler */ + handler(response: AG['Response'], Method: Method): Promise; + }; + + /** The hook after backend request complete */ + onComplete?: ResponseCompleteHandler; + + /** + * The hook to handle error + * + * For example: You can show error message in this hook + * + * @param error + */ + onError?: (error: any, response: AG['Response'] | null, methodInstance: Method) => any | Promise; + /** + * transform backend response when the responseType is json + * + * @param response alova response + */ + transformBackendResponse: (response: AG['Response']) => any; +} diff --git a/packages/alova/tsconfig.json b/packages/alova/tsconfig.json new file mode 100644 index 0000000..97b95af --- /dev/null +++ b/packages/alova/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "jsx": "preserve", + "lib": ["DOM", "ESNext"], + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "types": ["node"], + "strict": true, + "strictNullChecks": true, + "noUnusedLocals": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1788d8a..37c09e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,9 +195,6 @@ importers: sass: specifier: 1.99.0 version: 1.99.0 - simple-git-hooks: - specifier: 2.13.1 - version: 2.13.1 tsx: specifier: 4.21.0 version: 4.21.0 @@ -4705,10 +4702,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-git-hooks@2.13.1: - resolution: {integrity: sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==} - hasBin: true - simple-statistics@6.1.0: resolution: {integrity: sha512-Vi/xPPuiEIizXOCDx3N8LjcIqC7Z8euiIfvmj5oYimm9/KmXNKeK4aHiVMeE9q7e9wfg1i9YRoWnUNURq54ZFg==} @@ -10139,8 +10132,6 @@ snapshots: signal-exit@4.1.0: {} - simple-git-hooks@2.13.1: {} - simple-statistics@6.1.0: {} simple-statistics@7.8.9: {} diff --git a/src/constants/app.ts b/src/constants/app.ts index 8342fbb..244f15a 100644 --- a/src/constants/app.ts +++ b/src/constants/app.ts @@ -12,10 +12,6 @@ export const themeSchemaRecord: Record = export const themeSchemaOptions = transformRecordToOption(themeSchemaRecord); -export const loginModuleRecord: Record = { - 'pwd-login': 'page.login.pwdLogin.title', -}; - export const themeLayoutModeRecord: Record = { vertical: 'theme.layout.layoutMode.vertical', 'vertical-mix': 'theme.layout.layoutMode.vertical-mix', diff --git a/src/hooks/business/captcha.ts b/src/hooks/business/captcha.ts deleted file mode 100644 index 98124d9..0000000 --- a/src/hooks/business/captcha.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { computed } from 'vue'; -import { useCountDown, useLoading } from '@sa/hooks'; -import { REG_PHONE } from '@/constants/reg'; -import { $t } from '@/locales'; - -export function useCaptcha() { - const { loading, startLoading, endLoading } = useLoading(); - const { count, start, stop, isCounting } = useCountDown(10); - - const label = computed(() => { - let text = $t('page.login.codeLogin.getCode'); - - const countingLabel = $t('page.login.codeLogin.reGetCode', { time: count.value }); - - if (loading.value) { - text = ''; - } - - if (isCounting.value) { - text = countingLabel; - } - - return text; - }); - - function isPhoneValid(phone: string) { - if (phone.trim() === '') { - window.$message?.error?.($t('form.phone.required')); - - return false; - } - - if (!REG_PHONE.test(phone)) { - window.$message?.error?.($t('form.phone.invalid')); - - return false; - } - - return true; - } - - async function getCaptcha(phone: string) { - const valid = isPhoneValid(phone); - - if (!valid || loading.value) { - return; - } - - startLoading(); - - // request - await new Promise(resolve => { - setTimeout(resolve, 500); - }); - - window.$message?.success?.($t('page.login.codeLogin.sendCodeSuccess')); - - start(); - - endLoading(); - } - - return { - label, - start, - stop, - isCounting, - loading, - getCaptcha - }; -} diff --git a/src/layouts/modules/global-header/components/user-avatar.vue b/src/layouts/modules/global-header/components/user-avatar.vue index 268c9f6..8c8e39d 100644 --- a/src/layouts/modules/global-header/components/user-avatar.vue +++ b/src/layouts/modules/global-header/components/user-avatar.vue @@ -4,7 +4,6 @@ import type { VNode } from 'vue'; import { useAuthStore } from '@/store/modules/auth'; import { useRouterPush } from '@/hooks/common/router'; import { useSvgIcon } from '@/hooks/common/icon'; -import { $t } from '@/locales'; defineOptions({ name: 'UserAvatar' @@ -34,7 +33,7 @@ type DropdownOption = const options = computed(() => { const opts: DropdownOption[] = [ { - label: $t('common.userCenter'), + label: '个人中心', key: 'user-center', icon: SvgIconVNode({ icon: 'ph:user-circle', fontSize: 18 }) }, @@ -43,7 +42,7 @@ const options = computed(() => { key: 'divider' }, { - label: $t('common.logout'), + label: '退出登录', key: 'logout', icon: SvgIconVNode({ icon: 'ph:sign-out', fontSize: 18 }) } @@ -54,10 +53,10 @@ const options = computed(() => { function logout() { window.$dialog?.info({ - title: $t('common.tip'), - content: $t('common.logoutConfirm'), - positiveText: $t('common.confirm'), - negativeText: $t('common.cancel'), + title: '提示', + content: '确定退出登录吗?', + positiveText: '确定', + negativeText: '取消', onPositiveClick: () => { authStore.resetStore(); } @@ -69,14 +68,14 @@ function handleDropdown(key: DropdownKey) { logout(); } else { // If your other options are jumps from other routes, they will be directly supported here - routerPushByKey(key); + // routerPushByKey(key); } }