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

5
src/config/index.ts Normal file
View File

@@ -0,0 +1,5 @@
/**
* 配置统一导出
*/
export * from "./storage";

60
src/config/storage.ts Normal file
View File

@@ -0,0 +1,60 @@
/**
* 本地存储键名配置
*
* @description
* 统一管理 localStorage/sessionStorage 的键名
* 命名规范:{prefix}:{namespace}:{key}
*/
export const APP_PREFIX = "vea";
/**
* 存储键名常量
*/
export const STORAGE_KEYS = {
// ===== 认证相关 =====
ACCESS_TOKEN: `${APP_PREFIX}:auth:access_token`,
REFRESH_TOKEN: `${APP_PREFIX}:auth:refresh_token`,
REMEMBER_ME: `${APP_PREFIX}:auth:remember_me`,
// ===== 租户相关 =====
TENANT_ID: `${APP_PREFIX}:tenant:id`,
TENANT_INFO: `${APP_PREFIX}:tenant:info`,
// ===== 系统相关 =====
DICT_CACHE: `${APP_PREFIX}:system:dict_cache`,
// ===== UI 设置 =====
SHOW_TAGS_VIEW: `${APP_PREFIX}:ui:show_tags_view`,
SHOW_APP_LOGO: `${APP_PREFIX}:ui:show_app_logo`,
SHOW_WATERMARK: `${APP_PREFIX}:ui:show_watermark`,
ENABLE_AI_ASSISTANT: `${APP_PREFIX}:ui:enable_ai_assistant`,
LAYOUT: `${APP_PREFIX}:ui:layout`,
SIDEBAR_COLOR_SCHEME: `${APP_PREFIX}:ui:sidebar_color_scheme`,
THEME: `${APP_PREFIX}:ui:theme`,
THEME_COLOR: `${APP_PREFIX}:ui:theme_color`,
// ===== 应用状态 =====
DEVICE: `${APP_PREFIX}:app:device`,
SIZE: `${APP_PREFIX}:app:size`,
LANGUAGE: `${APP_PREFIX}:app:language`,
SIDEBAR_STATUS: `${APP_PREFIX}:app:sidebar_status`,
ACTIVE_TOP_MENU_PATH: `${APP_PREFIX}:app:active_top_menu_path`,
} as const;
/**
* 认证相关键名(便于批量操作)
*/
export const AUTH_KEYS = {
ACCESS_TOKEN: STORAGE_KEYS.ACCESS_TOKEN,
REFRESH_TOKEN: STORAGE_KEYS.REFRESH_TOKEN,
REMEMBER_ME: STORAGE_KEYS.REMEMBER_ME,
} as const;
/**
* 租户相关键名(便于批量操作)
*/
export const TENANT_KEYS = {
TENANT_ID: STORAGE_KEYS.TENANT_ID,
TENANT_INFO: STORAGE_KEYS.TENANT_INFO,
} as const;

65
src/config/vxe-table.ts Normal file
View File

@@ -0,0 +1,65 @@
/**
* VxeTable 全局配置
*
* @description
* VxeTable 是一个基于 Vue 的 PC 端表格组件
* @see https://vxetable.cn/v4.6/#/table/start/install
*/
import VXETable from "vxe-table";
/**
* 配置 VxeTable 全局参数
*/
export function configureVxeTable() {
VXETable.setConfig({
size: "medium",
zIndex: 9999,
version: 0,
loadingText: null,
table: {
showHeader: true,
showOverflow: "tooltip",
showHeaderOverflow: "tooltip",
autoResize: true,
border: "inner",
emptyText: "暂无数据",
rowConfig: {
isHover: true,
isCurrent: true,
keyField: "_VXE_ID",
},
columnConfig: {
resizable: false,
},
align: "center",
headerAlign: "center",
},
pager: {
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,
dblclickZoom: false,
showTitleOverflow: true,
transfer: true,
draggable: false,
},
});
}

View File

