diff --git a/src/service/api/login.ts b/src/service/api/login.ts index 76645d4..4ee6bad 100644 --- a/src/service/api/login.ts +++ b/src/service/api/login.ts @@ -1,25 +1,84 @@ import { request } from '../http' -interface Ilogin { - userName: string +interface LoginRequest { + loginName: string password: string + codeKey: string + securityCode: string + rememberMe?: boolean } -export function fetchLogin(data: Ilogin) { - const methodInstance = request.Post>('/login', data) +interface RegisterRequest { + loginName: string + password: string + userName: string + codeKey: string + securityCode: string +} + +interface LoginResponse { + tokenName: string + tokenValue: string +} + +interface CaptchaResponse { + codeKey: string + captchaPicture: string + captchaText?: string +} + +export function fetchLogin(data: LoginRequest) { + const methodInstance = request.Post>('/auth/login', data) methodInstance.meta = { authRole: null, } return methodInstance } + +export function fetchLogout() { + return request.Get>('/auth/logout') +} + +export function fetchRegister(data: RegisterRequest) { + const methodInstance = request.Post>('/auth/register', data) + methodInstance.meta = { + authRole: null, + } + return methodInstance +} + export function fetchUpdateToken(data: any) { - const method = request.Post>('/updateToken', data) + const method = request.Post>('/updateToken', data) method.meta = { authRole: 'refreshToken', } return method } -export function fetchUserRoutes(params: { id: number }) { +export function fetchUserRoutesOld(params: { id: number }) { return request.Get>('/getUserRoutes', { params }) } + +export function fetchLoginUserInfo() { + return request.Get>('/coder/sysLoginUser/getLoginUserInformation') +} + +// 验证码相关接口 + +// 获取PNG格式验证码 +export function fetchCaptchaPng() { + const methodInstance = request.Get>('/captcha/png') + methodInstance.meta = { + authRole: null, + } + return methodInstance +} + +// 获取GIF格式验证码 +export function fetchCaptchaGif() { + const methodInstance = request.Get>('/captcha/gif') + methodInstance.meta = { + authRole: null, + } + return methodInstance +} diff --git a/src/service/http/alova.ts b/src/service/http/alova.ts index a13e85a..a05fd44 100644 --- a/src/service/http/alova.ts +++ b/src/service/http/alova.ts @@ -1,4 +1,5 @@ import { local } from '@/utils' +import { coiMsgWarning } from '@/utils/coi' import { createAlova } from 'alova' import { createServerTokenAuthentication } from 'alova/client' import adapterFetch from 'alova/fetch' @@ -89,7 +90,7 @@ export function createAlovaInstance( }, onError: (error, method) => { const tip = `[${method.type}] - [${method.url}] - ${error.message}` - window.$message?.warning(tip) + coiMsgWarning(tip) }, onComplete: async (_method) => { diff --git a/src/service/http/config.ts b/src/service/http/config.ts index 92c947b..c13dd0f 100644 --- a/src/service/http/config.ts +++ b/src/service/http/config.ts @@ -10,8 +10,8 @@ export const DEFAULT_ALOVA_OPTIONS = { export const DEFAULT_BACKEND_OPTIONS = { codeKey: 'code', dataKey: 'data', - msgKey: 'message', - successCode: 200, + msgKey: 'msg', + successCode: 1, } /** 请求不成功各种状态的错误 */ diff --git a/src/service/http/handle.ts b/src/service/http/handle.ts index 51fa4c2..9ca33d6 100644 --- a/src/service/http/handle.ts +++ b/src/service/http/handle.ts @@ -1,6 +1,7 @@ import { fetchUpdateToken } from '@/service' import { useAuthStore } from '@/store' import { local } from '@/utils' +import { coiMsgError } from '@/utils/coi' import { ERROR_NO_TIP_STATUS, ERROR_STATUS, @@ -94,5 +95,5 @@ export function showError(error: Service.RequestError) { if (ERROR_NO_TIP_STATUS.includes(code)) return - window.$message.error(error.message) + coiMsgError(error.message) } diff --git a/src/store/auth.ts b/src/store/auth.ts index e03ff89..6951c4c 100644 --- a/src/store/auth.ts +++ b/src/store/auth.ts @@ -1,5 +1,5 @@ import { router } from '@/router' -import { fetchLogin } from '@/service' +import { fetchLogin, fetchLoginUserInfo } from '@/service' import { local } from '@/utils' import { useRouteStore } from './router' import { useTabStore } from './tab' @@ -52,14 +52,28 @@ export const useAuthStore = defineStore('auth-store', { }, /* 用户登录 */ - async login(userName: string, password: string) { + async login(loginName: string, password: string, codeKey: string, securityCode: string, rememberMe = false) { try { - const { isSuccess, data } = await fetchLogin({ userName, password }) + const { isSuccess, data } = await fetchLogin({ loginName, password, codeKey, securityCode, rememberMe }) if (!isSuccess) return + // 保存Token + local.set('accessToken', data.tokenValue) + this.token = data.tokenValue + + // 获取用户信息 + const userInfoResult = await fetchLoginUserInfo() + if (!userInfoResult.isSuccess) + return + // 处理登录信息 - await this.handleLoginInfo(data) + const userInfo = { + ...userInfoResult.data, + accessToken: data.tokenValue, + refreshToken: data.tokenValue, // 如果后端没有单独的refreshToken,暂时使用相同值 + } + await this.handleLoginInfo(userInfo as Api.Login.Info) } catch (e) { console.warn('[Login Error]:', e) diff --git a/src/store/router/helper.ts b/src/store/router/helper.ts index 06d2540..326a780 100644 --- a/src/store/router/helper.ts +++ b/src/store/router/helper.ts @@ -1,14 +1,36 @@ import type { MenuOption } from 'naive-ui' import type { RouteRecordRaw } from 'vue-router' +import { h } from 'vue' import { usePermission } from '@/hooks' import Layout from '@/layouts/index.vue' -import { $t, arrayToTree, renderIcon } from '@/utils' +import { arrayToTree, renderIcon } from '@/utils' import { clone, min, omit, pick } from 'radash' import { RouterLink } from 'vue-router' const metaFields: AppRoute.MetaKeys[] = ['title', 'icon', 'requiresAuth', 'roles', 'keepAlive', 'hide', 'order', 'href', 'activeMenu', 'withoutTab', 'pinTab', 'menuType'] +// 将后端菜单数据转换为前端路由数据 +function transformBackendToRoute(backendRoute: AppRoute.BackendRoute): AppRoute.RowRoute { + return { + id: backendRoute.menuId, + pid: backendRoute.parentId === 0 ? null : backendRoute.parentId, + name: backendRoute.name, + path: backendRoute.path, + componentPath: backendRoute.component || null, + redirect: backendRoute.redirect || undefined, + title: backendRoute.menuName, + icon: backendRoute.icon, + requiresAuth: true, // 动态路由都需要认证 + hide: backendRoute.isHide === '0', // 0-隐藏 1-显示 + keepAlive: backendRoute.isKeepAlive === '0', // 0-是 1-否 + pinTab: backendRoute.isAffix === '0', // 0-是 1-否 + activeMenu: backendRoute.activeMenu || undefined, + menuType: backendRoute.menuType as AppRoute.MenuType, + href: backendRoute.isLink === '0' ? backendRoute.path : undefined, // 如果是外链 + } +} + function standardizedRoutes(route: AppRoute.RowRoute[]) { return clone(route).map((i) => { const route = omit(i, metaFields) @@ -18,9 +40,20 @@ function standardizedRoutes(route: AppRoute.RowRoute[]) { }) as AppRoute.Route[] } -export function createRoutes(routes: AppRoute.RowRoute[]) { +// 处理路由数据的主函数 - 支持动态和静态路由以及混合模式 +export function createRoutes(routeData: (AppRoute.BackendRoute | AppRoute.RowRoute)[]) { const { hasPermission } = usePermission() + // 处理混合数据:分别处理后端数据和前端数据 + const backendRoutes = routeData.filter(item => 'menuId' in item) as AppRoute.BackendRoute[] + const frontendRoutes = routeData.filter(item => !('menuId' in item)) as AppRoute.RowRoute[] + + // 转换后端路由数据 + const transformedBackendRoutes = backendRoutes.map(transformBackendToRoute) + + // 合并所有路由 + const routes = [...frontendRoutes, ...transformedBackendRoutes] + // Structure the meta field let resultRouter = standardizedRoutes(routes) @@ -30,8 +63,43 @@ export function createRoutes(routes: AppRoute.RowRoute[]) { // Generate routes, no need to import files for those with redirect const modules = import.meta.glob('@/views/**/*.vue') resultRouter = resultRouter.map((item: AppRoute.Route) => { - if (item.componentPath && !item.redirect) - item.component = modules[`/src/views${item.componentPath}`] + if (item.componentPath && !item.redirect) { + // 对于动态路由,只有菜单类型才需要组件;对于静态路由,都需要组件 + const needComponent = item.meta.menuType === '2' || !item.meta.menuType + if (needComponent) { + // 处理组件路径,确保正确的路径格式 + let componentPath = item.componentPath + // 确保路径以 / 开头 + if (!componentPath.startsWith('/')) { + componentPath = `/${componentPath}` + } + // 确保路径以 .vue 结尾 + if (!componentPath.endsWith('.vue')) { + componentPath = `${componentPath}.vue` + } + const fullPath = `/src/views${componentPath}` + item.component = modules[fullPath] + + // 如果组件未找到,输出调试信息并提供默认组件 + if (!item.component) { + console.warn(`组件未找到: ${fullPath}`) + console.warn('可用组件路径:', Object.keys(modules).slice(0, 10)) // 只显示前10个避免日志过长 + + // 为找不到组件的页面提供一个默认的空页面组件 + item.component = () => h('div', { class: 'p-4' }, [ + h('div', { class: 'text-center text-gray-500' }, [ + h('h3', '页面开发中'), + h('p', `组件路径: ${fullPath}`), + h('p', '请联系开发人员创建对应的页面组件'), + ]), + ]) + } + } + else if (item.meta.menuType === '1') { + // 目录类型不需要组件,但需要确保有children + item.component = undefined + } + } return item }) @@ -41,7 +109,7 @@ export function createRoutes(routes: AppRoute.RowRoute[]) { const appRootRoute: RouteRecordRaw = { path: '/appRoot', name: 'appRoot', - redirect: import.meta.env.VITE_HOME_PATH, + redirect: import.meta.env.VITE_HOME_PATH || '/dashboard/monitor', component: Layout, meta: { title: '', @@ -91,11 +159,21 @@ function setRedirect(routes: AppRoute.Route[]) { } /* 生成侧边菜单的数据 */ -export function createMenus(userRoutes: AppRoute.RowRoute[]) { +export function createMenus(routeData: (AppRoute.BackendRoute | AppRoute.RowRoute)[]) { + // 处理混合数据:分别处理后端数据和前端数据 + const backendRoutes = routeData.filter(item => 'menuId' in item) as AppRoute.BackendRoute[] + const frontendRoutes = routeData.filter(item => !('menuId' in item)) as AppRoute.RowRoute[] + + // 转换后端路由数据 + const transformedBackendRoutes = backendRoutes.map(transformBackendToRoute) + + // 合并所有路由 + const userRoutes = [...frontendRoutes, ...transformedBackendRoutes] + const resultMenus = standardizedRoutes(userRoutes) // filter menus that do not need to be displayed - const visibleMenus = resultMenus.filter(route => !route.meta.hide) + const visibleMenus = resultMenus.filter(route => !route.meta.hide && route.meta.menuType !== '3') // 过滤按钮类型 // generate side menu return arrayToTree(transformAuthRoutesToMenus(visibleMenus)) @@ -123,7 +201,7 @@ function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) { id: item.id, pid: item.pid, label: - (!item.meta.menuType || item.meta.menuType === 'page') + (!item.meta.menuType || item.meta.menuType === '2') ? () => h( RouterLink, @@ -132,9 +210,9 @@ function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) { path: item.path, }, }, - { default: () => $t(`route.${String(item.name)}`, item.meta.title) }, + { default: () => item.meta.title }, ) - : () => $t(`route.${String(item.name)}`, item.meta.title), + : () => item.meta.title, key: item.path, icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined, } diff --git a/src/store/router/index.ts b/src/store/router/index.ts index 613c8fe..5158964 100644 --- a/src/store/router/index.ts +++ b/src/store/router/index.ts @@ -2,14 +2,15 @@ import type { MenuOption } from 'naive-ui' import { router } from '@/router' import { staticRoutes } from '@/router/routes.static' import { fetchUserRoutes } from '@/service' -import { useAuthStore } from '@/store/auth' -import { $t, local } from '@/utils' +import { $t } from '@/utils' +import { coiMsgError } from '@/utils/coi' import { createMenus, createRoutes, generateCacheRoutes } from './helper' interface RoutesStatus { isInitAuthRoute: boolean menus: MenuOption[] rowRoutes: AppRoute.RowRoute[] + backendRoutes: AppRoute.BackendRoute[] activeMenu: string | null cacheRoutes: string[] } @@ -20,6 +21,7 @@ export const useRouteStore = defineStore('route-store', { activeMenu: null, menus: [], rowRoutes: [], + backendRoutes: [], cacheRoutes: [], } }, @@ -38,50 +40,94 @@ export const useRouteStore = defineStore('route-store', { }, async initRouteInfo() { + // 始终加载静态路由(仪表盘等基础路由) + const allRoutes = [...staticRoutes] + if (import.meta.env.VITE_ROUTE_LOAD_MODE === 'dynamic') { - const userInfo = local.get('userInfo') + try { + // 获取动态路由并合并 + const { isSuccess, data } = await fetchUserRoutes() - if (!userInfo || !userInfo.id) { - const authStore = useAuthStore() - authStore.logout() - return + if (isSuccess && data) { + // 将动态路由添加到静态路由中 + const dynamicRoutes = Array.isArray(data) ? data : [] + console.warn('成功获取动态路由:', dynamicRoutes.length, '个') + + // 保持动态路由的原始ID关系,不需要修改ID,因为静态路由和动态路由可以共存 + const processedDynamicRoutes = dynamicRoutes + + return [...allRoutes, ...processedDynamicRoutes] + } + else { + console.warn('动态路由获取失败,只使用静态路由') + } + } + catch (error) { + console.error('动态路由获取异常,只使用静态路由:', error) } - - // Get user's route - const { data } = await fetchUserRoutes({ - id: userInfo.id, - }) - - if (!data) - return - - return data - } - else { - this.rowRoutes = staticRoutes - return staticRoutes } + + return allRoutes }, async initAuthRoute() { this.isInitAuthRoute = false // Initialize route information - const rowRoutes = await this.initRouteInfo() - if (!rowRoutes) { - window.$message.error($t(`app.getRouteError`)) + const routeData = await this.initRouteInfo() + if (!routeData) { + coiMsgError($t(`app.getRouteError`)) return } - this.rowRoutes = rowRoutes + + // 检查是否包含动态路由数据(通过是否有menuId字段判断) + const hasDynamicRoutes = routeData.some(item => 'menuId' in item) + + if (hasDynamicRoutes) { + // 混合模式:分离静态路由和动态路由 + const staticRouteData = routeData.filter(item => !('menuId' in item)) as AppRoute.RowRoute[] + const dynamicRouteData = routeData.filter(item => 'menuId' in item) as AppRoute.BackendRoute[] + + // 保存动态路由原始数据 + this.backendRoutes = dynamicRouteData + + // 转换动态路由为前端格式 + const transformedDynamicRoutes = dynamicRouteData.map(item => ({ + id: item.menuId, + pid: item.parentId === 0 ? null : item.parentId, + name: item.name, + path: item.path, + componentPath: item.component || null, + redirect: item.redirect || undefined, + title: item.menuName, + icon: item.icon, + requiresAuth: true, + hide: item.isHide === '0', + keepAlive: item.isKeepAlive === '0', + pinTab: item.isAffix === '0', + activeMenu: item.activeMenu || undefined, + menuType: item.menuType as AppRoute.MenuType, + href: item.isLink === '0' ? item.path : undefined, + } as AppRoute.RowRoute)) + + // 合并静态路由和转换后的动态路由 + this.rowRoutes = [...staticRouteData, ...transformedDynamicRoutes] + this.cacheRoutes = generateCacheRoutes(this.rowRoutes) + + console.warn('混合路由模式 - 静态路由:', staticRouteData.length, '动态路由:', transformedDynamicRoutes.length) + } + else { + // 纯静态路由模式 + this.rowRoutes = routeData as AppRoute.RowRoute[] + this.cacheRoutes = generateCacheRoutes(this.rowRoutes) + console.warn('静态路由模式 - 路由数量:', this.rowRoutes.length) + } // Generate actual route and insert - const routes = createRoutes(rowRoutes) + const routes = createRoutes(routeData) router.addRoute(routes) // Generate side menu - this.menus = createMenus(rowRoutes) - - // Generate the route cache - this.cacheRoutes = generateCacheRoutes(rowRoutes) + this.menus = createMenus(routeData) this.isInitAuthRoute = true },