mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 13:05:18 +00:00
发布v2.0.0分支
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import type { App } from "vue";
|
||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
||||
|
||||
// 注册所有图标
|
||||
export function setupElIcons(app: App<Element>) {
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { App } from "vue";
|
||||
|
||||
import { setupI18n } from "@/lang";
|
||||
import { setupRouter } from "@/router";
|
||||
import { setupStore } from "@/store";
|
||||
import { setupElIcons } from "./icons";
|
||||
import { setupPermission } from "./permission";
|
||||
import { InstallCodeMirror } from "codemirror-editor-vue3";
|
||||
import { setupVxeTable } from "./vxeTable";
|
||||
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
// 路由(router)
|
||||
setupRouter(app);
|
||||
// 状态管理(store)
|
||||
setupStore(app);
|
||||
// 国际化
|
||||
setupI18n(app);
|
||||
// Element-plus图标
|
||||
setupElIcons(app);
|
||||
// 路由守卫
|
||||
setupPermission();
|
||||
// vxe-table
|
||||
setupVxeTable(app);
|
||||
// 注册 CodeMirror
|
||||
app.use(InstallCodeMirror);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
import type { NavigationGuardNext, RouteLocationNormalized, RouteRecordRaw } from "vue-router";
|
||||
import NProgress from "@/utils/nprogress";
|
||||
import { Auth } from "@/utils/auth";
|
||||
import router from "@/router";
|
||||
import { usePermissionStore, useUserStore } from "@/store";
|
||||
|
||||
// 路由生成锁,防止重复生成
|
||||
let isGeneratingRoutes = false;
|
||||
|
||||
export function setupPermission() {
|
||||
// 白名单路由
|
||||
const whiteList = ["/login"];
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
|
||||
const isLoggedIn = Auth.isLoggedIn();
|
||||
|
||||
if (isLoggedIn) {
|
||||
// 如果已登录但访问登录页,重定向到首页
|
||||
if (to.path === "/login") {
|
||||
next({ path: "/" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理已登录用户的路由访问
|
||||
await handleAuthenticatedUser(to, from, next);
|
||||
} else {
|
||||
console.log("❌ User not logged in");
|
||||
|
||||
// 未登录用户的处理
|
||||
if (whiteList.includes(to.path)) {
|
||||
next();
|
||||
} else {
|
||||
redirectToLogin(to, next);
|
||||
NProgress.done();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 后置守卫,确保进度条关闭
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理已登录用户的路由访问
|
||||
*/
|
||||
async function handleAuthenticatedUser(
|
||||
to: RouteLocationNormalized,
|
||||
from: RouteLocationNormalized,
|
||||
next: NavigationGuardNext
|
||||
) {
|
||||
const permissionStore = usePermissionStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
try {
|
||||
// 检查用户信息是否存在
|
||||
if (!userStore.basicInfo.username) {
|
||||
await userStore.getUserInfo();
|
||||
}
|
||||
|
||||
// 检查路由是否已生成
|
||||
if (!permissionStore.routesLoaded) {
|
||||
// 防止重复生成路由
|
||||
if (isGeneratingRoutes) {
|
||||
console.log("⏳ Routes already generating, waiting...");
|
||||
// 等待当前路由生成完成
|
||||
await waitForRoutesGeneration(permissionStore);
|
||||
} else {
|
||||
await generateAndAddRoutes(permissionStore);
|
||||
}
|
||||
|
||||
// 路由生成完成后,重新导航到目标路由
|
||||
next({ ...to, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 路由已加载,检查路由是否存在
|
||||
if (to.matched.length === 0) {
|
||||
next("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
// 动态设置页面标题
|
||||
const title = (to.params.title as string) || (to.query.title as string);
|
||||
if (title) {
|
||||
to.meta.title = title;
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error("❌ Route guard error:", error);
|
||||
|
||||
// 出错时重置状态并重定向到登录页
|
||||
await resetUserStateAndRedirect(to, next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并添加动态路由
|
||||
*/
|
||||
async function generateAndAddRoutes(permissionStore: any) {
|
||||
isGeneratingRoutes = true;
|
||||
|
||||
try {
|
||||
const dynamicRoutes = await permissionStore.generateRoutes();
|
||||
|
||||
// 添加路由到路由器
|
||||
dynamicRoutes.forEach((route: RouteRecordRaw) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
} finally {
|
||||
isGeneratingRoutes = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待路由生成完成
|
||||
*/
|
||||
async function waitForRoutesGeneration(permissionStore: any): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (!isGeneratingRoutes && permissionStore.routesLoaded) {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// 超时保护,最多等待5秒
|
||||
setTimeout(() => {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置用户状态并重定向到登录页
|
||||
*/
|
||||
async function resetUserStateAndRedirect(to: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||
try {
|
||||
await useUserStore().resetAllState();
|
||||
redirectToLogin(to, next);
|
||||
} catch (resetError) {
|
||||
console.error("❌ Failed to reset user state:", resetError);
|
||||
// 强制跳转到登录页
|
||||
next("/login");
|
||||
} finally {
|
||||
NProgress.done();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向到登录页
|
||||
*/
|
||||
function redirectToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||
const params = new URLSearchParams(to.query as Record<string, string>);
|
||||
const queryString = params.toString();
|
||||
const redirect = queryString ? `${to.path}?${queryString}` : to.path;
|
||||
|
||||
next(`/login?redirect=${encodeURIComponent(redirect)}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { App } from "vue";
|
||||
import VXETable from "vxe-table"; // https://vxetable.cn/v4.6/#/table/start/install
|
||||
|
||||
// 全局默认参数
|
||||
VXETable.setConfig({
|
||||
// 全局尺寸
|
||||
size: "medium",
|
||||
// 全局 zIndex 起始值,如果项目的的 z-index 样式值过大时就需要跟随设置更大,避免被遮挡
|
||||
zIndex: 9999,
|
||||
// 版本号,对于某些带数据缓存的功能有用到,上升版本号可以用于重置数据
|
||||
version: 0,
|
||||
// 全局 loading 提示内容,如果为 null 则不显示文本
|
||||
loadingText: null,
|
||||
table: {
|
||||
showHeader: true,
|
||||
showOverflow: "tooltip",
|
||||
showHeaderOverflow: "tooltip",
|
||||
autoResize: true,
|
||||
// stripe: false,
|
||||
border: "inner",
|
||||
// round: false,
|
||||
emptyText: "暂无数据",
|
||||
rowConfig: {
|
||||
isHover: true,
|
||||
isCurrent: true,
|
||||
// 行数据的唯一主键字段名
|
||||
keyField: "_VXE_ID",
|
||||
},
|
||||
columnConfig: {
|
||||
resizable: false,
|
||||
},
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
},
|
||||
pager: {
|
||||
// size: "medium",
|
||||
// 配套的样式
|
||||
perfect: false,
|
||||
pageSize: 10,
|
||||
pagerCount: 7,
|
||||
pageSizes: [10, 20, 50],
|
||||
layouts: [
|
||||
"Total",
|
||||
"PrevJump",
|
||||
"PrevPage",
|
||||
"Number",
|
||||
"NextPage",
|
||||
"NextJump",
|
||||
"Sizes",
|
||||
"FullJump",
|
||||
],
|
||||
},
|
||||
modal: {
|
||||
minWidth: 500,
|
||||
minHeight: 400,
|
||||
lockView: true,
|
||||
mask: true,
|
||||
// duration: 3000,
|
||||
// marginSize: 20,
|
||||
dblclickZoom: false,
|
||||
showTitleOverflow: true,
|
||||
transfer: true,
|
||||
draggable: false,
|
||||
},
|
||||
});
|
||||
|
||||
export function setupVxeTable(app: App) {
|
||||
// Vxe Table 组件完整引入
|
||||
app.use(VXETable);
|
||||
}
|
||||
Reference in New Issue
Block a user