diff --git a/.env.development b/.env.development index 02b4fc6fff9b0ea9bf2e3f3feea043f9577883f1..770bf037ed1f1f4fb8bb358ccd39f777eef9a794 100644 --- a/.env.development +++ b/.env.development @@ -13,6 +13,9 @@ VITE_DROP_CONSOLE=false # 接口地址 VITE_GLOB_API_URL=/api +# 接口超时时间 +VITE_GLOB_API_TIMEOUT=30000 + # 文件上传地址 VITE_GLOB_UPLOAD_URL=/upload diff --git a/.env.production b/.env.production index b3378da39ba2d18e059cf207a92d243d36ef204e..f1bdfbcbd91610c48d09b2fe8b6d4902c392795c 100644 --- a/.env.production +++ b/.env.production @@ -17,6 +17,9 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE=false # 接口地址 VITE_GLOB_API_URL=/api +# 接口超时时间 +VITE_GLOB_API_TIMEOUT=30000 + # 文件上传地址 VITE_GLOB_UPLOAD_URL=/upload diff --git a/src/api/common/BaseApi.api.ts b/src/api/common/BaseApi.api.ts new file mode 100644 index 0000000000000000000000000000000000000000..a1604a912b2e79e5a9acd31ab4333c184672afee --- /dev/null +++ b/src/api/common/BaseApi.api.ts @@ -0,0 +1,21 @@ +import { defHttp } from '/@/utils/http/axios' + +/** + * 回声测试 + */ +export function echo(msg: string) { + return defHttp.get({ + url: '/echo', + params: { msg }, + }) +} + +/** + * 回声测试(必须要进行登录) + */ +export function authEcho(msg: string) { + return defHttp.get({ + url: '/auth/echo', + params: { msg }, + }) +} diff --git a/src/api/common/Pay.ts b/src/api/common/Pay.ts deleted file mode 100644 index 0713c762b634efa5a13f7e7e799f8cba08dc5dc4..0000000000000000000000000000000000000000 --- a/src/api/common/Pay.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { defHttp } from '/@/utils/http/axios' - -/** - * 取消支付(支付id) - */ -export function cancelByPaymentId(paymentId) { - return defHttp.post({ - url: '/uni_pay/cancelByPaymentId', - params: { paymentId }, - }) -} - -/** - * 退款 - */ -export function refund(obj) { - return defHttp.post({ - url: '/uni_pay/refund', - data: obj, - }) -} - -/** - * 刷新指定业务id的支付单状态 - */ -export function syncByBusinessId(businessId) { - return defHttp.post({ - url: '/uni_pay/syncByBusinessId', - method: 'POST', - params: { businessId }, - }) -} diff --git a/src/enums/pageEnum.ts b/src/enums/pageEnum.ts index 49b0a64e8e4128c3bda9d148686da0ec8c6cf92f..ab34f75d869b3f7c778fb88505dff8276eb547f8 100644 --- a/src/enums/pageEnum.ts +++ b/src/enums/pageEnum.ts @@ -1,5 +1,5 @@ export enum PageEnum { - // 登录 + // 登录页 BASE_LOGIN = '/login', // 个人设置 ACCOUNT_SETTING = '/account/setting', @@ -9,6 +9,11 @@ export enum PageEnum { BASE_HOME = '/dashboard', // 异常页 ERROR_PAGE = '/exception', - // error log page path + // 错误日志页面路径 ERROR_LOG_PAGE = '/error-log/list', } + +export const PageConstant = { + // 个人修改密码设置路由 + ACCOUNT_PASSWORD: { path: PageEnum.ACCOUNT_SETTING, query: { activeKey: 2 } }, +} diff --git a/src/hooks/bootx/useDict.ts b/src/hooks/bootx/useDict.ts index 21eda551cad27d27f9759152d5336bb710a7438f..5b09ca037e09ad773dfd7c5db0087f2f5360d860 100644 --- a/src/hooks/bootx/useDict.ts +++ b/src/hooks/bootx/useDict.ts @@ -10,7 +10,6 @@ const dictStore = useDictStore() async function getDict(): Promise { const dictList = dictStore.getDict if (dictList.length > 0) { - console.log(dictList) return dictList } else { return await dictStore.initDict() diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts index 06895a030c8c0c76b1057b559fb14fe35eaf57e5..a862d6a15d1a2dad846ed6d5754061998622ad7d 100644 --- a/src/hooks/web/usePermission.ts +++ b/src/hooks/web/usePermission.ts @@ -81,22 +81,6 @@ export function usePermission() { return true } - /** - * Change roles - * @param roles - */ - async function changeRole(roles: RoleEnum | RoleEnum[]): Promise { - if (projectSetting.permissionMode !== PermissionModeEnum.ROUTE_MAPPING) { - throw new Error('Please switch PermissionModeEnum to ROUTE_MAPPING mode in the configuration to operate!') - } - - if (!isArray(roles)) { - roles = [roles] - } - userStore.setRoleList(roles) - await resume() - } - /** * refresh menu data */ @@ -104,5 +88,5 @@ export function usePermission() { resume() } - return { changeRole, hasPermission, togglePermissionMode, refreshMenu } + return { hasPermission, togglePermissionMode, refreshMenu } } diff --git a/src/layouts/default/setting/components/SettingFooter.vue b/src/layouts/default/setting/components/SettingFooter.vue index ff80d4d543f49355bb183f1316271540f3777cf1..ead2af8f0b21029c9591a18b6971f2c15f26a4f1 100644 --- a/src/layouts/default/setting/components/SettingFooter.vue +++ b/src/layouts/default/setting/components/SettingFooter.vue @@ -2,17 +2,17 @@
- {{ t('layout.setting.copyBtn') }} + 拷贝 - {{ t('common.resetText') }} + 重置 - {{ t('layout.setting.clearBtn') }} + 清空缓存并返回登录页
diff --git a/src/logics/websocket/UserGlobalWebSocker.ts b/src/logics/websocket/UserGlobalWebSocker.ts index 4248384b6889f97fda830131988ad13d66e3f906..53ca017a01ab1a2a36c67d69f2d6b7ff3f35d1fa 100644 --- a/src/logics/websocket/UserGlobalWebSocker.ts +++ b/src/logics/websocket/UserGlobalWebSocker.ts @@ -7,23 +7,29 @@ import { EVENT_NOTICE, NOTIFICATION_ERROR, NOTIFICATION_INFO, NOTIFICATION_WARN import { publishWsEvent } from '/@/logics/websocket/WebsocketNotice' import { useMessage } from '/@/hooks/web/useMessage' import { findByParamKey } from '/@/api/common/Parameter' +import { authEcho } from '/@/api/common/BaseApi.api' const { notification } = useMessage() // websocket关闭 let wsClose: WebSocket['close'] +/** + * 初始化用户是否加载 + */ export async function initWebSocket() { const userStore = useUserStoreWithOut() const token = userStore.getToken const { data: wsUrl } = await findByParamKey('WebsocketServerUrl') - // TODO 判断token是否有效 - + // 判断token是否有效, 无效会自动退出 + await authEcho('') + // 后端服务地址 const serverUrl = `${wsUrl}/ws/user?AccessToken=${token}` + // 创建websocket实例 const { close } = useWebSocket(serverUrl, { - autoReconnect: true, - heartbeat: true, + autoReconnect: true, //自动重新连接 + heartbeat: true, // 心跳检测 onMessage: onMessage, onConnected: () => { console.log('用户全局WebSocket连接成功') diff --git a/src/logics/websocket/WebsocketNotice.ts b/src/logics/websocket/WebsocketNotice.ts index d5214f48df07daf12f4ef34b92df4e0aea3a129f..99364724ccc9feb298ce1fdae75e461c2ce6cc49 100644 --- a/src/logics/websocket/WebsocketNotice.ts +++ b/src/logics/websocket/WebsocketNotice.ts @@ -24,7 +24,7 @@ export function listenerEvent(key: WsListenerEnum, callback: Handler) { * 清除指定key所关联的事件监听 */ export function clearEventsByKey(key: WsListenerEnum) { - // 一个key可能对应多个事件回调, 清除时 + // 一个key可能对应多个事件回调, 清除该key所关联的所有会话 const eventHandlerList = emitter.all.get(key.toString()) as EventHandlerList eventHandlerList.forEach((value) => emitter.off(key.toString(), value)) } diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts index 468238572d4af77ecf354e1092e4a565a5aab211..1794ff40a39d9124063ecb603e11eea1652ac990 100644 --- a/src/router/guard/index.ts +++ b/src/router/guard/index.ts @@ -13,7 +13,7 @@ import nProgress from 'nprogress' import projectSetting from '/@/settings/projectSetting' import { createParamMenuGuard } from './paramMenuGuard' -// Don't change the order of creation +// 不要更改创建顺序 export function setupRouterGuard(router: Router) { createPageGuard(router) createPageLoadingGuard(router) @@ -111,15 +111,18 @@ function createScrollGuard(router: Router) { } /** - * Used to close the message instance when the route is switched + * 用于在切换路由时关闭消息实例 * @param router */ export function createMessageGuard(router: Router) { const { closeMessageOnSwitch } = projectSetting + console.log(projectSetting) + console.log(closeMessageOnSwitch) router.beforeEach(async () => { try { if (closeMessageOnSwitch) { + console.log(666444) Modal.destroyAll() notification.destroy() } diff --git a/src/router/guard/permissionGuard.ts b/src/router/guard/permissionGuard.ts index cd1eb99f6737471521cec6e4345bf083c6e19238..def995415c4c6f70013a24b720df8a940803cd7c 100644 --- a/src/router/guard/permissionGuard.ts +++ b/src/router/guard/permissionGuard.ts @@ -1,39 +1,34 @@ import type { Router, RouteRecordRaw } from 'vue-router' import { usePermissionStoreWithOut } from '/@/store/modules/permission' - import { PageEnum } from '/@/enums/pageEnum' import { useUserStoreWithOut } from '/@/store/modules/user' - import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic' - -import { initWebSocket } from "/@/logics/websocket/UserGlobalWebSocker"; +import { userLoginInitAction } from '/@/store/action/userAction' const LOGIN_PATH = PageEnum.BASE_LOGIN const whitePathList: PageEnum[] = [LOGIN_PATH] /** - * 路由守卫 + * 路由守卫: 处理下列情况 + * 1. 载入菜单 + * 2. 处理登录后用户提示 * @param router */ export function createPermissionGuard(router: Router) { const userStore = useUserStoreWithOut() const permissionStore = usePermissionStoreWithOut() router.beforeEach(async (to, from, next) => { - const token = userStore.getToken - - // Whitelist can be directly entered + // 可以访问输入白名单中的页面 if (whitePathList.includes(to.path as PageEnum)) { + // 如果在登录状态下访问登录页, 自动跳转到后续页面 if (to.path === LOGIN_PATH && token) { - const isSessionTimeout = userStore.getSessionTimeout try { - await userStore.afterLoginAction() - if (!isSessionTimeout) { - next((to.query?.redirect as string) || '/') - return - } + // 如果参数未携带重定向路径则自动跳转到首页 + next((to.query?.redirect as string) || '/') + return } catch {} } next() @@ -47,8 +42,7 @@ export function createPermissionGuard(router: Router) { next() return } - - // redirect login page + //重定向登录页面 const redirectData: { path: string; replace: boolean; query?: Recordable } = { path: LOGIN_PATH, replace: true, @@ -63,33 +57,17 @@ export function createPermissionGuard(router: Router) { return } - // Jump to the 404 page after processing the login - if (from.path === LOGIN_PATH && to.name === PAGE_NOT_FOUND_ROUTE.name && to.fullPath !== PageEnum.BASE_HOME) { - next(PageEnum.BASE_HOME) - return - } - - // get userinfo while last fetch time is empty - if (userStore.getLastUpdateTime === 0) { - try { - await userStore.refreshUserInfoAction() - } catch (err) { - next() - return - } - } - + // 路由是否初始化完毕, 完毕后直接直接跳转, 不再向下执行初始化操作 if (permissionStore.getIsDynamicAddedRoute) { next() return } - // 初始化 websocket连接. - initWebSocket() + // 用户初始化等操作. + userLoginInitAction() - // 重载菜单 + // 重载菜单, 进行路由菜单的组装并进行跳转 console.log('重载菜单') const routes = await permissionStore.buildRoutesAction() - routes.forEach((route) => { try { router.addRoute(route as unknown as RouteRecordRaw) @@ -98,8 +76,8 @@ export function createPermissionGuard(router: Router) { } }) + // 40x 系列路由 router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw) - permissionStore.setDynamicAddedRoute(true) if (to.name === PAGE_NOT_FOUND_ROUTE.name) { diff --git a/src/router/helper/routeHelper.ts b/src/router/helper/routeHelper.ts index 1dff7b99910e220352c03135e6d87a01a3a3b321..d664e469a15ac7887d21989ec60a9830730bd519 100644 --- a/src/router/helper/routeHelper.ts +++ b/src/router/helper/routeHelper.ts @@ -84,7 +84,7 @@ function dynamicImport(dynamicViewsModules: Record Promise(routeList: AppRouteRecordRaw[]): T[] { - routeList.forEach((route) => { + routeList?.forEach((route) => { const component = route.component as string if (component) { if (component.toUpperCase() === 'LAYOUT') { diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index a2e22367cbbf220dac2ebabfb996467eba79f13f..ca8e897449643440c839ff8a80f9bd91c0423128 100644 --- a/src/settings/projectSetting.ts +++ b/src/settings/projectSetting.ts @@ -165,17 +165,16 @@ const setting: ProjectConfig = { // Use error-handler-plugin useErrorHandle: false, - // Whether to open back to top + // 是否打开返回顶部 useOpenBackTop: true, - // Is it possible to embed iframe pages + // 是否可以嵌入 iframe 页面 canEmbedIFramePage: true, - // Whether to delete unclosed messages and notify when switching the interface - closeMessageOnSwitch: true, + // 切换接口时是否删除未关闭的消息并通知 + closeMessageOnSwitch: false, - // Whether to cancel the http request that has been sent but not responded when switching the interface. - // If it is enabled, I want to overwrite a single interface. Can be set in a separate interface + // 如果启用,我想覆盖单个接口。可以在单独的界面中设置 removeAllHttpPending: false, } diff --git a/src/store/action/userAction.ts b/src/store/action/userAction.ts new file mode 100644 index 0000000000000000000000000000000000000000..6456811a3214dd3db9ec3cc973a7c3c99c3eb43c --- /dev/null +++ b/src/store/action/userAction.ts @@ -0,0 +1,39 @@ +import { closeWebSocket, initWebSocket } from '/@/logics/websocket/UserGlobalWebSocker' +import { useUserStoreWithOut } from '/@/store/modules/user' +import { userPassWordCheck } from '/@/views/security/remind/UserRemind' + +/** + * 用户登录后初始化等操作, 主要用在下列两种场景上 + * 1. 用户登陆后的操作 + * 2. 刷新页面时路由重新加载 + */ +export async function userLoginInitAction() { + const userStore = useUserStoreWithOut() + + // 刷新登陆后用户信息 + userStore.refreshUserInfoAction().then() + // 初始化 websocket连接. + initWebSocket().then() + // 检查密码情况 + const check = await userPassWordCheck() + if (!check) { + return + } + // 检查消息提醒 +} + +/** + * 用户后的操作 + */ +export function userLogoutAction() { + const userStore = useUserStoreWithOut() + // 关闭websocket + closeWebSocket() + + // 重置 Token和用户信息 需要放最后 + userStore.resetState() +} + +/** + * 监听后端推送的退出事件 + */ diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index b8cdab6eba76e1c19cd69c54b4d0510d2c3d82bc..d0630be55d18915f64f7dffcaa3df445bb7e77f9 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -2,7 +2,7 @@ import type { AppRouteRecordRaw, Menu } from '/@/router/types' import { defineStore } from 'pinia' import { store } from '/@/store' -import { transformObjToRoute, flatMultiLevelRoutes } from '/@/router/helper/routeHelper' +import { flatMultiLevelRoutes, transformObjToRoute } from '/@/router/helper/routeHelper' import { transformRouteToMenu } from '/@/router/helper/menuHelper' import { DASHBOARD, ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic' @@ -15,7 +15,7 @@ import { useMessage } from '/@/hooks/web/useMessage' import { PageEnum } from '/@/enums/pageEnum' import { getAppEnvConfig } from '/@/utils/env' import { PermMenu } from '/@/api/sys/model/menuModel' -import { PROJECT_BASE } from "/@/router/routes/project"; +import { PROJECT_BASE } from '/@/router/routes/project' interface PermissionState { // 权限代码列表 @@ -89,7 +89,7 @@ export const usePermissionStore = defineStore({ this.lastBuildMenuTime = 0 }, /** - * 权限码和菜单更新 + * 权限码和菜单更新, 更新权限码并返回菜单信息 */ async changeMenuAndPermCode(): Promise { const { VITE_GLOB_APP_CLIENT } = getAppEnvConfig() @@ -104,28 +104,27 @@ export const usePermissionStore = defineStore({ * 转换路由为系统中的菜单 */ convertMenus(permMenus: PermMenu[]): AppRouteRecordRaw[] { - return permMenus?.map((o) => { - const menu = { - name: o.name, - path: o.path, - component: o.component, - targetOutside: o.targetOutside, - iframeUrl: o.iframeUrl, - redirect: o.redirect, - meta: { - orderNo: o.sortNo, - title: o.title, - icon: o.icon, - hideMenu: o.hidden, - hideChildrenInMenu: o.hideChildrenInMenu, - ignoreKeepAlive: !o.keepAlive, - }, - children: this.convertMenus(o.children), - } as AppRouteRecordRaw - if (o.component.toUpperCase()) { - } - return menu - }) + return ( + permMenus?.map((o) => { + return { + name: o.name, + path: o.path, + component: o.component, + targetOutside: o.targetOutside, + iframeUrl: o.iframeUrl, + redirect: o.redirect, + meta: { + orderNo: o.sortNo, + title: o.title, + icon: o.icon, + hideMenu: o.hidden, + hideChildrenInMenu: o.hideChildrenInMenu, + ignoreKeepAlive: !o.keepAlive, + }, + children: this.convertMenus(o.children), + } as AppRouteRecordRaw + }) || [] + ) }, /** @@ -184,7 +183,7 @@ export const usePermissionStore = defineStore({ let routeList: AppRouteRecordRaw[] = [] - // 获取菜单和权限码 + // 获取后台的菜单和更新权限码 try { const permMenus = await this.changeMenuAndPermCode() // 将 后端获取的菜单转换为系统中的菜单格式 @@ -192,17 +191,19 @@ export const usePermissionStore = defineStore({ } catch (error) { console.error(error) } + // 后端获取的菜单如果为空, 不进行处理 + if (routeList) { + routeList = transformObjToRoute(routeList) + // 后台路由到菜单结构 + const backMenuList = transformRouteToMenu(routeList) + this.setBackMenuList(backMenuList) + // 删除 meta.ignoreRoute 项 + routeList = filter(routeList, routeRemoveIgnoreFilter) + routeList = routeList.filter(routeRemoveIgnoreFilter) + + routeList = flatMultiLevelRoutes(routeList) + } - // 动态引入组件 - routeList = transformObjToRoute(routeList) - // 后台路由到菜单结构 - const backMenuList = transformRouteToMenu(routeList) - this.setBackMenuList(backMenuList) - // 删除 meta.ignoreRoute 项 - routeList = filter(routeList, routeRemoveIgnoreFilter) - routeList = routeList.filter(routeRemoveIgnoreFilter) - - routeList = flatMultiLevelRoutes(routeList) routes = [PAGE_NOT_FOUND_ROUTE, ...routeList] // 添加更多配置的路由菜单 diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 879f83080b6ea65f39270e1fff29926ab0f09410..8be57a3475bd7c8daf634036eb548187248eb063 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,44 +1,30 @@ import type { UserInfo } from '/#/store' import { defineStore } from 'pinia' import { store } from '/@/store' -import { RoleEnum } from '/@/enums/roleEnum' +import { clearAuthCache, getAuthCache, setAuthCache } from '/@/utils/auth' import { PageEnum } from '/@/enums/pageEnum' -import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum' -import { getAuthCache, setAuthCache } from '/@/utils/auth' import { LoginParams } from '/@/api/sys/model/userModel' import { doLogout, login } from '/@/api/sys/login' import { useMessage } from '/@/hooks/web/useMessage' import { router } from '/@/router' -import { usePermissionStore } from '/@/store/modules/permission' -import { RouteRecordRaw } from 'vue-router' -import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic' import { h } from 'vue' import { getFilePreviewUrlPrefix } from '/@/api/common/FileUpload' -// @ts-ignore import { getUserInfo } from '/@/api/sys/user' -import { closeWebSocket, initWebSocket } from '/@/logics/websocket/UserGlobalWebSocker' +import { userLogoutAction } from '/@/store/action/userAction' +import { TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum' interface UserState { userInfo: Nullable token?: string - roleList: RoleEnum[] - sessionTimeout?: boolean - lastUpdateTime: number } export const useUserStore = defineStore({ id: 'app-user', state: (): UserState => ({ - // user info + // 用户信息 userInfo: null, // token token: undefined, - // roleList - roleList: [], - // Whether the login expired - sessionTimeout: false, - // Last fetch time - lastUpdateTime: 0, }), getters: { getUserInfo(): UserInfo { @@ -47,87 +33,50 @@ export const useUserStore = defineStore({ getToken(): string { return this.token || getAuthCache(TOKEN_KEY) }, - getRoleList(): RoleEnum[] { - return this.roleList.length > 0 ? this.roleList : getAuthCache(ROLES_KEY) - }, - getSessionTimeout(): boolean { - return !!this.sessionTimeout - }, - getLastUpdateTime(): number { - return this.lastUpdateTime - }, }, actions: { - // token信息 + /** + * 设置token + */ setToken(info: string | undefined) { - this.token = info ? info : '' // for null or undefined value + this.token = info ? info : '' setAuthCache(TOKEN_KEY, info) }, - setRoleList(roleList: RoleEnum[]) { - this.roleList = roleList - setAuthCache(ROLES_KEY, roleList) - }, /** * 保存用户信息, 不要直接使用 * 使用 UserStoreUtil 中的包装方法来处理 */ setUserInfo(info: UserInfo | null) { this.userInfo = info - this.lastUpdateTime = new Date().getTime() setAuthCache(USER_INFO_KEY, info) }, - setSessionTimeout(flag: boolean) { - this.sessionTimeout = flag - }, + /** + * 重置 Token和用户状态 + */ resetState() { this.userInfo = null this.token = '' - this.roleList = [] - this.sessionTimeout = false + clearAuthCache() }, /** * 登录方法 */ async login(params: LoginParams) { try { + const to = router.currentRoute.value const { data: token } = await login(params) // 保存token this.setToken(token) - await this.afterLoginAction(true) + // 跳转路由 + await router.replace((to.query?.redirect as string) || '/') return token } catch (error) { - return Promise.reject(error) + console.error(error) } }, /** - * 登录后操作 + * 刷新登陆后用户信息 */ - async afterLoginAction(goHome?: boolean) { - if (!this.getToken) return null - // 刷新登陆后用户信息 - await this.refreshUserInfoAction() - const sessionTimeout = this.sessionTimeout - // 超时 - if (sessionTimeout) { - this.setSessionTimeout(false) - } else { - const permissionStore = usePermissionStore() - if (!permissionStore.isDynamicAddedRoute) { - // 构建路由 - const routes = await permissionStore.buildRoutesAction() - routes.forEach((route) => { - router.addRoute(route as unknown as RouteRecordRaw) - }) - // 404路由 - router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw) - permissionStore.setDynamicAddedRoute(true) - } - // 初始化 websocket连接. - initWebSocket() - goHome && (await router.replace(PageEnum.BASE_HOME)) - } - }, - // 刷新登陆后用户信息 async refreshUserInfoAction() { if (!this.getToken) return null const { data: userInfo } = await getUserInfo() @@ -137,25 +86,25 @@ export const useUserStore = defineStore({ this.setUserInfo(userInfo) }, /** - * 退出 + * 退出操作 */ async logout(goLogin = false) { if (this.getToken) { try { + // 服务端退出 await doLogout() } catch { - console.log('注销Token失败') + console.warn('注销Token失败') } } - this.setToken(undefined) - this.setSessionTimeout(false) - this.setUserInfo(null) - closeWebSocket() - goLogin && router.push(PageEnum.BASE_LOGIN) + // 重置状态 + userLogoutAction() + // 跳转到登录页 + goLogin && (await router.push(PageEnum.BASE_LOGIN)) }, /** - * @description: Confirm before logging out + * 是否进行退出弹窗 */ confirmLoginOut() { const { createConfirm } = useMessage() @@ -171,7 +120,7 @@ export const useUserStore = defineStore({ }, }) -// Need to be used outside the setup +// setup 之外使用 export function useUserStoreWithOut() { return useUserStore(store) } diff --git a/src/utils/dataUtil.ts b/src/utils/dataUtil.ts index ce38e8044b6bfbbe9d04fe955f95f649bbe2a956..3da9ef549158b26443c86c5b0f9fa1f2ac2b0657 100644 --- a/src/utils/dataUtil.ts +++ b/src/utils/dataUtil.ts @@ -41,7 +41,7 @@ export interface Tree { * @param fieldValue 要查询的字段值 * @param fieldName 字段名称 */ -export function findOneByField(list: any[], fieldValue: object, fieldName: string) { +export function findOneByField(list: any, fieldValue: object, fieldName: string) { const item = unref(list)?.filter((o) => { return o[fieldName] === fieldValue }) diff --git a/src/utils/env.ts b/src/utils/env.ts index ca0d943990afb08b7c37bd018e9b600928528436..414964422626e639600d754c362a5c0dc4f7c862 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -27,6 +27,7 @@ export function getAppEnvConfig() { VITE_GLOB_API_URL, VITE_GLOB_APP_SHORT_NAME, VITE_GLOB_API_URL_PREFIX, + VITE_GLOB_API_TIMEOUT, VITE_GLOB_UPLOAD_URL, VITE_GLOB_APP_CLIENT, } = ENV @@ -42,6 +43,7 @@ export function getAppEnvConfig() { VITE_GLOB_API_URL, VITE_GLOB_APP_SHORT_NAME, VITE_GLOB_API_URL_PREFIX, + VITE_GLOB_API_TIMEOUT, VITE_GLOB_UPLOAD_URL, VITE_GLOB_APP_CLIENT, } diff --git a/src/utils/http/axios/index.ts b/src/utils/http/axios/index.ts index 594b702adea0ec7dd9e1c9ceff36e1a723d3674a..166af4bb78c32cf1991129d9ce4684d43c6b3d1b 100644 --- a/src/utils/http/axios/index.ts +++ b/src/utils/http/axios/index.ts @@ -1,6 +1,3 @@ -// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动 -// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged - import type { AxiosResponse } from 'axios' import { clone } from 'lodash-es' import type { RequestOptions, Result } from '/#/axios' @@ -11,24 +8,28 @@ import { useGlobSetting } from '/@/hooks/setting' import { useMessage } from '/@/hooks/web/useMessage' import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum' import { isString } from '/@/utils/is' -import { getToken } from '/@/utils/auth' import { setObjToUrlParams, deepMerge } from '/@/utils' import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog' import { useI18n } from '/@/hooks/web/useI18n' import { joinTimestamp, formatRequestDate } from './helper' import { useUserStoreWithOut } from '/@/store/modules/user' import { AxiosRetry } from '/@/utils/http/axios/axiosRetry' +import { getAppEnvConfig } from "/@/utils/env"; + +/** + * axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动 + */ const globSetting = useGlobSetting() const urlPrefix = globSetting.urlPrefix const { createMessage, createErrorModal } = useMessage() /** - * @description: 数据处理,方便区分多种处理方式 + * 数据处理,方便区分多种处理方式 */ const transform: AxiosTransform = { /** - * @description: 处理响应数据。如果数据不是预期格式,可直接抛出错误 + * 处理响应数据。如果数据不是预期格式,可直接抛出错误 */ transformResponseHook: (res: AxiosResponse, options: RequestOptions) => { // 不进行任何处理,直接返回 @@ -48,18 +49,21 @@ const transform: AxiosTransform = { return rawData } - // 接收的通常是json的数据 - // 这里 code,data,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 + // 接收的通常是json的数据,这里 code,data,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 const { code, msg, traceId } = rawData + // 如果返回值中连code都没有, 说明返回的不是结构体, 不继续向下进行处理 + if (code === undefined) { + return + } + // 这里逻辑可以根据项目进行修改 const hasSuccess = rawData && Reflect.has(rawData, 'code') && code === ResultEnum.SUCCESS if (hasSuccess) { return rawData } - // 在此处根据自己项目的实际情况对不同的code执行不同的操作 - // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 + // 在此处根据自己项目的实际情况对不同的code执行不同的操作, 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 let timeoutMsg = '' const userStore = useUserStoreWithOut() switch (code) { @@ -67,7 +71,7 @@ const transform: AxiosTransform = { case ResultEnum.NOT_LOGIN: timeoutMsg = '登录超时,请重新登录!' userStore.setToken(undefined) - userStore.logout(true) + userStore.logout(true).then() break default: if (msg) { @@ -86,7 +90,9 @@ const transform: AxiosTransform = { throw new Error(timeoutMsg || '请求出错,请稍候重试') }, - // 请求之前处理config + /** + * 请求之前处理config + */ beforeRequestHook: (config, options) => { const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options if (joinPrefix) { @@ -96,6 +102,7 @@ const transform: AxiosTransform = { if (apiUrl && isString(apiUrl)) { config.url = `${apiUrl}${config.url}` } + // 请求参数和请求体藕最 const params = config.params || {} const data = config.data || false formatDate && data && !isString(data) && formatRequestDate(data) @@ -114,10 +121,6 @@ const transform: AxiosTransform = { if (Reflect.has(config, 'data') && config.data && (Object.keys(config.data).length > 0 || config.data instanceof FormData)) { config.data = data config.params = params - } else { - // 非GET请求如果没有提供data,则将params视为data (去除, 非GET也会进行普通参数请求) - // config.data = params - // config.params = undefined } if (joinParamsToUrl) { config.url = setObjToUrlParams(config.url as string, Object.assign({}, config.params, config.data)) @@ -132,11 +135,12 @@ const transform: AxiosTransform = { }, /** - * @description: 请求拦截器处理 + * 请求拦截器处理 */ requestInterceptors: (config, options) => { // 请求之前处理config - const token = getToken() + const userStore = useUserStoreWithOut() + const token = userStore.getToken if (token && (config as Recordable)?.requestOptions?.withToken !== false) { // 添加 token 到请求头 ;(config as Recordable).headers.AccessToken = token @@ -145,14 +149,14 @@ const transform: AxiosTransform = { }, /** - * @description: 响应拦截器处理 + * 响应拦截器处理 */ responseInterceptors: (res: AxiosResponse) => { return res }, /** - * @description: 响应错误处理 + * 响应错误处理 */ responseInterceptorsCatch: (axiosInstance: AxiosResponse, error: any) => { const { t } = useI18n() @@ -166,15 +170,15 @@ const transform: AxiosTransform = { try { if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { - errMessage = t('sys.api.apiTimeoutMessage') + errMessage = '接口请求超时,请刷新页面重试!' } if (err?.includes('Network Error')) { - errMessage = t('sys.api.networkExceptionMsg') + errMessage = '网络异常,请检查您的网络连接是否正常!' } if (errMessage) { if (errorMessageMode === 'modal') { - createErrorModal({ title: t('sys.api.errorTip'), content: errMessage }) + createErrorModal({ title: '错误提示', content: errMessage }) } else if (errorMessageMode === 'message') { createMessage.error(errMessage) } @@ -197,7 +201,12 @@ const transform: AxiosTransform = { }, } +/** + * 创建请求用 Axios 对象 + * @param opt + */ function createAxios(opt?: Partial) { + const { VITE_GLOB_API_TIMEOUT } = getAppEnvConfig() return new VAxios( // 深度合并 deepMerge( @@ -206,7 +215,7 @@ function createAxios(opt?: Partial) { // authentication schemes,e.g: Bearer // authenticationScheme: 'Bearer', authenticationScheme: '', - timeout: 10 * 1000, + timeout: VITE_GLOB_API_TIMEOUT, // 基础接口地址 // baseURL: globSetting.apiUrl, @@ -251,11 +260,3 @@ function createAxios(opt?: Partial) { ) } export const defHttp = createAxios() - -// other api url -// export const otherHttp = createAxios({ -// requestOptions: { -// apiUrl: 'xxx', -// urlPrefix: 'xxx', -// }, -// }); diff --git a/src/views/account/account.api.ts b/src/views/account/account.api.ts index bc2bfa3e8e1383aef030eacfd13c32b40ad22567..5dcd37daf032a8645546f9139133343a445a0b46 100644 --- a/src/views/account/account.api.ts +++ b/src/views/account/account.api.ts @@ -40,6 +40,16 @@ export function updatePassword(password, newPassword) { }) } +/** + * 要修改的密码是否重复 + */ +export function isRecentlyUsed(password) { + return defHttp.get>({ + url: '/security/password/isRecentlyUsed', + params: { password }, + }) +} + /** * 绑定手机 */ diff --git a/src/views/account/accountModel.ts b/src/views/account/accountModel.ts index cb8c617a19c077f97a77302eb41a73cbd77b3329..f36240a4a35d249161f8e34648d0a8b524ab795b 100644 --- a/src/views/account/accountModel.ts +++ b/src/views/account/accountModel.ts @@ -13,9 +13,9 @@ export interface UserDetails { // 邮箱 email?: string // 是否管理员 - admin?: boolean + administrator?: boolean // 终端id列表 - clientIdList?: string[] + clients?: string[] } /** diff --git a/src/views/account/security/PasswordEdit.vue b/src/views/account/security/PasswordEdit.vue index 6252d83e1e5742dd061218f07b7342e7c00a99f0..f990885ff6e4bd944ebd8ce81bc894081760d607 100644 --- a/src/views/account/security/PasswordEdit.vue +++ b/src/views/account/security/PasswordEdit.vue @@ -35,7 +35,7 @@ import { nextTick, reactive } from 'vue' import { FormInstance, Rule } from 'ant-design-vue/lib/form' import StrengthMeter from '/@/components/StrengthMeter/src/StrengthMeter.vue' - import { updatePassword } from '/@/views/account/account.api' + import { isRecentlyUsed, updatePassword } from '/@/views/account/account.api' const { visible, confirmLoading, modalWidth, labelCol, wrapperCol, handleCancel } = useFormEdit() @@ -48,7 +48,10 @@ }) const rules = reactive({ oldPassword: [{ required: true, message: '请输入原密码!' }], - newPassword: [{ required: true, message: '请输入新密码!' }], + newPassword: [ + { required: true, message: '请输入新密码!' }, + { validator: validatePasswordRecently, trigger: 'blur' }, + ], confirmPassword: [{ required: true, message: '请重新输入新密码!' }, { validator: validateConfirmPassword }], } as Record) const formRef = $ref() @@ -60,6 +63,14 @@ }) } + /** + * 查看要修改的密码是否重复 + */ + async function validatePasswordRecently() { + const { data } = await isRecentlyUsed(form.newPassword) + return data ? Promise.reject('不可以使用近期使用过的密码! ') : Promise.resolve() + } + // 验证新密码 function validateNewPassword() { if (confirmDirty) { diff --git a/src/views/account/setting/index.vue b/src/views/account/setting/index.vue index c8041e05193fb396a828474f70c9a1465e8abbc9..07aa9db9de6eeed3bf4601c77f667f5f7a12330b 100644 --- a/src/views/account/setting/index.vue +++ b/src/views/account/setting/index.vue @@ -1,7 +1,7 @@