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:
Ray.Hao
2025-12-12 13:59:40 +08:00
parent add4237b1f
commit 9fb1942619
45 changed files with 836 additions and 834 deletions

View File

@@ -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);
}
}

View File

@@ -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);
},
};

View File

@@ -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();
});
}

View File

@@ -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);
}

View File

@@ -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);
}