diff --git a/apps/backend-mock/src/modules/mock/mock.controller.ts b/apps/backend-mock/src/modules/mock/mock.controller.ts new file mode 100644 index 00000000..96ff812a --- /dev/null +++ b/apps/backend-mock/src/modules/mock/mock.controller.ts @@ -0,0 +1,23 @@ +import type { Response } from 'express'; + +import { Controller, Get, Query, Res } from '@nestjs/common'; + +@Controller('mock') +export class MockController { + /** + * 用于模拟任意的状态码 + * @param res + */ + @Get('status') + async mockAnyStatus( + @Res() res: Response, + @Query() { status }: { status: string }, + ) { + res.status(Number.parseInt(status, 10)).send({ + code: 1, + data: null, + error: null, + message: `code is ${status}`, + }); + } +} diff --git a/apps/backend-mock/src/modules/mock/mock.module.ts b/apps/backend-mock/src/modules/mock/mock.module.ts index 025ff20f..50151b14 100644 --- a/apps/backend-mock/src/modules/mock/mock.module.ts +++ b/apps/backend-mock/src/modules/mock/mock.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; +import { MockController } from './mock.controller'; import { MockService } from './mock.service'; @Module({ + controllers: [MockController], exports: [MockService], providers: [MockService], }) diff --git a/apps/web-antd/src/apis/modules/index.ts b/apps/web-antd/src/apis/modules/index.ts index c35b99fb..9acc852b 100644 --- a/apps/web-antd/src/apis/modules/index.ts +++ b/apps/web-antd/src/apis/modules/index.ts @@ -1,2 +1,3 @@ export * from './menu'; +export * from './mock'; export * from './user'; diff --git a/apps/web-antd/src/apis/modules/mock.ts b/apps/web-antd/src/apis/modules/mock.ts new file mode 100644 index 00000000..3f2f402c --- /dev/null +++ b/apps/web-antd/src/apis/modules/mock.ts @@ -0,0 +1,10 @@ +import { requestClient } from '#/forward'; + +/** + * 模拟人意状态码 + */ +async function getMockStatus(status: string) { + return requestClient.get('/mock/status', { params: { status } }); +} + +export { getMockStatus }; diff --git a/apps/web-antd/src/forward/request.ts b/apps/web-antd/src/forward/request.ts index a01de8fe..bc9f5648 100644 --- a/apps/web-antd/src/forward/request.ts +++ b/apps/web-antd/src/forward/request.ts @@ -1,23 +1,14 @@ /** * 该文件可自行根据业务逻辑进行调整 */ +import type { HttpResponse } from '@vben-core/request'; -import type { AxiosResponse } from '@vben-core/request'; - -import { RequestClient, isCancelError } from '@vben-core/request'; -import { useCoreAccessStore } from '@vben-core/stores'; +import { preferences } from '@vben-core/preferences'; +import { RequestClient } from '@vben-core/request'; import { message } from 'ant-design-vue'; -interface HttpResponse { - /** - * 0 表示成功 其他表示失败 - * 0 means success, others means fail - */ - code: number; - data: T; - message: string; -} +import { useAccessStore } from '#/store'; /** * 创建请求实例 @@ -29,59 +20,42 @@ function createRequestClient() { // 为每个请求携带 Authorization makeAuthorization: () => { return { - handler: () => { - // 这里不能用 useAccessStore,因为 useAccessStore 会导致循环引用 - const accessStore = useCoreAccessStore(); + // 默认 + key: 'Authorization', + tokenHandler: () => { + const accessStore = useAccessStore(); return { refreshToken: `Bearer ${accessStore.refreshToken}`, token: `Bearer ${accessStore.accessToken}`, }; }, - // 默认 - key: 'Authorization', + unAuthorizedHandler: async () => { + const accessStore = useAccessStore(); + accessStore.setAccessToken(null); + + if (preferences.app.loginExpiredMode === 'modal') { + accessStore.openLoginExpiredModal = true; + } else { + // 退出登录 + await accessStore.logout(); + } + }, }; }, + makeErrorMessage: (msg) => message.error(msg), + }); + client.addResponseInterceptor((response) => { + const { data: responseData, status } = response; + + const { code, data, message: msg } = responseData; + if (status >= 200 && status < 400 && code === 0) { + return data; + } + throw new Error(msg); }); - setupRequestInterceptors(client); return client; } -function setupRequestInterceptors(client: RequestClient) { - client.addResponseInterceptor( - (response: AxiosResponse) => { - const { data: responseData, status } = response; - - const { code, data, message: msg } = responseData; - - if (status >= 200 && status < 400 && code === 0) { - return data; - } else { - message.error(msg); - throw new Error(msg); - } - }, - (error: any) => { - if (isCancelError(error)) { - return Promise.reject(error); - } - - const err: string = error?.toString?.() ?? ''; - let errMsg = ''; - if (err?.includes('Network Error')) { - errMsg = '网络错误。'; - } else if (error?.message?.includes?.('timeout')) { - errMsg = '请求超时。'; - } else { - const data = error?.response?.data; - errMsg = (data?.message || data?.error?.message) ?? ''; - } - - message.error(errMsg); - return Promise.reject(error); - }, - ); -} - const requestClient = createRequestClient(); // 其他配置的请求方法 diff --git a/apps/web-antd/src/layouts/basic.vue b/apps/web-antd/src/layouts/basic.vue index 42cb3ea1..9b6811d9 100644 --- a/apps/web-antd/src/layouts/basic.vue +++ b/apps/web-antd/src/layouts/basic.vue @@ -6,11 +6,11 @@ import { LOGIN_PATH } from '@vben/constants'; import { IcRoundCreditScore, MdiDriveDocument, MdiGithub } from '@vben/icons'; import { BasicLayout, - LoginDialog, Notification, NotificationItem, UserDropdown, } from '@vben/layouts'; +import { AuthenticationLoginExpiredModal } from '@vben/universal-ui'; import { openWindow } from '@vben/utils'; import { preferences } from '@vben-core/preferences'; @@ -85,7 +85,7 @@ const menus = computed(() => [ const appStore = useAppStore(); const accessStore = useAccessStore(); -const { showLoginDialog, userInfo } = toRefs(accessStore); +const { openLoginExpiredModal, userInfo } = toRefs(accessStore); const router = useRouter(); async function handleLogout() { @@ -124,11 +124,11 @@ function handleMakeAll() { /> diff --git a/apps/web-antd/src/locales/langs/en-US.json b/apps/web-antd/src/locales/langs/en-US.json index cfcadf51..b12f278a 100644 --- a/apps/web-antd/src/locales/langs/en-US.json +++ b/apps/web-antd/src/locales/langs/en-US.json @@ -28,7 +28,12 @@ "embedded": "Embedded", "externalLink": "External Link" }, - "fallback": { "title": "Fallback Page" } + "fallback": { "title": "Fallback Page" }, + "features": { + "title": "Features", + "hideChildrenInMenu": "Hide Menu Children", + "loginExpired": "Login Expired" + } } } } diff --git a/apps/web-antd/src/locales/langs/zh-CN.json b/apps/web-antd/src/locales/langs/zh-CN.json index c488badf..e877d506 100644 --- a/apps/web-antd/src/locales/langs/zh-CN.json +++ b/apps/web-antd/src/locales/langs/zh-CN.json @@ -30,6 +30,11 @@ }, "fallback": { "title": "缺省页" + }, + "features": { + "title": "功能", + "hideChildrenInMenu": "隐藏菜单子项", + "loginExpired": "登录过期" } } } diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index 0c71a66b..ac0dbd1c 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -93,24 +93,9 @@ function setupAccessGuard(router: Router) { // 生成路由表 // 当前登录用户拥有的角色标识列表 let userRoles: string[] = []; - try { - const userInfo = - accessStore.userInfo || (await accessStore.fetchUserInfo()); - userRoles = userInfo.roles ?? []; - } catch (error: any) { - if (error.status === 409) { - accessStore.setShowLoginDialog(true); - } else if (error.status === 401) { - accessStore.reset(); - return { - path: LOGIN_PATH, - // 如不需要,直接删除 query - query: { redirect: encodeURIComponent(to.fullPath) }, - // 携带当前跳转的页面,登录后重新跳转该页面 - replace: true, - }; - } - } + const userInfo = + accessStore.userInfo || (await accessStore.fetchUserInfo()); + userRoles = userInfo.roles ?? []; // 生成菜单和路由 const { accessibleMenus, accessibleRoutes } = await generateAccess({ diff --git a/apps/web-antd/src/router/routes/modules/demos.ts b/apps/web-antd/src/router/routes/modules/demos.ts index f2870077..80e8199f 100644 --- a/apps/web-antd/src/router/routes/modules/demos.ts +++ b/apps/web-antd/src/router/routes/modules/demos.ts @@ -125,6 +125,48 @@ const routes: RouteRecordRaw[] = [ }, ], }, + { + meta: { + icon: 'mdi:feature-highlight', + title: $t('page.demos.features.title'), + }, + name: 'Features', + path: '/features', + redirect: '/features/hide-menu-children', + children: [ + { + name: 'HideChildrenInMenuParent', + path: 'hide-children-in-menu', + component: () => + import('#/views/demos/features/hide-menu-children/parent.vue'), + meta: { + hideChildrenInMenu: true, + icon: 'ic:round-menu', + title: 'page.demos.features.hideChildrenInMenu', + }, + children: [ + { + name: 'HideChildrenInMenuChildren', + path: 'hide-children-in-menu', + component: () => + import( + '#/views/demos/features/hide-menu-children/children.vue' + ), + }, + ], + }, + { + name: 'LoginExpired', + path: 'login-expired', + component: () => + import('#/views/demos/features/login-expired/index.vue'), + meta: { + icon: 'mdi:encryption-expiration', + title: $t('page.demos.features.loginExpired'), + }, + }, + ], + }, { meta: { icon: 'mdi:lightbulb-error-outline', diff --git a/apps/web-antd/src/store/modules/access.ts b/apps/web-antd/src/store/modules/access.ts index 9bbe54fe..09200781 100644 --- a/apps/web-antd/src/store/modules/access.ts +++ b/apps/web-antd/src/store/modules/access.ts @@ -5,7 +5,7 @@ import type { RouteRecordRaw } from 'vue-router'; import { computed, ref } from 'vue'; import { useRouter } from 'vue-router'; -import { DEFAULT_HOME_PATH } from '@vben/constants'; +import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants'; import { useCoreAccessStore } from '@vben-core/stores'; import { defineStore } from 'pinia'; @@ -17,12 +17,10 @@ export const useAccessStore = defineStore('access', () => { const router = useRouter(); const loading = ref(false); - const showLoginDialog = ref(false); - function setShowLoginDialog(value: boolean) { - showLoginDialog.value = value; - } + const openLoginExpiredModal = ref(false); const accessToken = computed(() => coreStoreAccess.accessToken); + const refreshToken = computed(() => coreStoreAccess.refreshToken); const userRoles = computed(() => coreStoreAccess.userRoles); const userInfo = computed(() => coreStoreAccess.userInfo); const accessRoutes = computed(() => coreStoreAccess.accessRoutes); @@ -31,6 +29,10 @@ export const useAccessStore = defineStore('access', () => { coreStoreAccess.setAccessMenus(menus); } + function setAccessToken(token: null | string) { + coreStoreAccess.setAccessToken(token); + } + function setAccessRoutes(routes: RouteRecordRaw[]) { coreStoreAccess.setAccessRoutes(routes); } @@ -70,7 +72,7 @@ export const useAccessStore = defineStore('access', () => { coreStoreAccess.setUserInfo(userInfo); coreStoreAccess.setAccessCodes(accessCodes); - showLoginDialog.value = false; + openLoginExpiredModal.value = false; onSuccess ? await onSuccess?.() : await router.push(userInfo.homePath || DEFAULT_HOME_PATH); @@ -85,6 +87,19 @@ export const useAccessStore = defineStore('access', () => { }; } + async function logout() { + coreStoreAccess.$reset(); + openLoginExpiredModal.value = false; + + // 回登陆页带上当前路由地址 + await router.replace({ + path: LOGIN_PATH, + query: { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + }, + }); + } + async function fetchUserInfo() { let userInfo: UserInfo | null = null; userInfo = await getUserInfo(); @@ -102,11 +117,13 @@ export const useAccessStore = defineStore('access', () => { authLogin, fetchUserInfo, loading, + logout, + openLoginExpiredModal, + refreshToken, reset, setAccessMenus, setAccessRoutes, - setShowLoginDialog, - showLoginDialog, + setAccessToken, userInfo, userRoles, }; diff --git a/apps/web-antd/src/store/modules/app.ts b/apps/web-antd/src/store/modules/app.ts index 8ffd545a..2def3a40 100644 --- a/apps/web-antd/src/store/modules/app.ts +++ b/apps/web-antd/src/store/modules/app.ts @@ -1,16 +1,18 @@ -import { useCoreAccessStore, useCoreTabbarStore } from '@vben-core/stores'; +import { useCoreTabbarStore } from '@vben-core/stores'; import { defineStore } from 'pinia'; +import { useAccessStore } from './access'; + export const useAppStore = defineStore('app', () => { - const coreStoreAccess = useCoreAccessStore(); + const accessStore = useAccessStore(); const coreTabbarStore = useCoreTabbarStore(); /** * 重置所有状态 */ async function resetAppState() { - coreStoreAccess.$reset(); + accessStore.$reset(); coreTabbarStore.$reset(); } diff --git a/apps/web-antd/src/views/demos/features/hide-menu-children/children.vue b/apps/web-antd/src/views/demos/features/hide-menu-children/children.vue new file mode 100644 index 00000000..9b009e36 --- /dev/null +++ b/apps/web-antd/src/views/demos/features/hide-menu-children/children.vue @@ -0,0 +1,3 @@ + diff --git a/apps/web-antd/src/views/demos/features/hide-menu-children/parent.vue b/apps/web-antd/src/views/demos/features/hide-menu-children/parent.vue new file mode 100644 index 00000000..b4458fe6 --- /dev/null +++ b/apps/web-antd/src/views/demos/features/hide-menu-children/parent.vue @@ -0,0 +1,13 @@ + + + diff --git a/apps/web-antd/src/views/demos/features/login-expired/index.vue b/apps/web-antd/src/views/demos/features/login-expired/index.vue new file mode 100644 index 00000000..2421a959 --- /dev/null +++ b/apps/web-antd/src/views/demos/features/login-expired/index.vue @@ -0,0 +1,40 @@ + + + diff --git a/internal/lint-configs/eslint-config/package.json b/internal/lint-configs/eslint-config/package.json index a1d0b2b7..a2bd0894 100644 --- a/internal/lint-configs/eslint-config/package.json +++ b/internal/lint-configs/eslint-config/package.json @@ -39,7 +39,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-i": "^2.29.1", - "eslint-plugin-jsdoc": "^48.6.0", + "eslint-plugin-jsdoc": "^48.7.0", "eslint-plugin-jsonc": "^2.16.0", "eslint-plugin-n": "^17.9.0", "eslint-plugin-no-only-tests": "^3.1.0", diff --git a/internal/node-utils/package.json b/internal/node-utils/package.json index 6e3a6078..10afd4e3 100644 --- a/internal/node-utils/package.json +++ b/internal/node-utils/package.json @@ -36,7 +36,7 @@ "nanoid": "^5.0.7", "pkg-types": "^1.1.3", "prettier": "^3.3.2", - "rimraf": "^6.0.0", + "rimraf": "^6.0.1", "zx": "^7.2.3" } } diff --git a/package.json b/package.json index 0054583c..f3890ad2 100644 --- a/package.json +++ b/package.json @@ -68,12 +68,12 @@ "husky": "^9.0.11", "is-ci": "^3.0.1", "jsdom": "^24.1.0", - "rimraf": "^6.0.0", + "rimraf": "^6.0.1", "turbo": "^2.0.6", "typescript": "^5.5.3", "unbuild": "^2.0.0", "vite": "^5.3.3", - "vitest": "^2.0.1", + "vitest": "^2.0.2", "vue-tsc": "^2.0.26" }, "engines": { diff --git a/packages/@core/forward/preferences/src/config.ts b/packages/@core/forward/preferences/src/config.ts index f992d885..998d0c2d 100644 --- a/packages/@core/forward/preferences/src/config.ts +++ b/packages/@core/forward/preferences/src/config.ts @@ -16,6 +16,7 @@ const defaultPreferences: Preferences = { isMobile: false, layout: 'sidebar-nav', locale: 'zh-CN', + loginExpiredMode: 'page', name: 'Vben Admin Pro', }, breadcrumb: { diff --git a/packages/@core/forward/preferences/src/types.ts b/packages/@core/forward/preferences/src/types.ts index 553c5ec7..bd6297a2 100644 --- a/packages/@core/forward/preferences/src/types.ts +++ b/packages/@core/forward/preferences/src/types.ts @@ -7,6 +7,12 @@ import type { ThemeModeType, } from '@vben-core/typings'; +/** + * 登录过期模式 + * 'modal' 弹窗模式 | 'page' 页面模式 + */ +type LoginExpiredModeType = 'modal' | 'page'; + type BreadcrumbStyleType = 'background' | 'normal'; type AccessModeType = 'allow-all' | 'backend' | 'frontend'; @@ -44,6 +50,8 @@ interface AppPreferences { layout: LayoutType; /** 支持的语言 */ locale: SupportedLanguagesType; + /** 登录过期模式 */ + loginExpiredMode: LoginExpiredModeType; /** 应用名 */ name: string; } @@ -236,6 +244,7 @@ export type { HeaderPreferences, LayoutHeaderModeType, LayoutType, + LoginExpiredModeType, LogoPreferences, NavigationPreferences, NavigationStyleType, diff --git a/packages/@core/forward/request/package.json b/packages/@core/forward/request/package.json index 6aecc203..982722c4 100644 --- a/packages/@core/forward/request/package.json +++ b/packages/@core/forward/request/package.json @@ -38,6 +38,7 @@ } }, "dependencies": { + "@vben-core/locales": "workspace:*", "@vben-core/toolkit": "workspace:*", "axios": "^1.7.2", "vue-request": "^2.0.4" diff --git a/packages/@core/forward/request/src/request-client/index.ts b/packages/@core/forward/request/src/request-client/index.ts index 0d85e38f..a6d3220c 100644 --- a/packages/@core/forward/request/src/request-client/index.ts +++ b/packages/@core/forward/request/src/request-client/index.ts @@ -1,3 +1,2 @@ export * from './request-client'; export type * from './types'; -export * from './util'; diff --git a/packages/@core/forward/request/src/request-client/modules/interceptor.ts b/packages/@core/forward/request/src/request-client/modules/interceptor.ts index 55b17c79..21b0264d 100644 --- a/packages/@core/forward/request/src/request-client/modules/interceptor.ts +++ b/packages/@core/forward/request/src/request-client/modules/interceptor.ts @@ -17,16 +17,22 @@ class InterceptorManager { ) => InternalAxiosRequestConfig | Promise, rejected?: (error: any) => any, ) { - this.axiosInstance.interceptors.request.use(fulfilled, rejected); + this.axiosInstance.interceptors.request.use( + fulfilled, + rejected || ((res) => res), + ); } - addResponseInterceptor( + addResponseInterceptor( fulfilled: ( - response: AxiosResponse, + response: AxiosResponse, ) => AxiosResponse | Promise, rejected?: (error: any) => any, ) { - this.axiosInstance.interceptors.response.use(fulfilled, rejected); + this.axiosInstance.interceptors.response.use( + fulfilled, + rejected || ((res) => res), + ); } } diff --git a/packages/@core/forward/request/src/request-client/request-client.ts b/packages/@core/forward/request/src/request-client/request-client.ts index 3c1368f7..8974a5a4 100644 --- a/packages/@core/forward/request/src/request-client/request-client.ts +++ b/packages/@core/forward/request/src/request-client/request-client.ts @@ -8,6 +8,7 @@ import type { import type { MakeAuthorizationFn, RequestClientOptions } from './types'; +import { $t } from '@vben-core/locales'; import { merge } from '@vben-core/toolkit'; import axios from 'axios'; @@ -19,6 +20,7 @@ import { FileUploader } from './modules/uploader'; class RequestClient { private instance: AxiosInstance; private makeAuthorization: MakeAuthorizationFn | undefined; + private options: RequestClientOptions; public addRequestInterceptor: InterceptorManager['addRequestInterceptor']; public addResponseInterceptor: InterceptorManager['addResponseInterceptor']; public download: FileDownloader['download']; @@ -39,6 +41,7 @@ class RequestClient { timeout: 10_000, }; const { makeAuthorization, ...axiosConfig } = options; + this.options = options; const requestConfig = merge(axiosConfig, defaultConfig); this.instance = axios.create(requestConfig); @@ -77,24 +80,86 @@ class RequestClient { }); } - private errorHandler(error: any) { - return Promise.reject(error); + private setupAuthorizationInterceptor() { + this.addRequestInterceptor( + (config: InternalAxiosRequestConfig) => { + const authorization = this.makeAuthorization?.(config); + if (authorization) { + const { token } = authorization.tokenHandler?.() ?? {}; + config.headers[authorization.key || 'Authorization'] = token; + } + return config; + }, + (error: any) => Promise.reject(error), + ); } - private setupAuthorizationInterceptor() { - this.addRequestInterceptor((config: InternalAxiosRequestConfig) => { - const authorization = this.makeAuthorization?.(config); - if (authorization) { - const { token } = authorization.handler?.() ?? {}; - config.headers[authorization.key || 'Authorization'] = token; - } - return config; - }, this.errorHandler); + private setupDefaultResponseInterceptor() { + this.addResponseInterceptor( + (response: AxiosResponse) => { + return response; + }, + (error: any) => { + if (axios.isCancel(error)) { + return Promise.reject(error); + } + + const err: string = error?.toString?.() ?? ''; + let errMsg = ''; + if (err?.includes('Network Error')) { + errMsg = $t('fallback.http.networkError'); + } else if (error?.message?.includes?.('timeout')) { + errMsg = $t('fallback.http.requestTimeout'); + } + const { makeAuthorization, makeErrorMessage } = this.options; + if (errMsg) { + makeErrorMessage?.(errMsg); + return Promise.reject(error); + } + + let errorMessage = error?.response?.data?.error?.message ?? ''; + const status = error?.response?.status; + + switch (status) { + case 400: { + errorMessage = $t('fallback.http.badRequest'); + break; + } + + case 401: { + errorMessage = $t('fallback.http.unauthorized'); + makeAuthorization?.().unAuthorizedHandler?.(); + break; + } + case 403: { + errorMessage = $t('fallback.http.forbidden'); + break; + } + // 404请求不存在 + case 404: { + errorMessage = $t('fallback.http.notFound'); + break; + } + case 408: { + errorMessage = $t('fallback.http.requestTimeout'); + + break; + } + default: { + errorMessage = $t('fallback.http.internalServerError'); + } + } + + makeErrorMessage?.(errorMessage); + return Promise.reject(error); + }, + ); } private setupInterceptors() { // 默认拦截器 this.setupAuthorizationInterceptor(); + this.setupDefaultResponseInterceptor(); } /** diff --git a/packages/@core/forward/request/src/request-client/types.ts b/packages/@core/forward/request/src/request-client/types.ts index 9aab5a1a..0c7753a9 100644 --- a/packages/@core/forward/request/src/request-client/types.ts +++ b/packages/@core/forward/request/src/request-client/types.ts @@ -7,18 +7,41 @@ type RequestContentType = | 'multipart/form-data;charset=utf-8'; interface MakeAuthorization { - handler: () => { refreshToken: string; token: string } | null; key?: string; + tokenHandler: () => { refreshToken: string; token: string } | null; + unAuthorizedHandler?: () => Promise; } type MakeAuthorizationFn = ( config?: InternalAxiosRequestConfig, ) => MakeAuthorization; +type ErrorMessageFn = (message: string) => void; + interface RequestClientOptions extends CreateAxiosDefaults { /** * 用于生成Authorization */ makeAuthorization?: MakeAuthorizationFn; + /** + * 用于生成错误消息 + */ + makeErrorMessage?: ErrorMessageFn; } -export type { MakeAuthorizationFn, RequestClientOptions, RequestContentType }; + +interface HttpResponse { + /** + * 0 表示成功 其他表示失败 + * 0 means success, others means fail + */ + code: number; + data: T; + message: string; +} + +export type { + HttpResponse, + MakeAuthorizationFn, + RequestClientOptions, + RequestContentType, +}; diff --git a/packages/@core/forward/request/src/request-client/util.test.ts b/packages/@core/forward/request/src/request-client/util.test.ts deleted file mode 100644 index fa1102af..00000000 --- a/packages/@core/forward/request/src/request-client/util.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import axios from 'axios'; -import { describe, expect, it } from 'vitest'; - -import { isCancelError } from './util'; - -describe('isCancelError', () => { - const source = axios.CancelToken.source(); - source.cancel('Operation canceled by the user.'); - - it('should detect cancellation', () => { - const error = new axios.Cancel('Operation canceled by the user.'); - - const result = isCancelError(error); - - expect(result).toBe(true); - }); - - it('should not detect cancellation on regular errors', () => { - const error = new Error('Regular error'); - - const result = isCancelError(error); - - expect(result).toBe(false); - }); -}); diff --git a/packages/@core/forward/request/src/request-client/util.ts b/packages/@core/forward/request/src/request-client/util.ts deleted file mode 100644 index 347c342c..00000000 --- a/packages/@core/forward/request/src/request-client/util.ts +++ /dev/null @@ -1,7 +0,0 @@ -import axios from 'axios'; - -function isCancelError(error: any) { - return axios.isCancel(error); -} - -export { isCancelError }; diff --git a/packages/@core/locales/src/langs/en-US.json b/packages/@core/locales/src/langs/en-US.json index 0909ef8a..d650cb77 100644 --- a/packages/@core/locales/src/langs/en-US.json +++ b/packages/@core/locales/src/langs/en-US.json @@ -39,7 +39,16 @@ "offline": "Offline Page", "offlineError": "Oops! Network Error", "offlineErrorDesc": "Sorry, can't connect to the internet. Check your connection.", - "coming-soon": "Coming Soon" + "comingSoon": "Coming Soon", + "http": { + "requestTimeout": "The request timed out. Please try again later.", + "networkError": "A network error occurred. Please check your internet connection and try again.", + "badRequest": "Bad Request. Please check your input and try again.", + "unauthorized": "Unauthorized. Please log in to continue.", + "forbidden": "Forbidden. You do not have permission to access this resource.", + "notFound": "Not Found. The requested resource could not be found.", + "internalServerError": "Internal Server Error. Something went wrong on our end. Please try again later." + } }, "widgets": { "document": "Document", @@ -104,6 +113,8 @@ "sendCode": "Get Security code", "sendText": "Resend in {0}s", "thirdPartyLogin": "Or continue with", + "loginAgainTitle": "Please Log In Again", + "loginAgainSubTitle": "Your login session has expired. Please log in again to continue.", "layout": { "center": "Align Center", "alignLeft": "Align Left", diff --git a/packages/@core/locales/src/langs/zh-CN.json b/packages/@core/locales/src/langs/zh-CN.json index 0ce6f001..6dd6aad8 100644 --- a/packages/@core/locales/src/langs/zh-CN.json +++ b/packages/@core/locales/src/langs/zh-CN.json @@ -39,7 +39,16 @@ "offline": "离线页面", "offlineError": "哎呀!网络错误", "offlineErrorDesc": "抱歉,无法连接到互联网,请检查您的网络连接并重试。", - "coming-soon": "即将推出" + "comingSoon": "即将推出", + "http": { + "requestTimeout": "请求超时,请稍后再试。", + "networkError": "网络异常,请检查您的网络连接后重试。", + "badRequest": "请求错误。请检查您的输入并重试。", + "unauthorized": "未授权。请登录以继续。", + "forbidden": "禁止访问, 您没有权限访问此资源。", + "notFound": "未找到, 请求的资源不存在。", + "internalServerError": "内部服务器错误,请稍后再试。" + } }, "widgets": { "document": "文档", @@ -104,6 +113,8 @@ "sendCode": "获取验证码", "sendText": "{0}秒后重新获取", "thirdPartyLogin": "其他登录方式", + "loginAgainTitle": "请重新登录", + "loginAgainSubTitle": "您的登录状态已过期,请重新登录以继续。", "layout": { "center": "居中", "alignLeft": "居左", diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue index 25b539ff..e8d063d4 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue @@ -14,13 +14,19 @@ import { useForwardPropsEmits, } from 'radix-vue'; -const props = defineProps< - { class?: HTMLAttributes['class'] } & DialogContentProps ->(); +const props = withDefaults( + defineProps< + { + class?: HTMLAttributes['class']; + showClose?: boolean; + } & DialogContentProps + >(), + { showClose: true }, +); const emits = defineEmits<{ close: [] } & DialogContentEmits>(); const delegatedProps = computed(() => { - const { class: _, ...delegated } = props; + const { class: _, showClose: __, ...delegated } = props; return delegated; }); @@ -46,6 +52,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits); diff --git a/packages/@core/ui-kit/shadcn-ui/src/index.ts b/packages/@core/ui-kit/shadcn-ui/src/index.ts index ca60c1a4..336db23d 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/index.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/index.ts @@ -2,6 +2,7 @@ import './styles/index.css'; export * from './components'; export { + VisuallyHidden, useEmitAsProps, useForwardExpose, useForwardProps, diff --git a/packages/business/layouts/package.json b/packages/business/layouts/package.json index 3e29dfe3..c1a595c8 100644 --- a/packages/business/layouts/package.json +++ b/packages/business/layouts/package.json @@ -48,7 +48,6 @@ "@vben-core/stores": "workspace:*", "@vben-core/tabs-ui": "workspace:*", "@vben-core/toolkit": "workspace:*", - "@vben/universal-ui": "workspace:*", "@vueuse/core": "^10.11.0", "vue": "^3.4.31", "vue-router": "^4.4.0" diff --git a/packages/business/layouts/src/widgets/index.ts b/packages/business/layouts/src/widgets/index.ts index 03303c8a..718d1a73 100644 --- a/packages/business/layouts/src/widgets/index.ts +++ b/packages/business/layouts/src/widgets/index.ts @@ -4,7 +4,6 @@ export { default as CozeAssistant } from './coze-assistant.vue'; export * from './global-search'; export { default as LanguageToggle } from './language-toggle.vue'; export { default as AuthenticationLayoutToggle } from './layout-toggle.vue'; -export * from './login-dialog'; export * from './notification'; export * from './preferences'; export * from './theme-toggle'; diff --git a/packages/business/layouts/src/widgets/login-dialog/index.ts b/packages/business/layouts/src/widgets/login-dialog/index.ts deleted file mode 100644 index 879f0864..00000000 --- a/packages/business/layouts/src/widgets/login-dialog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as LoginDialog } from './login-dialog.vue'; diff --git a/packages/business/layouts/src/widgets/login-dialog/login-dialog.vue b/packages/business/layouts/src/widgets/login-dialog/login-dialog.vue deleted file mode 100644 index 3ad397cf..00000000 --- a/packages/business/layouts/src/widgets/login-dialog/login-dialog.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/packages/business/layouts/src/widgets/preferences/preferences-sheet.vue b/packages/business/layouts/src/widgets/preferences/preferences-sheet.vue index 99c445a6..1e846e5f 100644 --- a/packages/business/layouts/src/widgets/preferences/preferences-sheet.vue +++ b/packages/business/layouts/src/widgets/preferences/preferences-sheet.vue @@ -399,22 +399,22 @@ async function handleReset() { :disabled="!diffPreference" class="mx-4 w-full" size="sm" - variant="outline" - @click="handleClearCache" - > - - {{ $t('preferences.clearAndLogout') }} - - {{ $t('preferences.copyPreferences') }} + + + {{ $t('preferences.clearAndLogout') }} + diff --git a/packages/business/universal-ui/src/authentication/index.ts b/packages/business/universal-ui/src/authentication/index.ts index f82360a4..6b1c7bb8 100644 --- a/packages/business/universal-ui/src/authentication/index.ts +++ b/packages/business/universal-ui/src/authentication/index.ts @@ -1,6 +1,7 @@ export { default as AuthenticationCodeLogin } from './code-login.vue'; export { default as AuthenticationForgetPassword } from './forget-password.vue'; export { default as AuthenticationLogin } from './login.vue'; +export { default as AuthenticationLoginExpiredModal } from './login-expired-modal.vue'; export { default as AuthenticationQrCodeLogin } from './qrcode-login.vue'; export { default as AuthenticationRegister } from './register.vue'; export type { diff --git a/packages/business/universal-ui/src/authentication/login-expired-modal.vue b/packages/business/universal-ui/src/authentication/login-expired-modal.vue new file mode 100644 index 00000000..e9e80201 --- /dev/null +++ b/packages/business/universal-ui/src/authentication/login-expired-modal.vue @@ -0,0 +1,55 @@ + + + diff --git a/packages/business/universal-ui/src/authentication/login.vue b/packages/business/universal-ui/src/authentication/login.vue index 12e55c0c..fefa7641 100644 --- a/packages/business/universal-ui/src/authentication/login.vue +++ b/packages/business/universal-ui/src/authentication/login.vue @@ -31,7 +31,10 @@ withDefaults(defineProps(), { showForgetPassword: true, showQrcodeLogin: true, showRegister: true, + showRememberMe: true, showThirdPartyLogin: true, + subTitle: '', + title: '', usernamePlaceholder: '', }); @@ -89,10 +92,10 @@ function handleGo(path: string) {