From f22b0dfcbc82e7376b0e5e21f706c83869148f30 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 31 Dec 2025 18:57:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E8=81=94=E8=B0=83=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 16 ++ config.js | 10 +- src/api/index.ts | 13 +- src/assets/images/logo.png | Bin 0 -> 11461 bytes src/components/commonFilter/index.vue | 70 ++++++ src/components/overflowTabs/index.vue | 220 +++++++++++++++++ src/components/stageBreadcrumbs/index.vue | 3 + src/mock/menu.ts | 34 +-- src/pages/Layout/index.vue | 27 ++- src/pages/Layout/rightMenuGroup.vue | 197 +++++++++++++++ src/pages/Login/index.vue | 56 ++--- src/pages/stage/dict/dictManage.vue | 121 ++++++++++ src/pages/stage/dict/index.vue | 63 +++++ src/pages/stage/flow/index.vue | 13 + src/pages/stage/origanization/AuditLogs.vue | 169 +++++++++++++ .../origanization/OrganizationDetail.vue | 224 ++++++++++++++++++ .../origanization}/index.vue | 51 ++-- src/pages/stage/permission/index.vue | 13 + src/pages/stage/personnel/index.vue | 13 + src/request/index.ts | 179 ++++++++------ src/router/index.ts | 116 +++++---- src/store/modules/user.ts | 15 +- src/styles/common.scss | 15 ++ src/styles/element.scss | 24 +- src/styles/stage.scss | 43 ++-- src/utils/storage.ts | 33 +++ vite.config.ts | 2 +- 27 files changed, 1514 insertions(+), 226 deletions(-) create mode 100644 src/assets/images/logo.png create mode 100644 src/components/commonFilter/index.vue create mode 100644 src/components/overflowTabs/index.vue create mode 100644 src/pages/Layout/rightMenuGroup.vue create mode 100644 src/pages/stage/dict/dictManage.vue create mode 100644 src/pages/stage/dict/index.vue create mode 100644 src/pages/stage/flow/index.vue create mode 100644 src/pages/stage/origanization/AuditLogs.vue create mode 100644 src/pages/stage/origanization/OrganizationDetail.vue rename src/pages/{StageManage/organization => stage/origanization}/index.vue (74%) create mode 100644 src/pages/stage/permission/index.vue create mode 100644 src/pages/stage/personnel/index.vue create mode 100644 src/utils/storage.ts diff --git a/components.d.ts b/components.d.ts index 2ac19fe..ce1a001 100644 --- a/components.d.ts +++ b/components.d.ts @@ -12,12 +12,19 @@ export {} declare module 'vue' { export interface GlobalComponents { CardItem: typeof import('./src/components/cardItem/index.vue')['default'] + CommonFilter: typeof import('./src/components/commonFilter/index.vue')['default'] ElAside: typeof import('element-plus/es')['ElAside'] + ElAvatar: typeof import('element-plus/es')['ElAvatar'] + ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCol: typeof import('element-plus/es')['ElCol'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDatePick: typeof import('element-plus/es')['ElDatePick'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] + ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] + ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] @@ -32,11 +39,20 @@ declare module 'vue' { ElMain: typeof import('element-plus/es')['ElMain'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElPopover: typeof import('element-plus/es')['ElPopover'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTree: typeof import('element-plus/es')['ElTree'] + OverflowTabs: typeof import('./src/components/overflowTabs/index.vue')['default'] PageForm: typeof import('./src/components/pageForm/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] diff --git a/config.js b/config.js index 214efe0..a006adf 100644 --- a/config.js +++ b/config.js @@ -1,3 +1,9 @@ +/** + * 项目配置文件 + * VITE_PROJECT_PREFIX 项目前缀,默认是'/' + * VITE_PUBLIC_PATH 当前项目的basePath默认是'/' + * VITE_APP_BASE_API 请求接口地址域名 + * */ export const VITE_PROJECT_PREFIX = '/'; -export const VITE_PUBLIC_PATH = './'; -export const VITE_APP_BASE_API = 'http://api.test.com'; \ No newline at end of file +export const VITE_PUBLIC_PATH = '/'; +export const VITE_APP_BASE_API = 'https://mversion-dev.zzmjart.com/api' //'http://192.168.42.106'; \ No newline at end of file diff --git a/src/api/index.ts b/src/api/index.ts index 8487b86..e95d9b5 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,18 +1,17 @@ import request from '@/request'; -// 设置请求的参数信息 -export const getUserList = (params?: any) => { - return request.get('/api/user/list', params); -}; - // 获取路由菜单数据 export const getRouteMenus = () => { - return request.get('/api/menus'); + return request.get('/auth/v1/backend/menu'); }; // 登录接口 export const login = (data: { username: string; password: string }) => { - return request.post('/api/auth/login', data); + return request.post('/auth/oauth2/token', data,{ + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); }; // 获取用户信息 diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca3ee08c76de39c71f4bcf8f661bdebb426ada4 GIT binary patch literal 11461 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 +
+
+ + + +
+ +
+
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/src/components/overflowTabs/index.vue b/src/components/overflowTabs/index.vue new file mode 100644 index 0000000..a28ef09 --- /dev/null +++ b/src/components/overflowTabs/index.vue @@ -0,0 +1,220 @@ + + + + + \ No newline at end of file diff --git a/src/components/stageBreadcrumbs/index.vue b/src/components/stageBreadcrumbs/index.vue index d89d084..bcda1ae 100644 --- a/src/components/stageBreadcrumbs/index.vue +++ b/src/components/stageBreadcrumbs/index.vue @@ -26,10 +26,13 @@ const { title } = defineProps<{ justify-content: space-between; :deep(.mj-panel-title){ margin-bottom: 0; + flex-shrink: 0; } .stage-breadcrumbs-content{ flex: 1; + display: flex; + align-items: center; &::before{ content:''; display: inline-block; diff --git a/src/mock/menu.ts b/src/mock/menu.ts index b963ba9..6abacd1 100644 --- a/src/mock/menu.ts +++ b/src/mock/menu.ts @@ -46,7 +46,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "clue", path: "clue", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackBackstageManage/organization/index.vue", meta: { title: "线索管理", icon: "", @@ -58,7 +58,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "customer", path: "customer", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "客户管理", icon: "", @@ -69,7 +69,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "studio", path: "studio", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "游戏与工作室", icon: "", @@ -80,7 +80,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "businessmanage", path: "businessmanage", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "商机管理", icon: "", @@ -103,7 +103,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "income", path: "income", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "收入合同", icon: "", @@ -126,7 +126,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "requirement", path: "requirement", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "需求管理", icon: "", @@ -137,7 +137,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "projectmanage", path: "projectmanage", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "项目管理", icon: "", @@ -148,7 +148,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "task", path: "task", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "任务管理", icon: "", @@ -159,7 +159,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "work", path: "work", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "工时管理", icon: "", @@ -193,7 +193,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "resume", path: "resume", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "流程管理", icon: "", @@ -204,7 +204,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "push", path: "push", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "推送管理", icon: "", @@ -216,7 +216,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "job", path: "job", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "主场岗位", icon: "", @@ -240,7 +240,7 @@ export const mockMenuData: MockMenuRoute[] = [ { path: "flow", name: "flow", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "流程管理", icon: "", @@ -250,7 +250,7 @@ export const mockMenuData: MockMenuRoute[] = [ }, { name: "organization", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", path: "organization", meta: { title: "组织管理", @@ -262,7 +262,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "personnel", path: "personnel", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "人员管理", icon: "", @@ -273,7 +273,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "permission", path: "permission", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/organization/index.vue", meta: { title: "权限管理", icon: "", @@ -284,7 +284,7 @@ export const mockMenuData: MockMenuRoute[] = [ { name: "dict", path: "dict", - component: "@/pages/StageManage/organization/index.vue", + component: "@/pages/BackstageManage/dict/index.vue", meta: { title: "字典管理", icon: "", diff --git a/src/pages/Layout/index.vue b/src/pages/Layout/index.vue index bd7ef05..3d08a19 100644 --- a/src/pages/Layout/index.vue +++ b/src/pages/Layout/index.vue @@ -4,7 +4,9 @@
-
+
{{ topTitle }} @@ -25,12 +27,15 @@ + + + @@ -44,6 +49,8 @@ import mjMenus from "@/components/standardMenu/index.vue"; import { useUserStore } from "@/store"; import { DArrowLeft, DArrowRight } from "@element-plus/icons-vue"; +import rightMenuGroup from './rightMenuGroup.vue'; +import companyLogo from '@/assets/images/logo.png'; defineOptions({ name: "Layout", }); @@ -212,10 +219,18 @@ onUnmounted(() => { flex-direction: column; border-right: 1px solid var(--mj-border-color); - .mj-aside-company { + .mj-aside-logo { height: var(--mj-menu-header-height); + line-height: var(--mj-menu-header-height); border-bottom: 1px solid var(--mj-border-color); flex-shrink: 0; + .mj-company-logo{ + width: 39px; + height: 32px; + display: inline-block; + vertical-align: middle; + margin-left: 20px; + } } .mj-aside-menu { @@ -245,6 +260,14 @@ onUnmounted(() => { } } + .mj-header-content{ + display: flex; + justify-content: space-between; + .mj-standard-menu{ + flex: 1; + } + } + .mj-header-content { --el-header-padding: 0; border-bottom: 1px solid var(--mj-border-color); diff --git a/src/pages/Layout/rightMenuGroup.vue b/src/pages/Layout/rightMenuGroup.vue new file mode 100644 index 0000000..0052ebf --- /dev/null +++ b/src/pages/Layout/rightMenuGroup.vue @@ -0,0 +1,197 @@ + + + diff --git a/src/pages/Login/index.vue b/src/pages/Login/index.vue index ad4ddce..4e02361 100644 --- a/src/pages/Login/index.vue +++ b/src/pages/Login/index.vue @@ -52,9 +52,6 @@
-
-
- - - - -
+ + + + + + + +
@@ -52,13 +56,26 @@ + \ No newline at end of file diff --git a/src/pages/stage/personnel/index.vue b/src/pages/stage/personnel/index.vue new file mode 100644 index 0000000..aaa49fc --- /dev/null +++ b/src/pages/stage/personnel/index.vue @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/src/request/index.ts b/src/request/index.ts index 061dc2f..43fad20 100644 --- a/src/request/index.ts +++ b/src/request/index.ts @@ -1,15 +1,20 @@ import axios from "axios"; -import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -import { ElNotification } from 'element-plus'; -import { getMockData, shouldUseMock } from '@/mock' //mock数据信息 - - -const baseUrl = import.meta.env.VITE_APP_BASE_API; +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; +import { ElNotification } from "element-plus"; +import { VITE_APP_BASE_API } from "../../config.js"; +import TokenManager from "@/utils/storage"; +import { getMockData, shouldUseMock } from "@/mock"; //mock数据信息 +const tokenManager = TokenManager.getInstance(); +const baseUrl = "/api"; //TODO: 本地调试需要修改为/api // 1. 锁和队列定义在类外部,确保全局唯一 let isRefreshing = false; let requestsQueue: Array<(token: string) => void> = []; +// 登录接口 传递参数不一样 +const AUTH_OAUTH2_TOKEN_URL = "/auth/oauth2/token"; +const CLIENT_CREDENTIALS = "Basic " + window.btoa("mversion:secret"); + class HttpRequest { private instance: AxiosInstance; @@ -24,16 +29,24 @@ class HttpRequest { } // --- Token 管理方法 --- - private getAccessToken() { return localStorage.getItem("accessToken"); } - private getRefreshToken() { return localStorage.getItem("refreshToken"); } + private getAccessToken() { + return tokenManager.getToken("accessToken"); + } + private getRefreshToken() { + return tokenManager.getToken("refreshToken"); + } private clearTokens() { - localStorage.removeItem("accessToken"); - localStorage.removeItem("refreshToken"); + tokenManager.clearStorage(); // 这里可以触发跳转登录逻辑,例如:router.push('/login') } private setTokens(data: any) { - localStorage.setItem("accessToken", data.accessToken); - localStorage.setItem("refreshToken", data.refreshToken); + tokenManager.setToken("accessToken", data.accessToken); + tokenManager.setToken("refreshToken", data.refreshToken); + } + + // 判断是否为认证接口 + private isAuthEndpoint(url: string): boolean { + return url === AUTH_OAUTH2_TOKEN_URL; } private setupInterceptors() { @@ -41,7 +54,11 @@ class HttpRequest { this.instance.interceptors.request.use( (config) => { const token = this.getAccessToken(); - if (token && config.headers) { + + // 如果login接口传递clientId参数 + if (this.isAuthEndpoint(config.url || "")) { + config.headers.Authorization = CLIENT_CREDENTIALS; + } else if (token && config.headers) { config.headers.Authorization = `Bearer ${token}`; } return config; @@ -54,12 +71,16 @@ class HttpRequest { async (response: AxiosResponse) => { const { data: res, config: originalRequest } = response; + console.log("响应拦截器",res,originalRequest) + // TODO:如果是登录接口就不要走全局的拦截 而是直接返回当前的数据信息 + if (this.isAuthEndpoint(originalRequest.url || "")) { + return res; + } // 业务成功直接返回数据 - if (res.code === 0) return res; + if (res.code === 200) return res.data; - // 重点:401 未授权处理 + // 401 未授权处理 if (res.code === 401) { - // 如果已经在刷新中了,将请求挂起并加入队列 if (isRefreshing) { return new Promise((resolve) => { @@ -73,30 +94,32 @@ class HttpRequest { // 开始刷新流程 isRefreshing = true; const rToken = this.getRefreshToken(); - + if (!rToken) { this.clearTokens(); return Promise.reject(res); } try { - // 使用一个干净的 axios 实例去发刷新请求,避免拦截器死循环 - const refreshRes = await axios.post(`${this.instance.defaults.baseURL}/auth/refresh`, { - refreshToken: rToken - }); - + // TODO:使用一个干净的 axios 实例去发刷新请求,避免拦截器死循环 + const refreshRes = await axios.post( + `${this.instance.defaults.baseURL}/auth/refresh`, + { + refreshToken: rToken, + } + ); + const newToken = refreshRes.data.data.accessToken; this.setTokens(refreshRes.data.data); // 刷新成功:释放队列 - requestsQueue.forEach(callback => callback(newToken)); + requestsQueue.forEach((callback) => callback(newToken)); requestsQueue = []; isRefreshing = false; // 重试本次请求 originalRequest.headers!.Authorization = `Bearer ${newToken}`; return this.instance(originalRequest); - } catch (error) { // 刷新失败:清理并彻底报错 isRefreshing = false; @@ -107,7 +130,7 @@ class HttpRequest { } // 其它业务错误 - ElNotification.error({ title: '提示', message: res.msg || '服务异常' }); + ElNotification.error({ title: "提示", message: res.msg || "服务异常" }); return Promise.reject(res); }, (error) => { @@ -121,63 +144,75 @@ class HttpRequest { } public request(config: AxiosRequestConfig): Promise> { - // 检查是否应该使用 Mock 数据 - const requestUrl = config.url || ''; - - // 优先使用请求 URL(通常是相对路径,如 /api/menus) - // 这样可以直接匹配 mockApiMap 中的 key - if (shouldUseMock(requestUrl)) { - const mockData = getMockData(requestUrl, config.params, config.data); - if (mockData) { - console.log(`[Mock] 使用 Mock 数据: ${requestUrl}`, mockData); - // 模拟网络延迟 - return new Promise((resolve) => { - setTimeout(() => { - resolve(mockData as ApiResponse); - }, 100); - }); - } - } - + // TODO:检查是否应该使用 Mock 数据 + // const requestUrl = config.url || ''; + // if (shouldUseMock(requestUrl)) { + // const mockData = getMockData(requestUrl, config.params, config.data); + // if (mockData) { + // console.log(`[Mock] 使用 Mock 数据: ${requestUrl}`, mockData); + // // 模拟网络延迟 + // return new Promise((resolve) => { + // setTimeout(() => { + // resolve(mockData as ApiResponse); + // }, 100); + // }); + // } + // } + return this.instance(config) as unknown as Promise>; } - // GET 请求 - public get(url: string, params?: any, config?: AxiosRequestConfig): Promise> { - return this.request({ - url, - method: 'get', - params, - ...config + // GET 请求 + public get( + url: string, + params?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.request({ + url, + method: "get", + params, + ...config, }); } // POST 请求 - public post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { - return this.request({ - url, - method: 'post', - data, - ...config + public post( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.request({ + url, + method: "post", + data, + ...config, }); } // PUT 请求 - public put(url: string, data?: any, config?: AxiosRequestConfig): Promise> { - return this.request({ - url, - method: 'put', - data, - ...config + public put( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.request({ + url, + method: "put", + data, + ...config, }); } // DELETE 请求 - public delete(url: string, config?: AxiosRequestConfig): Promise> { - return this.request({ - url, - method: 'delete', - ...config + public delete( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.request({ + url, + method: "delete", + ...config, }); } } @@ -186,13 +221,13 @@ const httpRequest = new HttpRequest(baseUrl); // 导出方法 export const request = { - get: (url: string, params?: any, config?: AxiosRequestConfig) => + get: (url: string, params?: any, config?: AxiosRequestConfig) => httpRequest.get(url, params, config), - post: (url: string, data?: any, config?: AxiosRequestConfig) => + post: (url: string, data?: any, config?: AxiosRequestConfig) => httpRequest.post(url, data, config), - put: (url: string, data?: any, config?: AxiosRequestConfig) => + put: (url: string, data?: any, config?: AxiosRequestConfig) => httpRequest.put(url, data, config), - delete: (url: string, config?: AxiosRequestConfig) => + delete: (url: string, config?: AxiosRequestConfig) => httpRequest.delete(url, config), }; -export default httpRequest; \ No newline at end of file +export default httpRequest; diff --git a/src/router/index.ts b/src/router/index.ts index d1629b1..032af76 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,13 +1,12 @@ import { createWebHistory, createRouter, type RouteRecordRaw } from 'vue-router' import { useUserStore } from '@/store' import { getRouteMenus } from '@/api' -import useTokenRefresh from '@/hooks/useTokenRefresh' +import TokenManager from '@/utils/storage'; import Login from '@/pages/Login/index.vue'; import HomeView from '@/pages/Layout/index.vue'; -const baseUrl = import.meta.env.VITE_APP_BASE_API || ''; - +const tokenManager = TokenManager.getInstance(); // 基础路由(不需要权限验证) const constantRoutes: RouteRecordRaw[] = [ { @@ -27,7 +26,7 @@ const asyncRoutes: RouteRecordRaw[] = [ path: '/', name: 'Layout', component: HomeView, - redirect: '/home', + // redirect: '/home', meta: { requiresAuth: true, }, @@ -57,7 +56,7 @@ const loadComponent = (componentPath: string) => { fullPath = componentPath } else if (componentPath.includes('/')) { // 补全路径,确保以 @/pages 开头 - fullPath = componentPath.startsWith('pages/') ? `@/${componentPath}` : `@/pages/${componentPath}` + fullPath = componentPath.startsWith('pages/') ? `@/${componentPath}` : `@/pages/${componentPath}/index.vue` } else { // 补全 index.vue fullPath = `@/pages/${componentPath}/index.vue` @@ -76,34 +75,35 @@ const loadComponent = (componentPath: string) => { } // 将后端返回的路由数据转换为 Vue Router 路由 -const transformRoutes = (routes: any[]): RouteRecordRaw[] => { - return routes.map((route) => { - const component = route.component ? loadComponent(route.component) : undefined - // 构建基础路由对象 - const routeRecord: any = { - path: route.path, - name: route.name || route.path, - meta: { - title: route.meta?.title || route.title || route.name, - icon: route.meta?.icon || route.icon, - requiresAuth: route.meta?.requiresAuth !== false, // 默认需要权限 - roles: route.meta?.roles || route.roles, - ...route.meta, - }, - } - - // 如果有组件,添加组件属性 - if (component) { - routeRecord.component = component - } - - // 处理子路由 +const transformRoutes = (routes: any[], parentCode: string = ''): RouteRecordRaw[] => { + return routes.flatMap((route) => { + const fullCode = parentCode ? `${parentCode}/${route.code}` : route.code; + + // 如果当前路由有子路由,说明它是一个路由前缀,不需要组件 if (route.children && route.children.length > 0) { - routeRecord.children = transformRoutes(route.children) - } + // 将子路由的路径加上当前路由的前缀,然后递归处理 + return transformRoutes(route.children, fullCode); + } else { + // 叶子节点才需要组件和路由配置 + const component = fullCode ? loadComponent(fullCode) : undefined; + + const routeRecord: any = { + path: route.code, + name: route.code, + meta: { + title: route.name, + icon: route.icon, + ...route.meta, + }, + } - return routeRecord as RouteRecordRaw - }) + if (component) { + routeRecord.component = component; + } + + return routeRecord as RouteRecordRaw; + } + }); } // 添加动态路由 @@ -116,15 +116,50 @@ const addDynamicRoutes = async () => { } try { - // 从后端获取路由菜单数据 - const response = await getRouteMenus() - if (response.code === 0 && response.data) { + // TODO:从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单) + let response:any; + if (userStore.isBackendUser) { + const backendResponse = await getRouteMenus(); + response = [{ + code: 'stage', + name: '后台管理', + icon: '', + children: backendResponse, + }]; + }else{ + // response = await getUserMenus(); + response = []; + } + if (response) { + const processRoutes = (routes: any[], prefix: string = ''): RouteRecordRaw[] => { + return routes.flatMap(route => { + const currentPath = prefix ? `${prefix}/${route.code}` : route.code; + + if (route.children && route.children.length > 0) { + // 如果有子路由,递归处理并添加当前路径作为前缀 + return processRoutes(route.children, currentPath); + } else { + // 叶子节点,创建路由记录 + const component = loadComponent(currentPath); + return { + path: route.code, + name: route.code, + component: component || HomeView, // 使用Layout的组件 + meta: { + title: route.name, + icon: route.icon, + ...route.meta, + } + } as RouteRecordRaw; + } + }); + }; // 转换路由数据 - const dynamicRoutes = transformRoutes(Array.isArray(response.data) ? response.data : [response.data]) + // const dynamicRoutes = transformRoutes(Array.isArray(response) ? response : [response]) + const dynamicRoutes = processRoutes(Array.isArray(response) ? response : [response]) // 将动态路由添加到 Layout 的 children 中 const layoutRoute = router.getRoutes().find(route => route.name === 'Layout') - console.log('Layout route:', layoutRoute,dynamicRoutes) if (layoutRoute) { dynamicRoutes.forEach(route => { router.addRoute('Layout', route) @@ -135,15 +170,14 @@ const addDynamicRoutes = async () => { router.addRoute(route) }) } - + console.log('Layout route:', router.getRoutes()) // 保存路由数据到 store - userStore.setRoutes(response.data) + userStore.setRoutes(response) // 标记路由已加载 userStore.isRoutesLoaded = true } } catch (error) { - console.error('Failed to load routes:', error) // 如果获取路由失败,清除用户数据并跳转到登录页 userStore.clearUserData() router.push('/login') @@ -153,10 +187,10 @@ const addDynamicRoutes = async () => { // 路由导航守卫 router.beforeEach(async (to, _from, next) => { const userStore = useUserStore() - const { getAccessToken } = useTokenRefresh(baseUrl) + const accessToken = tokenManager.getToken('accessToken'); // 获取 token - const token = getAccessToken() || userStore.token + const token = accessToken || userStore.token // 如果已登录,更新 store 中的 token if (token) { diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 03a7d1c..2425b07 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,7 +1,7 @@ import { defineStore } from "pinia"; -import useTokenRefresh from "@/hooks/useTokenRefresh"; +import TokenManager from '@/utils/storage'; +const tokenManager = TokenManager.getInstance(); -const baseUrl = import.meta.env.VITE_APP_BASE_API || ""; interface UserInfo { name?: string; @@ -25,12 +25,14 @@ interface RouteMenu { const useUserStore = defineStore("user", { state: () => { - const { getAccessToken } = useTokenRefresh(baseUrl); + const accessToken = tokenManager.getToken('accessToken'); + const userInfo = tokenManager.getToken('userInfo'); return { - token: getAccessToken() || "", - userInfo: {} as UserInfo, + token: accessToken || "", + userInfo: userInfo ? JSON.parse(userInfo) : {}, routes: [] as RouteMenu[], isRoutesLoaded: false, // 标记路由是否已加载 + isBackendUser:true, //标记是否是后台用户 }; }, getters: { @@ -54,8 +56,7 @@ const useUserStore = defineStore("user", { this.userInfo = {}; this.routes = []; this.isRoutesLoaded = false; - const { clearTokens } = useTokenRefresh(baseUrl); - clearTokens(); + tokenManager.clearStorage(); }, }, }); diff --git a/src/styles/common.scss b/src/styles/common.scss index 6fb9da3..0b106ba 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -19,4 +19,19 @@ html,body{ } +.filter-popper.el-popover.el-popper{ + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); +} + +// 全局重新element相关样式 +.mj-input-form{ + .el-input{ + --el-border-radius-base:10px; + --el-border-color:#E2E8F0; + } +} + + diff --git a/src/styles/element.scss b/src/styles/element.scss index 298e17a..4858def 100644 --- a/src/styles/element.scss +++ b/src/styles/element.scss @@ -23,11 +23,19 @@ // 标注弹窗样式 .standard-ui-dialog{ - --el-dialog-padding-primary:0; - --el-dialog-inset-padding-primary:16px; + &.el-dialog{ + --el-dialog-inset-padding-primary:16px; + --el-dialog-padding-primary:0; + --el-dialog-border-radius:16px; + --el-dialog-bg-header-footer:#FBFCFD; + --el-dialog-border-header-footer-color:#E5E7EB; + padding: var(--el-dialog-padding-primary); + } .el-dialog__header{ - border-bottom: 1px solid #E5E7EB; + border-bottom: 1px solid var(--el-dialog-border-header-footer-color); + background-color:var(--el-dialog-bg-header-footer); padding: var(--el-dialog-inset-padding-primary); + border-radius: var(--el-dialog-border-radius) var(--el-dialog-border-radius) 0 0; } .el-dialog__headerbtn{ height: 60px; @@ -37,12 +45,8 @@ } .el-dialog__footer{ padding: var(--el-dialog-inset-padding-primary); + background-color: var(--el-dialog-bg-header-footer); + border-top: 1px solid var(--el-dialog-border-header-footer-color); + border-radius: 0 0 var(--el-dialog-border-radius) var(--el-dialog-border-radius); } -} - - -// 全局重新element相关样式 -.el-input{ - --el-border-radius-base:10px; - --el-border-color:#E2E8F0; } \ No newline at end of file diff --git a/src/styles/stage.scss b/src/styles/stage.scss index 5703cee..bdf31c8 100644 --- a/src/styles/stage.scss +++ b/src/styles/stage.scss @@ -1,30 +1,35 @@ - - -.mj-panel-title{ +.mj-panel-title { font-size: 15px; font-weight: bold; - color:#1D293D; + color: #1D293D; + display: flex; + align-items: center; margin-bottom: 16px; - &::before{ - content:""; + + &::before { + content: ""; width: 4px; height: 16px; background-color: #155DFC; - display: inline-block; - vertical-align: middle; - border-radius: 3px; + border-radius: 2px; margin-right: 8px; - margin-bottom: 2px; } } -.mj-panel_header{ - height: 54px; - padding: 0 24px; - box-sizing: border-box; - border-bottom: 1px solid #F1F5F9; - .el-tabs{ - --el-tabs-header-height:54px; - --el-border-color-light:transparent; - } +.mj-panel_header { + height: 54px; + padding: 0 24px; + box-sizing: border-box; + border-bottom: 1px solid #F1F5F9; + + .el-tabs { + --el-tabs-header-height: 54px; + --el-border-color-light: transparent; + } +} + +// 自定义组件中overflow-tabs高亮样式 +.is-active-item-overflow-tabs { + color: #409eff; + font-weight: bold; } \ No newline at end of file diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 0000000..b459f19 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,33 @@ +class TokenManager { + private static instance: TokenManager | null = null; + private storage: Storage; + + private constructor(storageType: 'localStorage' | 'sessionStorage' = 'localStorage') { + this.storage = storageType === 'localStorage' ? localStorage : sessionStorage; + } + + public static getInstance(storageType: 'localStorage' | 'sessionStorage' = 'localStorage'): TokenManager { + if (!TokenManager.instance) { + TokenManager.instance = new TokenManager(storageType); + } + return TokenManager.instance; + } + + setToken(key: string, token: string): void { + this.storage.setItem(key, token); + } + + getToken(key: string): string | null { + return this.storage.getItem(key); + } + + removeToken(key: string): void { + this.storage.removeItem(key); + } + clearStorage(): void { + this.storage.clear(); + } +} + + +export default TokenManager; \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index cfe9845..0702ad4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -44,7 +44,7 @@ export default defineConfig(({ mode }) => { "/api": { target: VITE_APP_BASE_API, changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, ""), + rewrite: (path) => path.replace(/^\/api/, "") }, }, },