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 0000000..7ca3ee0
Binary files /dev/null and b/src/assets/images/logo.png differ
diff --git a/src/components/commonFilter/index.vue b/src/components/commonFilter/index.vue
new file mode 100644
index 0000000..d70c4bf
--- /dev/null
+++ b/src/components/commonFilter/index.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
-
+
+
![]()
+
@@ -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/, "")
},
},
},