@@ -1,172 +1,13 @@
/**
* 项目常量统一管理
* 存储键命名规范:{prefix}:{namespace}:{key}
/**
* 常量统一导出
*
* @deprecated 此文件已废弃,请使用以下路径:
* - 存储键常量 @/config/storage
* - 验证规则 @/utils/validators
* - 角色常量 @/enums
*/
export const APP_PREFIX = "vea";
export const STORAGE_KEYS = {
// 用户认证相关
ACCESS_TOKEN: `${APP_PREFIX}:auth:access_token`, // JWT访问令牌
REFRESH_TOKEN: `${APP_PREFIX}:auth:refresh_token`, // JWT刷新令牌
REMEMBER_ME: `${APP_PREFIX}:auth:remember_me`, // 记住登录状态
// 租户相关
TENANT_ID: `${APP_PREFIX}:tenant:id`, // 当前租户ID
TENANT_INFO: `${APP_PREFIX}:tenant:info`, // 当前租户信息
// 系统核心相关
DICT_CACHE: `${APP_PREFIX}:system:dict_cache`, // 字典数据缓存
// UI设置相关
SHOW_TAGS_VIEW: `${APP_PREFIX}:ui:show_tags_view`, // 显示标签页视图
SHOW_APP_LOGO: `${APP_PREFIX}:ui:show_app_logo`, // 显示应用Logo
SHOW_WATERMARK: `${APP_PREFIX}:ui:show_watermark`, // 显示水印
ENABLE_AI_ASSISTANT: `${APP_PREFIX}:ui:enable_ai_assistant`, // 启用 AI 助手
LAYOUT: `${APP_PREFIX}:ui:layout`, // 布局模式
SIDEBAR_COLOR_SCHEME: `${APP_PREFIX}:ui:sidebar_color_scheme`, // 侧边栏配色方案
THEME: `${APP_PREFIX}:ui:theme`, // 主题模式
THEME_COLOR: `${APP_PREFIX}:ui:theme_color`, // 主题色
// 应用状态相关
DEVICE: `${APP_PREFIX}:app:device`, // 设备类型
SIZE: `${APP_PREFIX}:app:size`, // 屏幕尺寸
LANGUAGE: `${APP_PREFIX}:app:language`, // 应用语言
SIDEBAR_STATUS: `${APP_PREFIX}:app:sidebar_status`, // 侧边栏状态
ACTIVE_TOP_MENU_PATH: `${APP_PREFIX}:app:active_top_menu_path`, // 当前激活的顶部菜单路径
} as const;
export const ROLE_ROOT = "ROOT"; // 超级管理员角色
// 分组键集合(便于批量操作)
export const AUTH_KEYS = {
ACCESS_TOKEN: STORAGE_KEYS.ACCESS_TOKEN,
REFRESH_TOKEN: STORAGE_KEYS.REFRESH_TOKEN,
REMEMBER_ME: STORAGE_KEYS.REMEMBER_ME,
} as const;
export const TENANT_KEYS = {
TENANT_ID: STORAGE_KEYS.TENANT_ID,
TENANT_INFO: STORAGE_KEYS.TENANT_INFO,
} as const;
export const SYSTEM_KEYS = {
DICT_CACHE: STORAGE_KEYS.DICT_CACHE,
} as const;
export const SETTINGS_KEYS = {
SHOW_TAGS_VIEW: STORAGE_KEYS.SHOW_TAGS_VIEW,
SHOW_APP_LOGO: STORAGE_KEYS.SHOW_APP_LOGO,
SHOW_WATERMARK: STORAGE_KEYS.SHOW_WATERMARK,
ENABLE_AI_ASSISTANT: STORAGE_KEYS.ENABLE_AI_ASSISTANT,
SIDEBAR_COLOR_SCHEME: STORAGE_KEYS.SIDEBAR_COLOR_SCHEME,
LAYOUT: STORAGE_KEYS.LAYOUT,
THEME_COLOR: STORAGE_KEYS.THEME_COLOR,
THEME: STORAGE_KEYS.THEME,
} as const;
export const APP_KEYS = {
DEVICE: STORAGE_KEYS.DEVICE,
SIZE: STORAGE_KEYS.SIZE,
LANGUAGE: STORAGE_KEYS.LANGUAGE,
SIDEBAR_STATUS: STORAGE_KEYS.SIDEBAR_STATUS,
ACTIVE_TOP_MENU_PATH: STORAGE_KEYS.ACTIVE_TOP_MENU_PATH,
} as const;
export const ALL_STORAGE_KEYS = {
...AUTH_KEYS,
...TENANT_KEYS,
...SYSTEM_KEYS,
...SETTINGS_KEYS,
...APP_KEYS,
} as const;
export type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];
/**
* 表单验证规则常量
* 提供常用的验证规则,减少重复代码
*
* @example
* ```ts
* const rules = reactive({
* username: [VALIDATORS.required("用户名不能为空")],
* email: [VALIDATORS.email],
* mobile: [VALIDATORS.mobile],
* });
* ```
*/
export const VALIDATORS = {
/**
* 必填验证
* @param message 错误提示信息
*/
required: (message: string) => ({
required: true,
message,
trigger: "blur",
}),
/**
* 邮箱格式验证
*/
email: {
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
message: "请输入正确的邮箱地址",
trigger: "blur",
},
/**
* 手机号码验证(中国大陆)
*/
mobile: {
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: "请输入正确的手机号码",
trigger: "blur",
},
/**
* 身份证号码验证(中国大陆)
*/
idCard: {
pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/,
message: "请输入正确的身份证号码",
trigger: "blur",
},
/**
* URL 格式验证
*/
url: {
pattern: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i,
message: "请输入正确的URL地址",
trigger: "blur",
},
/**
* 数字验证
*/
number: {
pattern: /^\d+$/,
message: "请输入数字",
trigger: "blur",
},
/**
* 整数验证正整数、负整数、0
*/
integer: {
pattern: /^-?\d+$/,
message: "请输入整数",
trigger: "blur",
},
/**
* 正整数验证
*/
positiveInteger: {
pattern: /^[1-9]\d*$/,
message: "请输入正整数",
trigger: "blur",
},
} as const;
// 向后兼容导出
export * from "@/config/storage";
export { ROLE_ROOT } from "@/enums";
export { VALIDATORS } from "@/utils/validators";

