修改 vite.config.ts 配置文件,追加 proxy 配置
server: {
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
ws: true,
rewrite: path => path.replace(/^\/api/, "")
}
},
},修改 src/utils/http/index.ts 追加基础请求路径,以配置上面的 proxy
const defaultConfig: AxiosRequestConfig = {
baseURL: "/api", // 追加这个配置
timeout: 10000,
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
},
paramsSerializer: {
serialize: stringify as unknown as CustomParamsSerializer
}
};创建 src/api/login/LoginApi.ts
import { http } from "@/utils/http";
export type SysUserLoginVo = {
success: boolean;
message: string;
code: string;
data: {
/** token */
token: string;
/** token 类型,如:Bearer */
tokenType: string;
/** 过期时间(单位:秒),如:604800 */
expiration: number;
/** 刷新 token */
refreshToken: string;
};
};
/**
* 登录
* @param data 参数名为 data 才能适配后端 Request Body
*/
export const login = (data?: object) => {
return http.request<SysUserLoginVo>("post", "/auth/login", { data });
};修改 src/store/modules/user.ts 并做好适配
// ****** 已有代码 ******
// 使用我们自己的接口
import { type SysUserLoginVo, login } from "@/api/login/LoginApi";
// ****** 已有代码 ******
export const useUserStore = defineStore("pure-user", {
state: (): userType => ({
// ****** 已有代码 ******
}),
actions: {
// ****** 已有代码 ******
async loginByUsername(data) {
// 使用我们自己的接口
return new Promise<SysUserLoginVo>((resolve, reject) => {
login(data)
.then(data => {
if (data?.success) {
// 适配前端 token 的数据结构
const tokenData = {
accessToken: data.data.token,
expires: new Date(Date.now() + data.data.expiration * 1000),
refreshToken: data.data.refreshToken
};
setToken(tokenData);
}
resolve(data);
})
.catch(error => {
reject(error);
});
});
},
// ****** 已有代码 ******
}
});
export function useUserStoreHook() {
return useUserStore(store);
}完成上面的修改后,点击登录如果报错并不会弹出错误提示
修改 src/utils/http/index.ts 在响应拦截器中追加异常输出
// 追加导入
import { message } from "@/utils/message";
private httpInterceptorsResponse(): void {
// 已有代码...
(error: PureHttpError) => {
const $error = error;
$error.isCancelRequest = Axios.isCancel($error);
// 追加代码:统一处理 HTTP 错误
if (error.response) {
const { status, data } = error.response;
// @ts-ignore
message(data?.message || `请求出错 ${status}`, { type: "error" });
}
// 所有的响应异常 区分来源为取消请求/非取消请求
return Promise.reject($error);
}
);
}登录成功后,前端模板需要加载后端的异步路由,此时我们没有这个接口会报错,修改 src/api/routes.ts
import { http } from "@/utils/http";
type Result = {
success: boolean;
data: Array<any>;
};
export const getAsyncRoutes = () => {
// 修改一下接口地址
return http.request<Result>("get", "/sys/menu/get-async-routes");
};修改 com.lusifer.crmeb.controller.SysMenuController 增加前端所需的路由菜单
package com.lusifer.crmeb.controller;
import com.lusifer.crmeb.component.security.util.SecurityUtils;
import com.lusifer.crmeb.entity.SysMenu;
import com.lusifer.crmeb.mapper.SysMenuMapper;
import com.lusifer.crmeb.rule.pojo.response.ResponseData;
import com.lusifer.crmeb.rule.pojo.response.SuccessResponseData;
import com.lusifer.crmeb.service.SysMenuService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.*;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 用户权限表 前端控制器
*
* @author Lusifer
* @since 2026-05-04
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/sys/menu")
@Tag(name = "菜单管理")
public class SysMenuController {
private final SysMenuService sysMenuService;
private final SysMenuMapper sysMenuMapper;
@Operation(summary = "获取异步路由")
@GetMapping("/get-async-routes")
public ResponseData<List<Map<String, Object>>> getAsyncRoutes() {
Long userId = SecurityUtils.getUserId();
log.info("========== 开始获取用户 {} 的异步路由 ==========", userId);
List<SysMenu> allMenus = sysMenuService.list();
log.info("数据库中共有 {} 个菜单", allMenus.size());
List<SysMenu> userMenus =
allMenus.stream()
.filter(menu -> menu.getStatusFlag() != null && menu.getStatusFlag() == '1')
.filter(menu -> menu.getType() != null && (menu.getType() == 0 || menu.getType() == 1))
.sorted(Comparator.comparingInt(SysMenu::getSort))
.collect(Collectors.toList());
log.info("过滤后用户可用菜单数量: {}", userMenus.size());
List<Map<String, Object>> routes = buildRoutes(userMenus, 0L);
log.info("构建完成的路由数量: {}", routes.size());
log.info("路由数据结构: {}", routes);
log.info("========== 异步路由获取完成 ==========");
return new SuccessResponseData<>(routes);
}
private List<Map<String, Object>> buildRoutes(List<SysMenu> menus, Long parentId) {
List<Map<String, Object>> routes = new ArrayList<>();
List<SysMenu> childMenus =
menus.stream()
.filter(menu -> menu.getPid() != null && menu.getPid().equals(parentId))
.sorted(Comparator.comparingInt(SysMenu::getSort))
.collect(Collectors.toList());
for (SysMenu menu : childMenus) {
Map<String, Object> route = new LinkedHashMap<>();
if (menu.getType() == 0) {
// 目录类型
String path = menu.getPath() != null ? menu.getPath() : "";
if (!path.startsWith("/")) {
path = "/" + path;
}
route.put("path", path);
Map<String, Object> meta = new LinkedHashMap<>();
meta.put("title", menu.getName() != null ? menu.getName() : "未命名");
if (menu.getIcon() != null && !menu.getIcon().isEmpty()) {
meta.put("icon", menu.getIcon());
}
if (menu.getSort() != null) {
meta.put("rank", menu.getSort());
}
meta.put("showLink", true);
route.put("meta", meta);
List<Map<String, Object>> children = buildRoutes(menus, menu.getMenuId());
if (!children.isEmpty()) {
route.put("children", children);
}
} else {
// 菜单类型
String path = menu.getPath() != null ? menu.getPath() : "";
if (!path.startsWith("/")) {
path = "/" + path;
}
route.put("path", path);
route.put("name", menu.getName() != null ? menu.getName() : "");
// component 字段:只有当 uri 不为空时才添加
if (menu.getUri() != null && !menu.getUri().isEmpty()) {
route.put("component", menu.getUri());
}
Map<String, Object> meta = new LinkedHashMap<>();
meta.put("title", menu.getName() != null ? menu.getName() : "未命名");
if (menu.getIcon() != null && !menu.getIcon().isEmpty()) {
meta.put("icon", menu.getIcon());
}
if (menu.getSort() != null) {
meta.put("rank", menu.getSort());
}
meta.put("showLink", true);
route.put("meta", meta);
}
routes.add(route);
}
return routes;
}
}