refactor: 重构项目结构 - enums/config/types/plugins
- 重构 enums: 按业务域合并为 5 个文件 - 创建 config: storage.ts, vxe-table.ts - 删除 plugins,功能迁移到 main.ts - 创建完整 types 结构 - 新增 utils: validators, websocket, register-components - 创建 router/guards/permission.ts - 更新配置文件
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { App } from "vue";
|
||||
|
||||
import { setupDirective } from "@/directives";
|
||||
import { setupI18n } from "@/lang";
|
||||
import { setupRouter } from "@/router";
|
||||
import { setupStore } from "@/store";
|
||||
import { setupElIcons } from "./icons";
|
||||
import { setupPermission } from "./permission";
|
||||
import { setupWebSocket } from "./websocket";
|
||||
import { InstallCodeMirror } from "codemirror-editor-vue3";
|
||||
import { setupVxeTable } from "./vxeTable";
|
||||
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
// 自定义指令(directive)
|
||||
setupDirective(app);
|
||||
// 路由(router)
|
||||
setupRouter(app);
|
||||
// 状态管理(store)
|
||||
setupStore(app);
|
||||
// 国际化
|
||||
setupI18n(app);
|
||||
// Element-plus图标
|
||||
setupElIcons(app);
|
||||
// 路由守卫
|
||||
setupPermission();
|
||||
// WebSocket服务
|
||||
setupWebSocket();
|
||||
// vxe-table
|
||||
setupVxeTable(app);
|
||||
// 注册 CodeMirror
|
||||
app.use(InstallCodeMirror);
|
||||
},
|
||||
};
|
||||
@@ -1,111 +0,0 @@
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
import NProgress from "@/utils/nprogress";
|
||||
import router from "@/router";
|
||||
import { usePermissionStore, useUserStore } from "@/store";
|
||||
import { useTenantStoreHook } from "@/store/modules/tenant-store";
|
||||
|
||||
/**
|
||||
* 多租户功能是否启用
|
||||
* 通过环境变量控制,实现零侵入的可插拔设计
|
||||
*/
|
||||
const TENANT_ENABLED = import.meta.env.VITE_APP_TENANT_ENABLED === "true";
|
||||
|
||||
/**
|
||||
* 初始化多租户上下文(插件式设计)
|
||||
* - 仅在启用多租户时执行
|
||||
* - 失败不影响主流程(优雅降级)
|
||||
* - 完全解耦,可随时移除
|
||||
*/
|
||||
async function initTenantContextIfEnabled(): Promise<void> {
|
||||
if (!TENANT_ENABLED) {
|
||||
console.debug("[Tenant] 多租户功能未启用,跳过初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.debug("[Tenant] 开始加载租户...");
|
||||
const tenantStore = useTenantStoreHook();
|
||||
await tenantStore.loadTenant();
|
||||
console.debug("[Tenant] 租户加载成功");
|
||||
} catch (error) {
|
||||
// 优雅降级:后端未启用多租户或接口不存在时,不影响正常流程
|
||||
console.debug("[Tenant] 租户上下文初始化失败(可能后端未启用多租户):", error);
|
||||
}
|
||||
}
|
||||
|
||||
export function setupPermission() {
|
||||
const whiteList = ["/login"];
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
|
||||
try {
|
||||
const isLoggedIn = useUserStore().isLoggedIn();
|
||||
|
||||
// 未登录处理
|
||||
if (!isLoggedIn) {
|
||||
if (whiteList.includes(to.path)) {
|
||||
next();
|
||||
} else {
|
||||
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`);
|
||||
NProgress.done();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 已登录登录页重定向
|
||||
if (to.path === "/login") {
|
||||
next({ path: "/" });
|
||||
return;
|
||||
}
|
||||
|
||||
const permissionStore = usePermissionStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 动态路由生成
|
||||
if (!permissionStore.isRouteGenerated) {
|
||||
if (!userStore.userInfo?.roles?.length) {
|
||||
await userStore.getUserInfo();
|
||||
}
|
||||
|
||||
// 【多租户插件】初始化租户上下文(零侵入设计)
|
||||
// - 通过 VITE_APP_TENANT_ENABLED 环境变量控制
|
||||
// - 失败不影响主流程,优雅降级
|
||||
// - 可通过设置环境变量为 false 完全移除此功能
|
||||
await initTenantContextIfEnabled();
|
||||
|
||||
const dynamicRoutes = await permissionStore.generateRoutes();
|
||||
dynamicRoutes.forEach((route: RouteRecordRaw) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
|
||||
next({ ...to, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 路由404检查
|
||||
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 useUserStore().resetAllState();
|
||||
next("/login");
|
||||
NProgress.done();
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import { useDictSync } from "@/composables";
|
||||
import { AuthStorage } from "@/utils/auth";
|
||||
|
||||
/**
|
||||
* WebSocket 服务实例约定接口
|
||||
* 至少包含 disconnect/closeWebSocket/cleanup 三者之一
|
||||
*/
|
||||
type WebSocketService = {
|
||||
disconnect?: () => void;
|
||||
closeWebSocket?: () => void;
|
||||
cleanup?: () => void;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
// 全局 WebSocket 实例管理
|
||||
const websocketInstances = new Map<string, WebSocketService>();
|
||||
|
||||
// 用于防止重复初始化的状态标记
|
||||
let isInitialized = false;
|
||||
let dictWebSocketInstance: ReturnType<typeof useDictSync> | null = null;
|
||||
|
||||
/**
|
||||
* 注册 WebSocket 实例,便于统一清理
|
||||
*/
|
||||
export function registerWebSocketInstance(key: string, instance: WebSocketService) {
|
||||
websocketInstances.set(key, instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WebSocket 实例
|
||||
*/
|
||||
export function getWebSocketInstance(key: string) {
|
||||
return websocketInstances.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化WebSocket服务
|
||||
*/
|
||||
export function setupWebSocket() {
|
||||
// 检查是否已经初始化
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查环境变量是否配置
|
||||
const wsEndpoint = import.meta.env.VITE_APP_WS_ENDPOINT;
|
||||
if (!wsEndpoint) {
|
||||
console.log("[WebSocketPlugin] 未配置WebSocket端点,跳过WebSocket初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已登录(基于是否存在访问令牌)
|
||||
if (!AuthStorage.getAccessToken()) {
|
||||
console.warn(
|
||||
"[WebSocketPlugin] 未找到访问令牌,WebSocket初始化已跳过。用户登录后将自动重新连接。"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 延迟初始化,确保应用完全启动
|
||||
setTimeout(() => {
|
||||
// 保存实例引用
|
||||
dictWebSocketInstance = useDictSync();
|
||||
registerWebSocketInstance("dictSync", dictWebSocketInstance);
|
||||
|
||||
// 初始化字典WebSocket服务
|
||||
dictWebSocketInstance.initWebSocket();
|
||||
// 初始化在线用户计数WebSocket
|
||||
import("@/composables").then(({ useOnlineCount }) => {
|
||||
const onlineCountInstance = useOnlineCount({ autoInit: false });
|
||||
onlineCountInstance.initWebSocket();
|
||||
});
|
||||
|
||||
// 在窗口关闭前断开WebSocket连接
|
||||
window.addEventListener("beforeunload", handleWindowClose);
|
||||
isInitialized = true;
|
||||
}, 1000); // 延迟1秒初始化
|
||||
} catch (error) {
|
||||
console.error("[WebSocketPlugin] 初始化WebSocket服务失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理窗口关闭
|
||||
*/
|
||||
function handleWindowClose() {
|
||||
cleanupWebSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理WebSocket连接
|
||||
*/
|
||||
export function cleanupWebSocket() {
|
||||
// 清理字典 WebSocket
|
||||
if (dictWebSocketInstance) {
|
||||
try {
|
||||
dictWebSocketInstance.closeWebSocket();
|
||||
} catch (error) {
|
||||
console.error("[WebSocketPlugin] 断开字典WebSocket连接失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理所有注册的 WebSocket 实例
|
||||
websocketInstances.forEach((instance, key) => {
|
||||
try {
|
||||
if (instance && typeof instance.disconnect === "function") {
|
||||
instance.disconnect();
|
||||
} else if (instance && typeof instance.closeWebSocket === "function") {
|
||||
instance.closeWebSocket();
|
||||
} else if (instance && typeof instance.cleanup === "function") {
|
||||
instance.cleanup();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[WebSocketPlugin] 断开 ${key} WebSocket连接失败:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
// 清空实例映射
|
||||
websocketInstances.clear();
|
||||
|
||||
// 移除事件监听器
|
||||
window.removeEventListener("beforeunload", handleWindowClose);
|
||||
|
||||
// 重置状态
|
||||
dictWebSocketInstance = null;
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新初始化WebSocket(用于登录后重连)
|
||||
*/
|
||||
export function reinitializeWebSocket() {
|
||||
// 先清理现有连接
|
||||
cleanupWebSocket();
|
||||
|
||||
// 延迟后重新初始化
|
||||
setTimeout(() => {
|
||||
setupWebSocket();
|
||||
}, 500);
|
||||
}
|
||||
Reference in New Issue
Block a user