View File

@@ -1,5 +1,12 @@
/**
* API响应码枚
* API
*
* @description
* API
*/
/**
* API
*/
export const enum ApiCodeEnum {
/**

47
src/enums/business.ts Normal file
View File

@@ -0,0 +1,47 @@
/**
* 业务相关枚举
*
* @description
* 包含菜单、用户、角色等业务实体的枚举定义
*/
/**
* 菜单类型枚举
*/
export enum MenuTypeEnum {
CATALOG = "C", // 目录
MENU = "M", // 菜单
BUTTON = "B", // 按钮
}
/**
* 用户性别枚举
*/
export enum UserGender {
/** 未知 */
UNKNOWN = 0,
/** 男 */
MALE = 1,
/** 女 */
FEMALE = 2,
}
/**
* 超级管理员角色标识
*
* @description
* 拥有系统最高权限,可以访问所有资源
*/
export const ROLE_ROOT = "ROOT";
/**
* 角色类型枚举
*/
export enum RoleType {
/** 超级管理员 */
ROOT = "ROOT",
/** 管理员 */
ADMIN = "ADMIN",
/** 普通用户 */
USER = "USER",
}

View File

@@ -1,3 +1,26 @@
/**
*
*
* @description
*
*/
/**
*
*/
export const FormTypeEnum: Record<string, OptionType> = {
INPUT: { value: 1, label: "输入框" },
SELECT: { value: 2, label: "下拉框" },
RADIO: { value: 3, label: "单选框" },
CHECK_BOX: { value: 4, label: "复选框" },
INPUT_NUMBER: { value: 5, label: "数字输入框" },
SWITCH: { value: 6, label: "开关" },
TEXT_AREA: { value: 7, label: "文本域" },
DATE: { value: 8, label: "日期框" },
DATE_TIME: { value: 9, label: "日期时间框" },
HIDDEN: { value: 10, label: "隐藏域" },
};
/**
*
*/

View File

@@ -1,15 +0,0 @@
/**
* 表单类型枚举
*/
export const FormTypeEnum: Record<string, OptionType> = {
INPUT: { value: 1, label: "输入框" },
SELECT: { value: 2, label: "下拉框" },
RADIO: { value: 3, label: "单选框" },
CHECK_BOX: { value: 4, label: "复选框" },
INPUT_NUMBER: { value: 5, label: "数字输入框" },
SWITCH: { value: 6, label: "开关" },
TEXT_AREA: { value: 7, label: "文本域" },
DATE: { value: 8, label: "日期框" },
DATE_TIME: { value: 9, label: "日期时间框" },
HIDDEN: { value: 10, label: "隐藏域" },
};

46
src/enums/common.ts Normal file
View File

@@ -0,0 +1,46 @@
/**
* 通用枚举
*
* @description
* 包含对话框模式、通用状态等跨业务的枚举定义
*/
/**
* 对话框模式枚举
*
* @description
* 定义对话框的操作模式(创建、编辑、查看)
*/
export enum DialogMode {
/** 创建模式 - 新增数据 */
CREATE = "create",
/** 编辑模式 - 修改数据 */
EDIT = "edit",
/** 查看模式 - 只读展示 */
VIEW = "view",
}
/**
* 通用状态枚举
*
* @description
* 适用于大多数业务实体的启用/禁用状态
*/
export enum CommonStatus {
/** 禁用 */
DISABLED = 0,
/** 启用 */
ENABLED = 1,
}
/**
* 审核状态枚举
*/
export enum AuditStatus {
/** 待审核 */
PENDING = 0,
/** 已通过 */
APPROVED = 1,
/** 已拒绝 */
REJECTED = 2,
}

View File

@@ -1,12 +0,0 @@
/**
* 通用对话框模式枚举
* @description 定义对话框的操作模式(创建、编辑、查看)
*/
export enum DialogMode {
/** 创建模式 - 新增数据 */
CREATE = "create",
/** 编辑模式 - 修改数据 */
EDIT = "edit",
/** 查看模式 - 只读展示 */
VIEW = "view",
}

View File

@@ -1,22 +0,0 @@
/**
* 通用状态枚举
* 适用于大多数业务实体的启用/禁用状态
*/
export enum CommonStatus {
/** 禁用 */
DISABLED = 0,
/** 启用 */
ENABLED = 1,
}
/**
* 审核状态枚举
*/
export enum AuditStatus {
/** 待审核 */
PENDING = 0,
/** 已通过 */
APPROVED = 1,
/** 已拒绝 */
REJECTED = 2,
}

View File

@@ -1,15 +1,12 @@
export * from "./api/code-enum";
/**
* 枚举统一导出
*
* @description
* 按业务域分组的枚举定义
*/
export * from "./codegen/form-enum";
export * from "./codegen/query-enum";
export * from "./settings/layout-enum";
export * from "./settings/theme-enum";
export * from "./settings/locale-enum";
export * from "./settings/device-enum";
export * from "./common/dialog-enum";
export * from "./common/status-enum";
export * from "./system/menu-enum";
export * from "./system/user-enum";
export * from "./api";
export * from "./business";
export * from "./codegen";
export * from "./common";
export * from "./settings";

123
src/enums/settings.ts Normal file
View File

@@ -0,0 +1,123 @@
/**
* 设置相关枚举
*
* @description
* 包含主题、布局、语言、设备等应用设置的枚举定义
*/
/**
* 主题模式枚举
*/
export const enum ThemeMode {
/**
* 明亮主题
*/
LIGHT = "light",
/**
* 暗黑主题
*/
DARK = "dark",
/**
* 系统自动
*/
AUTO = "auto",
}
/**
* 侧边栏配色方案枚举
*/
export const enum SidebarColor {
/**
* 经典蓝
*/
CLASSIC_BLUE = "classic-blue",
/**
* 极简白
*/
MINIMAL_WHITE = "minimal-white",
}
/**
* 菜单布局枚举
*/
export const enum LayoutMode {
/**
* 左侧菜单布局
*/
LEFT = "left",
/**
* 顶部菜单布局
*/
TOP = "top",
/**
* 混合菜单布局
*/
MIX = "mix",
}
/**
* 侧边栏状态枚举
*/
export const enum SidebarStatus {
/**
* 展开
*/
OPENED = "opened",
/**
* 关闭
*/
CLOSED = "closed",
}
/**
* 组件尺寸枚举
*/
export const enum ComponentSize {
/**
* 默认
*/
DEFAULT = "default",
/**
* 大型
*/
LARGE = "large",
/**
* 小型
*/
SMALL = "small",
}
/**
* 语言枚举
*/
export const enum LanguageEnum {
/**
* 中文
*/
ZH_CN = "zh-cn",
/**
* 英文
*/
EN = "en",
}
/**
* 设备枚举
*/
export const enum DeviceEnum {
/**
* 宽屏设备
*/
DESKTOP = "desktop",
/**
* 窄屏设备
*/
MOBILE = "mobile",
}

View File

@@ -1,14 +0,0 @@
/**
* 设备枚举
*/
export const enum DeviceEnum {
/**
* 宽屏设备
*/
DESKTOP = "desktop",
/**
* 窄屏设备
*/
MOBILE = "mobile",
}

View File

@@ -1,53 +0,0 @@
/**
* 菜单布局枚举
*/
export const enum LayoutMode {
/**
* 左侧菜单布局
*/
LEFT = "left",
/**
* 顶部菜单布局
*/
TOP = "top",
/**
* 混合菜单布局
*/
MIX = "mix",
}
/**
* 侧边栏状态枚举
*/
export const enum SidebarStatus {
/**
* 展开
*/
OPENED = "opened",
/**
* 关闭
*/
CLOSED = "closed",
}
/**
* 组件尺寸枚举
*/
export const enum ComponentSize {
/**
* 默认
*/
DEFAULT = "default",
/**
* 大型
*/
LARGE = "large",
/**
* 小型
*/
SMALL = "small",
}

View File

@@ -1,14 +0,0 @@
/**
* 语言枚举
*/
export const enum LanguageEnum {
/**
* 中文
*/
ZH_CN = "zh-cn",
/**
* 英文
*/
EN = "en",
}

View File

@@ -1,32 +0,0 @@
/**
* 主题枚举
*/
export const enum ThemeMode {
/**
* 明亮主题
*/
LIGHT = "light",
/**
* 暗黑主题
*/
DARK = "dark",
/**
* 系统自动
*/
AUTO = "auto",
}
/**
* 侧边栏配色方案枚举
*/
export const enum SidebarColor {
/**
* 经典蓝
*/
CLASSIC_BLUE = "classic-blue",
/**
* 极简白
*/
MINIMAL_WHITE = "minimal-white",
}

View File

@@ -1,6 +0,0 @@
// 核心枚举定义
export enum MenuTypeEnum {
CATALOG = "C", // 目录
MENU = "M", // 菜单
BUTTON = "B", // 按钮
}

View File

@@ -1,11 +0,0 @@
/**
* 用户性别枚举
*/
export enum UserGender {
/** 未知 */
UNKNOWN = 0,
/** 男 */
MALE = 1,
/** 女 */
FEMALE = 2,
}

View File

@@ -1,19 +1,63 @@
/**
* 应用启动入口
*
* @description
* Vue3 应用初始化,包括样式、插件、配置的加载
*/
import { createApp } from "vue";
import App from "./App.vue";
import setupPlugins from "@/plugins";
// 暗黑主题样式
// ===== 样式导入 =====
import "element-plus/theme-chalk/dark/css-vars.css";
import "vxe-table/lib/style.css";
// 暗黑模式自定义变量
import "@/styles/dark/css-vars.css";
import "@/styles/index.scss";
import "uno.css";
// 过渡动画
import "animate.css";
// ===== 核心配置 =====
import { setupDirective } from "@/directives";
import { setupI18n } from "@/lang";
import { setupRouter } from "@/router";
import { setupStore } from "@/store";
// ===== 全局组件 =====
import { registerElementIcons } from "@/utils/register-components";
// ===== 第三方插件 =====
import VXETable from "vxe-table";
import { InstallCodeMirror } from "codemirror-editor-vue3";
import { configureVxeTable } from "@/config/vxe-table";
// ===== 路由守卫 =====
import { setupPermissionGuard } from "@/router/guards/permission";
// ===== 业务服务 =====
import { setupWebSocket } from "@/utils/websocket";
// 创建 Vue 应用实例
const app = createApp(App);
// 注册插件
app.use(setupPlugins);
// 1⃣ 核心配置
setupDirective(app);
setupRouter(app);
setupStore(app);
setupI18n(app);
// 2⃣ 全局组件
registerElementIcons(app);
// 3⃣ 第三方插件
configureVxeTable();
app.use(VXETable);
app.use(InstallCodeMirror);
// 4⃣ 路由守卫
setupPermissionGuard();
// 5⃣ WebSocket 初始化
setupWebSocket();
// 6⃣ 挂载应用
app.mount("#app");

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

View File

@@ -33,7 +33,7 @@ async function initTenantContextIfEnabled(): Promise<void> {
}
}
export function setupPermission() {
export function setupPermissionGuard() {
const whiteList = ["/login"];
router.beforeEach(async (to, from, next) => {

24
src/types/api/auth.ts Normal file
View File

@@ -0,0 +1,24 @@
/**
* 认证相关类型定义
*/
export interface LoginRequest {
username: string;
password: string;
captchaId?: string;
captchaCode?: string;
rememberMe?: boolean;
tenantId?: number;
}
export interface LoginResult {
accessToken: string;
refreshToken: string;
tokenType: string;
expiresIn: number;
}
export interface CaptchaInfo {
captchaId: string;
captchaBase64: string;
}

32
src/types/api/common.ts Normal file
View File

@@ -0,0 +1,32 @@
/**
* 通用 API 类型定义
*/
export interface ApiResponse<T = any> {
code: string;
data: T;
msg: string;
}
export interface PageQuery {
pageNum: number;
pageSize: number;
}
export interface PageResult<T> {
list: T;
total: number;
}
export interface OptionType {
value: string | number;
label: string;
children?: OptionType[];
}
export interface ExcelResult {
code: string;
invalidCount: number;
validCount: number;
messageList: string[];
}

6
src/types/api/index.ts Normal file
View File

@@ -0,0 +1,6 @@
/**
* API 类型统一导出
*/
export * from "./common";
export * from "./auth";

35
src/types/env.d.ts vendored
View File

@@ -1,35 +0,0 @@
// https://cn.vitejs.dev/guide/env-and-mode
// TypeScript 类型提示都为 string https://github.com/vitejs/vite/issues/6930
interface ImportMetaEnv {
/** 应用端口 */
VITE_APP_PORT: number;
/** 应用名称 */
VITE_APP_NAME: string;
/** API 基础路径(代理前缀) */
VITE_APP_BASE_API: string;
/** API 地址 */
VITE_APP_API_URL: string;
/** 是否开启 Mock 服务 */
VITE_MOCK_DEV_SERVER: boolean;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
/**
* 平台的名称、版本、运行所需的`node`版本、依赖、构建时间的类型提示
*/
declare const __APP_INFO__: {
pkg: {
name: string;
version: string;
engines: {
node: string;
};
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
};
buildTimestamp: number;
};

121
src/types/global.d.ts vendored
View File

@@ -1,111 +1,16 @@
/**
* 全局类型声明
*
* @deprecated 请使用 @/types 下的具名导出
*/
declare global {
/**
* 响应数据
*/
interface ApiResponse<T = any> {
code: string;
data: T;
msg: string;
}
/**
* 分页查询参数
*/
interface PageQuery {
pageNum: number;
pageSize: number;
}
/**
* 分页响应对象
*/
interface PageResult<T> {
/** 数据列表 */
list: T;
/** 总数 */
total: number;
}
/**
* 页签对象
*/
interface TagView {
/** 页签名称 */
name: string;
/** 页签标题 */
title: string;
/** 页签路由路径 */
path: string;
/** 页签路由完整路径 */
fullPath: string;
/** 页签图标 */
icon?: string;
/** 是否固定页签 */
affix?: boolean;
/** 是否开启缓存 */
keepAlive?: boolean;
/** 路由查询参数 */
query?: any;
}
/**
* 系统设置
*/
interface AppSettings {
/** 系统标题 */
title: string;
/** 系统版本 */
version: string;
/** 是否显示设置 */
showSettings: boolean;
/** 是否显示多标签导航 */
showTagsView: boolean;
/** 是否显示应用Logo */
showAppLogo: boolean;
/** 导航栏布局(left|top|mix) */
layout: "left" | "top" | "mix";
/** 主题颜色 */
themeColor: string;
/** 主题模式(dark|light) */
theme: import("@/enums/settings/theme-enum").ThemeMode;
/** 布局大小(default |large |small) */
size: string;
/** 语言( zh-cn| en) */
language: string;
/** 是否显示水印 */
showWatermark: boolean;
/** 水印内容 */
watermarkContent: string;
/** 侧边栏配色方案 */
sidebarColorScheme: "classic-blue" | "minimal-white";
/** 是否启用 AI 助手 */
enableAiAssistant: boolean;
}
/**
* 下拉选项数据类型
*/
interface OptionType {
/** 值 */
value: string | number;
/** 文本 */
label: string;
/** 子列表 */
children?: OptionType[];
}
/**
* 导入结果
*/
interface ExcelResult {
/** 状态码 */
code: string;
/** 无效数据条数 */
invalidCount: number;
/** 有效数据条数 */
validCount: number;
/** 错误信息 */
messageList: Array<string>;
}
type ApiResponse<T = any> = import("@/types/api").ApiResponse<T>;
type PageQuery = import("@/types/api").PageQuery;
type PageResult<T> = import("@/types/api").PageResult<T>;
type OptionType = import("@/types/api").OptionType;
type ExcelResult = import("@/types/api").ExcelResult;
type TagView = import("@/types/ui").TagView;
type AppSettings = import("@/types/ui").AppSettings;
}
export {};

6
src/types/index.ts Normal file
View File

@@ -0,0 +1,6 @@
/**
* 类型统一导出
*/
export * from "./api";
export * from "./ui";

54
src/types/router.d.ts vendored
View File

@@ -1,54 +0,0 @@
import "vue-router";
declare module "vue-router" {
// https://router.vuejs.org/zh/guide/advanced/meta.html#typescript
// 可以通过扩展 RouteMeta 接口来输入 meta 字段
interface RouteMeta {
/**
* 菜单名称
* @example 'Dashboard'
*/
title?: string;
/**
* 菜单图标
* @example 'el-icon-edit'
*/
icon?: string;
/**
* 是否隐藏菜单
* true 隐藏, false 显示
* @default false
*/
hidden?: boolean;
/**
* 始终显示父级菜单,即使只有一个子菜单
* true 显示父级菜单, false 隐藏父级菜单,显示唯一子节点
* @default false
*/
alwaysShow?: boolean;
/**
* 是否固定在页签上
* true 固定, false 不固定
* @default false
*/
affix?: boolean;
/**
* 是否缓存页面
* true 缓存, false 不缓存
* @default false
*/
keepAlive?: boolean;
/**
* 是否在面包屑导航中隐藏
* true 隐藏, false 显示
* @default false
*/
breadcrumb?: boolean;
}
}

View File

@@ -1,5 +0,0 @@
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@@ -1,6 +0,0 @@
// https://github.com/sockjs/sockjs-client/issues/565
declare module "sockjs-client/dist/sockjs.min.js" {
import Client from "sockjs-client";
export default Client;
}

6
src/types/ui/index.ts Normal file
View File

@@ -0,0 +1,6 @@
/**
* UI 类型统一导出
*/
export * from "./tagsview";
export * from "./settings";

22
src/types/ui/settings.ts Normal file
View File

@@ -0,0 +1,22 @@
/**
* 应用设置相关类型定义
*/
import type { ThemeMode } from "@/enums";
export interface AppSettings {
title: string;
version: string;
showSettings: boolean;
showTagsView: boolean;
showAppLogo: boolean;
layout: "left" | "top" | "mix";
themeColor: string;
theme: ThemeMode;
size: string;
language: string;
showWatermark: boolean;
watermarkContent: string;
sidebarColorScheme: "classic-blue" | "minimal-white";
enableAiAssistant: boolean;
}

14
src/types/ui/tagsview.ts Normal file
View File

@@ -0,0 +1,14 @@
/**
* 标签页相关类型定义
*/
export interface TagView {
name: string;
title: string;
path: string;
fullPath: string;
icon?: string;
affix?: boolean;
keepAlive?: boolean;
query?: any;
}

View File

@@ -0,0 +1,18 @@
/**
* 全局组件注册工具
*
* @description
* 批量注册 Element Plus 图标等全局组件
*/
import type { App } from "vue";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
/**
* 注册 Element Plus 所有图标为全局组件
*/
export function registerElementIcons(app: App) {
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
}

69
src/utils/validators.ts Normal file
View File

@@ -0,0 +1,69 @@
/**
* 表单验证规则工具
*
* @description
* 提供常用的表单验证规则
*/
import type { FormItemRule } from "element-plus";
/**
* 验证规则生成器
*/
export const VALIDATORS = {
/**
* 必填项验证
*/
required(message: string): FormItemRule {
return {
required: true,
message,
trigger: "blur",
};
},
/**
* 邮箱验证
*/
email: {
type: "email",
message: "请输入正确的邮箱地址",
trigger: "blur",
} as FormItemRule,
/**
* 手机号验证
*/
mobile: {
pattern: /^1[3-9]\d{9}$/,
message: "请输入正确的手机号码",
trigger: "blur",
} as FormItemRule,
/**
* URL 验证
*/
url: {
type: "url",
message: "请输入正确的URL地址",
trigger: "blur",
} as FormItemRule,
/**
* 数字验证
*/
number: {
type: "number",
message: "请输入数字",
trigger: "blur",
} as FormItemRule,
/**
* 整数验证
*/
integer: {
type: "integer",
message: "请输入整数",
trigger: "blur",
} as FormItemRule,
};

114
src/utils/websocket.ts Normal file
View File

@@ -0,0 +1,114 @@
/**
* WebSocket 服务管理
*
* @description
* 统一管理应用中的所有 WebSocket 连接
* - 字典同步 WebSocket
* - 在线用户计数 WebSocket
* - 其他业务 WebSocket
*
* @author 有来技术团队
*/
import { useDictSync } from "@/composables";
import { AuthStorage } from "@/utils/auth";
/**
* WebSocket 服务实例约定接口
*/
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) {
console.warn("[WebSocket] 已初始化,跳过重复初始化");
return;
}
if (!AuthStorage.getAccessToken()) {
console.warn("[WebSocket] 未登录,跳过 WebSocket 初始化");
return;
}
try {
dictWebSocketInstance = useDictSync();
registerWebSocketInstance("dict-sync", dictWebSocketInstance);
isInitialized = true;
console.log("[WebSocket] 初始化成功");
} catch (error) {
console.error("[WebSocket] 初始化失败:", error);
}
}
/**
* 清理所有 WebSocket 连接
*/
export function cleanupWebSocket() {
console.log("[WebSocket] 开始清理连接...");
websocketInstances.forEach((instance, key) => {
try {
if (instance.disconnect) {
instance.disconnect();
} else if (instance.closeWebSocket) {
instance.closeWebSocket();
} else if (instance.cleanup) {
instance.cleanup();
}
console.log(`[WebSocket] ${key} 已断开`);
} catch (error) {
console.error(`[WebSocket] ${key} 清理失败:`, error);
}
});
websocketInstances.clear();
dictWebSocketInstance = null;
isInitialized = false;
console.log("[WebSocket] 清理完成");
}
/**
* 重新初始化 WebSocket
*/
export function reinitializeWebSocket() {
cleanupWebSocket();
setupWebSocket();
}
if (typeof window !== "undefined") {
window.addEventListener("beforeunload", () => {
cleanupWebSocket();
});
}