Skip to content

CDesign Pro 项目

项目概述

CDesign Pro 是一个基于 Vue 3 + TypeScript + Vite 构建的现代化后台管理系统。项目采用最新的前端技术栈,提供了完整的权限管理、动态路由、主题配置等功能,适合作为企业级后台管理系统的脚手架。项目地址:https://gitee.com/WilliamHao/cdesign-pro。dev 分支删除了一些组件的演示页面,只保留了必要的组件。main 分支是项目的主分支,包含了完整的代码和文档。后续会持续更新。

核心特性

核心特性

  • 🚀 现代化技术栈 - Vue 3.5 + TypeScript 5.6 + Vite 7.1
  • 📦 开箱即用 - 完整的后台管理系统解决方案
  • 🎨 丰富的主题配置 - 支持亮色/暗色/自动主题切换
  • 🔐 完善的权限系统 - 动态路由、权限验证、角色管理
  • 🌐 国际化支持 - 内置中英文切换
  • 📊 丰富的组件库 - 图表、表单、表格等业务组件
  • 🛠️ 开发工具齐全 - ESLint、Prettier、Stylelint、Husky 等
  • 🎯 Mock 数据支持 - 基于 MSW 的 Mock 服务

技术栈

核心框架

技术版本说明状态
Vue3.5.21渐进式 JavaScript 框架核心
TypeScript5.6.3JavaScript 的超集,提供类型支持核心
Vite7.1.5下一代前端构建工具构建工具

UI 框架

技术版本说明状态
Element Plus2.11.2基于 Vue 3 的组件库UI 组件
Tailwind CSS4.1.14实用优先的 CSS 框架样式
@element-plus/icons-vue2.3.2Element Plus 图标库图标

状态管理

技术版本说明
Pinia3.0.3Vue 官方状态管理库
pinia-plugin-persistedstate4.3.0Pinia 持久化插件

路由

技术版本说明
Vue Router4.5.1Vue.js 官方路由

工具库

技术版本说明
Axios1.12.2HTTP 客户端
Vue i18n9.14.0国际化解决方案
ECharts6.0.0数据可视化图表库
@vueuse/core13.9.0Vue Composition API 工具集
@wangeditor/editor5.1.23富文本编辑器
xlsx0.18.5Excel 文件处理
xgplayer3.0.20视频播放器
mitt3.0.1事件总线
nprogress0.2.0进度条
crypto-js4.2.0加密库
file-saver2.0.5文件保存
highlight.js11.10.0代码高亮

开发工具

技术版本说明
ESLint9.9.1JavaScript 代码检查工具
Prettier3.5.3代码格式化工具
Stylelint16.20.0CSS/SCSS 代码检查工具
Husky9.1.5Git hooks 工具
lint-staged15.5.2只检查暂存区代码
commitizen4.3.0规范化提交信息
MSW2.13.2Mock Service Worker
Vue DevTools7.7.6Vue 开发者工具

项目结构

项目目录树

点击展开查看完整的项目结构。

cdesign-pro/
├── .husky/                    # Git hooks 配置
│   ├── commit-msg            # 提交信息验证
│   └── pre-commit            # 提交前检查
├── .vscode/                   # VS Code 配置
│   ├── extensions.json       # 推荐扩展
│   └── settings.json         # 编辑器设置
├── public/                    # 静态资源
│   ├── favicon.ico           # 网站图标
│   └── mockServiceWorker.js  # MSW Service Worker
├── scripts/                   # 脚本文件
│   └── clean-dev.ts          # 清理开发环境脚本
├── src/                       # 源代码目录
│   ├── api/                  # API 接口定义
│   │   ├── auth.ts           # 认证相关接口
│   │   └── system-manage.ts  # 系统管理接口
│   ├── assets/               # 静态资源
│   │   ├── images/           # 图片资源
│   │   │   ├── avatar/       # 头像
│   │   │   ├── ceremony/     # 节日相关
│   │   │   ├── common/       # 公共图片
│   │   │   ├── draw/         # 绘画相关
│   │   │   ├── lock/         # 锁屏背景
│   │   │   ├── login/        # 登录相关
│   │   │   ├── settings/     # 设置相关
│   │   │   ├── svg/          # SVG 图片
│   │   │   └── user/         # 用户相关
│   │   ├── styles/           # 样式文件
│   │   │   ├── core/         # 核心样式
│   │   │   │   ├── app.scss
│   │   │   │   ├── dark.scss
│   │   │   │   ├── el-dark.scss
│   │   │   │   ├── el-light.scss
│   │   │   │   ├── el-ui.scss
│   │   │   │   ├── md.scss
│   │   │   │   ├── mixin.scss
│   │   │   │   ├── reset.scss
│   │   │   │   ├── router-transition.scss
│   │   │   │   ├── tailwind.css
│   │   │   │   ├── theme-animation.scss
│   │   │   │   └── theme-change.scss
│   │   │   ├── custom/       # 自定义样式
│   │   │   └── index.scss    # 样式入口
│   │   └── svg/              # SVG 资源
│   │       └── loading.ts
│   ├── components/           # 组件目录
│   │   └── core/             # 核心组件
│   │       ├── banners/      # 横幅组件
│   │       ├── base/         # 基础组件
│   │       ├── cards/        # 卡片组件
│   │       ├── charts/       # 图表组件
│   │       ├── forms/        # 表单组件
│   │       ├── layouts/      # 布局组件
│   │       ├── media/        # 媒体组件
│   │       ├── others/       # 其他组件
│   │       ├── tables/       # 表格组件
│   │       ├── text-effect/  # 文本特效
│   │       ├── theme/        # 主题组件
│   │       ├── views/        # 页面组件
│   │       └── widget/       # 小部件
│   ├── config/               # 配置文件
│   │   ├── assets/           # 资源配置
│   │   ├── modules/          # 配置模块
│   │   ├── fastEnter.ts      # 快速入口配置
│   │   ├── index.ts          # 主配置
│   │   └── setting.ts        # 设置配置
│   ├── directives/           # 自定义指令
│   │   ├── business/         # 业务指令
│   │   ├── core/             # 核心指令
│   │   └── index.ts          # 指令入口
│   ├── enums/                # 枚举定义
│   │   ├── appEnum.ts        # 应用枚举
│   │   └── formEnum.ts       # 表单枚举
│   ├── hooks/                # 组合式函数
│   │   ├── core/             # 核心 hooks
│   │   └── index.ts          # hooks 入口
│   ├── locales/              # 国际化
│   │   ├── langs/            # 语言文件
│   │   │   ├── en.json       # 英文
│   │   │   └── zh.json       # 中文
│   │   └── index.ts          # 国际化配置
│   ├── mock/                 # Mock 数据
│   │   ├── browser/          # 浏览器 Mock
│   │   │   ├── handlers/     # 处理器
│   │   │   └── index.ts      # Mock 入口
│   │   ├── temp/             # 临时数据
│   │   └── upgrade/          # 升级相关
│   ├── plugins/              # 插件
│   │   ├── echarts.ts        # ECharts 插件
│   │   └── index.ts          # 插件入口
│   ├── router/               # 路由配置
│   │   ├── core/             # 路由核心
│   │   │   ├── ComponentLoader.ts          # 组件加载器
│   │   │   ├── IframeRouteManager.ts       # Iframe 路由管理
│   │   │   ├── MenuProcessor.ts            # 菜单处理器
│   │   │   ├── RoutePermissionValidator.ts # 路由权限验证
│   │   │   ├── RouteRegistry.ts            # 路由注册器
│   │   │   ├── RouteTransformer.ts         # 路由转换器
│   │   │   ├── RouteValidator.ts           # 路由验证器
│   │   │   └── index.ts
│   │   ├── guards/           # 路由守卫
│   │   │   ├── afterEach.ts  # 后置守卫
│   │   │   └── beforeEach.ts # 前置守卫
│   │   ├── modules/          # 路由模块
│   │   ├── routes/           # 路由定义
│   │   │   ├── asyncRoutes.ts # 异步路由
│   │   │   └── staticRoutes.ts # 静态路由
│   │   ├── index.ts          # 路由入口
│   │   └── routesAlias.ts    # 路由别名
│   ├── store/                # 状态管理
│   │   ├── modules/          # Store 模块
│   │   │   ├── menu.ts       # 菜单状态
│   │   │   ├── setting.ts    # 设置状态
│   │   │   ├── table.ts      # 表格状态
│   │   │   ├── user.ts       # 用户状态
│   │   │   └── worktab.ts    # 工作标签状态
│   │   └── index.ts          # Store 入口
│   ├── types/                # 类型定义
│   │   ├── api/              # API 类型
│   │   ├── common/           # 公共类型
│   │   ├── component/        # 组件类型
│   │   ├── config/           # 配置类型
│   │   ├── directive/        # 指令类型
│   │   ├── router/           # 路由类型
│   │   ├── store/            # Store 类型
│   │   └── index.ts          # 类型入口
│   ├── utils/                # 工具函数
│   │   ├── constants/        # 常量
│   │   ├── form/             # 表单工具
│   │   ├── http/             # HTTP 工具
│   │   ├── navigation/       # 导航工具
│   │   ├── socket/           # Socket 工具
│   │   ├── storage/          # 存储工具
│   │   ├── sys/              # 系统工具
│   │   ├── table/            # 表格工具
│   │   ├── ui/               # UI 工具
│   │   ├── index.ts          # 工具入口
│   │   └── router.ts         # 路由工具
│   ├── views/                # 页面视图
│   │   ├── auth/             # 认证页面
│   │   │   ├── forget-password/ # 忘记密码
│   │   │   ├── login/        # 登录
│   │   │   └── register/     # 注册
│   │   ├── dashboard/        # 仪表盘
│   │   │   └── console/      # 控制台
│   │   ├── exception/        # 异常页面
│   │   │   ├── 403/          # 403 页面
│   │   │   ├── 404/          # 404 页面
│   │   │   └── 500/          # 500 页面
│   │   ├── index/            # 首页
│   │   └── outside/          # 外部页面
│   ├── App.vue               # 根组件
│   ├── env.d.ts              # 环境变量类型
│   └── main.ts               # 应用入口
├── .env                       # 环境变量
├── .env.development           # 开发环境变量
├── .env.production            # 生产环境变量
├── .gitattributes             # Git 属性
├── .gitignore                 # Git 忽略文件
├── .prettierignore            # Prettier 忽略文件
├── .prettierrc                # Prettier 配置
├── .stylelintignore           # Stylelint 忽略文件
├── .stylelintrc.cjs           # Stylelint 配置
├── commitlint.config.cjs      # Commitlint 配置
├── eslint.config.mjs          # ESLint 配置
├── index.html                 # HTML 模板
├── package.json               # 项目配置
├── pnpm-lock.yaml             # 依赖锁定文件
├── tsconfig.json              # TypeScript 配置
└── vite.config.ts             # Vite 配置

核心功能实现

1. 路由系统

路由架构

项目采用 静态路由 + 动态路由 的混合路由架构:

  • 静态路由:不需要权限验证的页面(登录、注册、异常页面等)
  • 动态路由:根据用户权限动态加载的路由

核心模块

路由注册器 (RouteRegistry)

负责动态路由的注册和卸载:

typescript
class RouteRegistry {
  // 注册动态路由
  register(menuList: MenuItem[]): void;

  // 卸载动态路由
  unregister(): void;

  // 检查是否已注册
  isRegistered(): boolean;
}

菜单处理器 (MenuProcessor)

负责菜单数据的获取和处理:

typescript
class MenuProcessor {
  // 获取菜单列表(支持前端模式和后端模式)
  async getMenuList(): Promise<MenuItem[]>;

  // 验证菜单数据
  validateMenuList(menuList: MenuItem[]): boolean;
}

路由权限验证器 (RoutePermissionValidator)

验证用户是否有权限访问目标路径:

typescript
class RoutePermissionValidator {
  // 验证路径权限
  static validatePath(
    targetPath: string,
    menuList: MenuItem[],
    homePath: string,
  ): { path: string; hasPermission: boolean };
}

路由守卫

前置守卫 (beforeEach)

完整的路由导航守卫流程:

  1. 启动进度条 - 根据 showNprogress 配置显示进度条
  2. 检查登录状态 - 未登录用户跳转到登录页
  3. 路由初始化检查 - 防止路由初始化失败后死循环
  4. 动态路由注册 - 首次访问时获取菜单并注册路由
  5. 根路径重定向 - 将根路径重定向到首页
  6. 权限验证 - 验证用户是否有权限访问目标路径
  7. 页面标题设置 - 设置页面标题
  8. 工作标签页管理 - 添加工作标签页

后置守卫 (afterEach)

  • 关闭进度条
  • 关闭加载动画

路由配置

typescript
// vite.config.ts
export default defineConfig({
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
      "@views": resolvePath("src/views"),
      "@imgs": resolvePath("src/assets/images"),
      "@icons": resolvePath("src/assets/icons"),
      "@utils": resolvePath("src/utils"),
      "@stores": resolvePath("src/store"),
      "@styles": resolvePath("src/assets/styles"),
    },
  },
});

2. HTTP 请求封装

核心特性

  • 请求拦截器:自动添加 Token、设置请求头
  • 响应拦截器:统一处理响应数据、错误处理
  • 401 防抖机制:防止多次弹出未授权提示
  • 自动重试:请求失败自动重试(可配置)
  • 统一消息提示:成功/失败消息提示

实现细节

Axios 实例配置

typescript
const axiosInstance = axios.create({
  timeout: 15000, // 请求超时时间
  baseURL: VITE_API_URL, // API 基础路径
  withCredentials: true, // 跨域携带 cookie
  validateStatus: (status) => status >= 200 && status < 300,
});

请求拦截器

typescript
axiosInstance.interceptors.request.use(
  (request: InternalAxiosRequestConfig) => {
    // 自动添加 Token
    const { accessToken } = useUserStore();
    if (accessToken) {
      request.headers.set("Authorization", accessToken);
    }

    // 自动设置 Content-Type
    if (request.data && !(request.data instanceof FormData)) {
      request.headers.set("Content-Type", "application/json");
      request.data = JSON.stringify(request.data);
    }

    return request;
  },
);

响应拦截器

typescript
axiosInstance.interceptors.response.use(
  (response: AxiosResponse<BaseResponse>) => {
    const { code, msg } = response.data;

    // 成功响应
    if (code === ApiStatus.success) {
      return response;
    }

    // 401 未授权
    if (code === ApiStatus.unauthorized) {
      handleUnauthorizedError(msg);
    }

    // 其他错误
    throw createHttpError(msg || "请求失败", code);
  },
  (error) => {
    // 网络错误处理
    if (error.response?.status === ApiStatus.unauthorized) {
      handleUnauthorizedError();
    }
    return Promise.reject(handleError(error));
  },
);

401 防抖机制

typescript
let isUnauthorizedErrorShown = false;
let unauthorizedTimer: NodeJS.Timeout | null = null;

function handleUnauthorizedError(message?: string): never {
  const error = createHttpError(message || "未授权", ApiStatus.unauthorized);

  if (!isUnauthorizedErrorShown) {
    isUnauthorizedErrorShown = true;
    logOut(); // 退出登录

    // 3秒后重置状态
    unauthorizedTimer = setTimeout(resetUnauthorizedError, 3000);

    showError(error, true);
    throw error;
  }

  throw error;
}

自动重试机制

typescript
async function retryRequest<T>(
  config: ExtendedAxiosRequestConfig,
  retries: number = 0,
): Promise<T> {
  try {
    return await request<T>(config);
  } catch (error) {
    // 判断是否需要重试
    if (retries > 0 && error instanceof HttpError && shouldRetry(error.code)) {
      await delay(1000); // 延迟1秒
      return retryRequest<T>(config, retries - 1);
    }
    throw error;
  }
}

function shouldRetry(statusCode: number) {
  return [
    ApiStatus.requestTimeout, // 408
    ApiStatus.internalServerError, // 500
    ApiStatus.badGateway, // 502
    ApiStatus.serviceUnavailable, // 503
    ApiStatus.gatewayTimeout, // 504
  ].includes(statusCode);
}

API 方法

typescript
const api = {
  get<T>(config: ExtendedAxiosRequestConfig) {
    return retryRequest<T>({ ...config, method: "GET" });
  },
  post<T>(config: ExtendedAxiosRequestConfig) {
    return retryRequest<T>({ ...config, method: "POST" });
  },
  put<T>(config: ExtendedAxiosRequestConfig) {
    return retryRequest<T>({ ...config, method: "PUT" });
  },
  del<T>(config: ExtendedAxiosRequestConfig) {
    return retryRequest<T>({ ...config, method: "DELETE" });
  },
};

3. 状态管理

Pinia Store 架构

项目采用 Pinia 作为状态管理工具,支持模块化和持久化。

核心模块

  1. User Store - 用户状态管理
  2. Setting Store - 系统设置状态管理
  3. Menu Store - 菜单状态管理
  4. Worktab Store - 工作标签页状态管理
  5. Table Store - 表格状态管理

持久化机制

版本化存储键管理

typescript
class StorageKeyManager {
  private version: string;

  constructor() {
    this.version = __APP_VERSION__; // 从环境变量获取版本号
  }

  // 生成版本化的存储键
  getStorageKey(storeId: string): string {
    return `sys-v${this.version}-${storeId}`;
  }
}

持久化配置

typescript
import { createPersistedState } from "pinia-plugin-persistedstate";

store.use(
  createPersistedState({
    key: (storeId: string) => storageKeyManager.getStorageKey(storeId),
    storage: localStorage,
    serializer: {
      serialize: JSON.stringify,
      deserialize: JSON.parse,
    },
  }),
);

自动数据迁移

当版本更新时,自动迁移旧版本数据到当前版本:

typescript
// 存储键示例
// v1.0.0: sys-v1.0.0-user
// v1.0.1: sys-v1.0.1-user
// 自动从 sys-v1.0.0-user 迁移到 sys-v1.0.1-user

User Store

用户状态管理模块,管理用户登录状态、个人信息、语言设置等。

状态定义

typescript
{
  language: LanguageEnum,              // 语言设置
  isLogin: boolean,                    // 登录状态
  isLock: boolean,                     // 锁屏状态
  lockPassword: string,                // 锁屏密码
  info: Partial<Api.Auth.UserInfo>,    // 用户信息
  searchHistory: AppRouteRecord[],     // 搜索历史
  accessToken: string,                 // 访问令牌
  refreshToken: string                 // 刷新令牌
}

核心方法

typescript
// 设置用户信息
setUserInfo(newInfo: Api.Auth.UserInfo): void

// 设置登录状态
setLoginStatus(status: boolean): void

// 设置语言
setLanguage(lang: LanguageEnum): void

// 设置令牌
setToken(newAccessToken: string, newRefreshToken?: string): void

// 退出登录
logOut(): void

// 检查并清理工作台标签页(不同用户登录时)
checkAndClearWorktabs(): void

退出登录流程

typescript
function logOut() {
  // 1. 保存当前用户 ID
  const currentUserId = info.value.userId;
  if (currentUserId) {
    localStorage.setItem(StorageConfig.LAST_USER_ID_KEY, String(currentUserId));
  }

  // 2. 清空用户信息
  info.value = {};
  isLogin.value = false;
  isLock.value = false;
  lockPassword.value = "";
  accessToken.value = "";
  refreshToken.value = "";

  // 3. 清理路由和菜单
  sessionStorage.removeItem("iframeRoutes");
  useMenuStore().setHomePath("");
  resetRouterState(500);

  // 4. 跳转到登录页
  router.push({
    name: "Login",
    query: { redirect: currentRoute.fullPath },
  });
}

Setting Store

系统设置状态管理,管理主题、菜单、界面显示等配置。

状态定义

typescript
{
  // 菜单相关
  menuType: MenuTypeEnum,              // 菜单类型
  menuOpenWidth: number,               // 菜单展开宽度
  menuOpen: boolean,                   // 菜单是否展开
  dualMenuShowText: boolean,           // 双菜单是否显示文本

  // 主题相关
  systemThemeType: SystemThemeEnum,    // 系统主题类型
  systemThemeMode: SystemThemeEnum,    // 系统主题模式
  menuThemeType: MenuThemeEnum,        // 菜单主题类型
  systemThemeColor: string,            // 系统主题颜色

  // 界面显示
  showMenuButton: boolean,             // 显示菜单按钮
  showFastEnter: boolean,              // 显示快速入口
  showRefreshButton: boolean,          // 显示刷新按钮
  showCrumbs: boolean,                 // 显示面包屑
  showWorkTab: boolean,                // 显示工作标签
  showLanguage: boolean,               // 显示语言切换
  showNprogress: boolean,              // 显示进度条
  showSettingGuide: boolean,           // 显示设置引导
  showFestivalText: boolean,           // 显示节日文本
  watermarkVisible: boolean,           // 显示水印

  // 功能设置
  autoClose: boolean,                  // 自动关闭
  uniqueOpened: boolean,               // 唯一展开
  colorWeak: boolean,                  // 色弱模式
  refresh: boolean,                    // 刷新标记

  // 样式设置
  boxBorderMode: boolean,              // 边框模式
  pageTransition: string,              // 页面过渡效果
  tabStyle: string,                    // 标签页样式
  customRadius: string,                // 自定义圆角
  containerWidth: ContainerWidthEnum   // 容器宽度
}

核心方法

typescript
// 切换菜单布局
switchMenuLayouts(type: MenuTypeEnum): void

// 设置全局主题
setGlopTheme(theme: SystemThemeEnum, themeMode: SystemThemeEnum): void

// 切换菜单样式
switchMenuStyles(theme: MenuThemeEnum): void

// 设置 Element Plus 主题颜色
setElementTheme(theme: string): void

// 刷新页面
reload(): void

// 设置水印显示
setWatermarkVisible(visible: boolean): void

计算属性

typescript
// 获取菜单主题
const getMenuTheme = computed((): MenuThemeType => {
  const list = AppConfig.themeList.filter(
    (item) => item.theme === menuThemeType.value,
  );
  return isDark.value ? AppConfig.darkMenuStyles[0] : list[0];
});

// 判断是否为暗色模式
const isDark = computed((): boolean => {
  return systemThemeType.value === SystemThemeEnum.DARK;
});

// 获取菜单展开宽度
const getMenuOpenWidth = computed((): string => {
  return menuOpenWidth.value + "px";
});

// 获取自定义圆角
const getCustomRadius = computed((): string => {
  return customRadius.value + "rem";
});

4. 主题系统

主题类型

支持三种主题类型:

  1. 亮色主题 (Light) - 浅色背景
  2. 暗色主题 (Dark) - 深色背景
  3. 自动主题 (Auto) - 跟随系统主题

主题配置

typescript
// src/config/index.ts
{
  // 系统主题列表
  settingThemeList: [
    {
      name: 'Light',
      theme: SystemThemeEnum.LIGHT,
      color: ['#fff', '#fff'],
      img: configImages.themeStyles.light
    },
    {
      name: 'Dark',
      theme: SystemThemeEnum.DARK,
      color: ['#22252A'],
      img: configImages.themeStyles.dark
    },
    {
      name: 'System',
      theme: SystemThemeEnum.AUTO,
      color: ['#fff', '#22252A'],
      img: configImages.themeStyles.system
    }
  ],

  // 菜单主题列表
  themeList: [
    {
      theme: MenuThemeEnum.DESIGN,
      background: '#FFFFFF',
      systemNameColor: 'var(--art-gray-800)',
      iconColor: '#6B6B6B',
      textColor: '#29343D',
      img: configImages.menuStyles.design
    },
    {
      theme: MenuThemeEnum.DARK,
      background: '#191A23',
      systemNameColor: '#D9DADB',
      iconColor: '#BABBBD',
      textColor: '#BABBBD',
      img: configImages.menuStyles.dark
    },
    {
      theme: MenuThemeEnum.LIGHT,
      background: '#ffffff',
      systemNameColor: 'var(--art-gray-800)',
      iconColor: '#6B6B6B',
      textColor: '#29343D',
      img: configImages.menuStyles.light
    }
  ],

  // 系统主色
  systemMainColor: [
    '#5D87FF',
    '#B48DF3',
    '#1D84FF',
    '#60C041',
    '#38C0FC',
    '#F9901F',
    '#FF80C8'
  ]
}

主题初始化

typescript
// src/hooks/core/useTheme.ts
export function initializeTheme() {
  const settingStore = useSettingStore();
  const { systemThemeType, systemThemeColor } = storeToRefs(settingStore);

  // 初始化主题类型
  const theme = localStorage.getItem(StorageConfig.THEME_KEY);
  if (theme) {
    settingStore.setGlopTheme(theme as SystemThemeEnum, systemThemeType.value);
  }

  // 初始化主题颜色
  setElementThemeColor(systemThemeColor.value);

  // 监听系统主题变化
  if (systemThemeType.value === SystemThemeEnum.AUTO) {
    const darkModeMediaQuery = window.matchMedia(
      "(prefers-color-scheme: dark)",
    );
    darkModeMediaQuery.addEventListener("change", (e) => {
      document.documentElement.classList.toggle("dark", e.matches);
    });
  }
}

Element Plus 主题色动态设置

typescript
// src/utils/ui/colors.ts
export function setElementThemeColor(color: string) {
  const el = document.documentElement;
  el.style.setProperty("--el-color-primary", color);

  // 生成不同深度的颜色
  for (let i = 1; i <= 9; i++) {
    const lightColor = lighten(color, i / 10);
    el.style.setProperty(`--el-color-primary-light-${i}`, lightColor);
  }

  // 生成深色
  const darkColor = darken(color, 0.1);
  el.style.setProperty("--el-color-primary-dark-2", darkColor);
}

5. 国际化

配置

typescript
// src/locales/index.ts
import { createI18n } from "vue-i18n";
import enMessages from "./langs/en.json";
import zhMessages from "./langs/zh.json";

const messages = {
  [LanguageEnum.EN]: enMessages,
  [LanguageEnum.ZH]: zhMessages,
};

const i18n = createI18n({
  locale: getDefaultLanguage(), // 从存储中获取语言设置
  legacy: false, // 使用 Composition API 模式
  globalInjection: true, // 全局注入 $t 函数
  fallbackLocale: LanguageEnum.ZH, // 回退语言
  messages,
});

// 全局翻译函数
export const $t = i18n.global.t as Translation;

语言选项

typescript
export const languageOptions = [
  { value: LanguageEnum.ZH, label: "简体中文" },
  { value: LanguageEnum.EN, label: "English" },
];

使用方式

在模板中使用

vue
<template>
  <div>{{ $t("common.submit") }}</div>
</template>

在脚本中使用

typescript
import { $t } from "@/locales";

const message = $t("common.submit");

切换语言

typescript
import { useUserStore } from "@/store/modules/user";

const userStore = useUserStore();
userStore.setLanguage(LanguageEnum.EN);

6. Mock 服务

MSW 配置

typescript
// src/mock/browser/index.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";

export const worker = setupWorker(...handlers);

export async function setupMockServiceWorker() {
  const VITE_ENABLE_MSW = import.meta.env.VITE_ENABLE_MSW === "true";

  if (!VITE_ENABLE_MSW) {
    console.log("[MSW] 未启用,使用真实 API");
    return false;
  }

  try {
    await worker.start({
      onUnhandledRequest: "bypass", // 未处理的请求直接通过
    });
    console.log("[MSW] Mock Service Worker 已启动");
    return true;
  } catch (error) {
    console.error("[MSW] 启动失败:", error);
    return false;
  }
}

Handler 示例

typescript
// src/mock/browser/handlers/auth.ts
import { http, HttpResponse } from "msw";

export const authHandlers = [
  // 登录
  http.post("/api/login", () => {
    return HttpResponse.json({
      code: 200,
      msg: "登录成功",
      data: {
        accessToken: "mock-access-token",
        refreshToken: "mock-refresh-token",
      },
    });
  }),

  // 获取用户信息
  http.get("/api/user/info", () => {
    return HttpResponse.json({
      code: 200,
      msg: "获取成功",
      data: {
        userId: 1,
        username: "admin",
        nickname: "管理员",
        avatar: "https://example.com/avatar.jpg",
        roles: ["admin"],
      },
    });
  }),
];

环境变量控制

bash
# .env.development
VITE_ENABLE_MSW = true   # 启用 Mock

# .env.production
# 不设置或设置为 false,使用真实 API

MSW 方案优势

1. 真实的网络环境

MSW 在 Service Worker 层面拦截请求,而不是在应用层面 Mock。这意味着:

  • 请求仍然通过真实的网络层发送
  • 可以在 DevTools 的 Network 面板中看到完整的请求和响应
  • 可以测试请求超时、网络错误等真实场景
  • 更接近生产环境的实际表现

2. 无需修改代码

  • 不需要在代码中引入 Mock 库或修改业务逻辑
  • 只需在入口文件启动 MSW,业务代码完全无感知
  • 切换 Mock 和真实 API 只需修改环境变量,无需改动代码

3. 支持浏览器和 Node.js 环境

  • 浏览器环境:使用 Service Worker 拦截请求
  • Node.js 环境:使用 Node.js 原生模块拦截请求
  • 可以在单元测试、集成测试中使用同一套 Mock 数据

4. 与真实 API 无缝切换

  • 未定义 Mock 的请求会自动转发到真实服务器
  • 可以部分 Mock,部分使用真实 API
  • 方便渐进式开发和测试

5. 类型安全

  • 支持 TypeScript,提供完整的类型定义
  • 可以与 OpenAPI 规范集成,自动生成 Mock Handler

6. 开发体验优秀

  • 支持热更新,修改 Mock 数据无需重启服务
  • 提供丰富的调试工具和日志
  • 社区活跃,文档完善

Mock 方案对比

特性MSWMock.jsApifox传统 Mock Server
实现方式Service Worker 拦截XHR/Fetch 劫持独立 Mock 服务器独立服务器
网络请求✅ 真实网络请求❌ 劫持请求,不发送✅ 真实网络请求✅ 真实网络请求
DevTools 支持✅ 可在 Network 查看❌ 无法查看✅ 可在 Network 查看✅ 可在 Network 查看
代码侵入性✅ 无侵入⚠️ 需要引入库✅ 无侵入✅ 无侵入
环境支持✅ 浏览器 + Node.js✅ 浏览器 + Node.js❌ 仅浏览器✅ 浏览器 + Node.js
部分 Mock✅ 支持⚠️ 需要配置✅ 支持✅ 支持
热更新✅ 支持⚠️ 需要配置✅ 支持❌ 需要重启
类型安全✅ TypeScript 支持⚠️ 社区类型定义✅ 自动生成⚠️ 需手动维护
学习成本⚠️ 中等✅ 低⚠️ 中等⚠️ 高
部署复杂度✅ 零部署✅ 零部署⚠️ 需要部署服务⚠️ 需要部署服务
团队协作✅ Mock 数据在代码中✅ Mock 数据在代码中✅ 云端同步⚠️ 需要共享服务器
OpenAPI 集成✅ 支持❌ 不支持✅ 深度集成⚠️ 需手动配置
成本✅ 开源免费✅ 开源免费⚠️ 免费版有限制⚠️ 需要服务器成本

详细对比分析

1. Mock.js

优点:

  • 学习成本低,API 简单直观
  • 内置数据生成器,可以快速生成随机数据
  • 社区成熟,使用广泛

缺点:

  • 劫持 XHR/Fetch,不是真实的网络请求
  • 无法在 DevTools 中查看请求和响应
  • 对 Fetch API 的支持不够完善
  • 难以模拟网络错误、超时等场景
  • 需要在业务代码中引入 Mock 数据,有侵入性

适用场景:

  • 快速原型开发
  • 简单的 Mock 需求
  • 不需要真实网络请求的场景

2. Apifox

优点:

  • 提供 Web 界面,可视化配置 Mock
  • 支持 OpenAPI 规范,自动生成 Mock 数据
  • 团队协作方便,云端同步
  • 集成 API 文档、调试、Mock、测试等功能

缺点:

  • 需要启动独立的 Mock 服务器
  • 免费版功能有限制
  • 依赖外部服务,离线开发受限
  • 需要额外的学习和配置成本

适用场景:

  • 团队协作项目
  • 需要完整的 API 管理平台
  • 有 OpenAPI 规范的项目

3. MSW (Mock Service Worker)

优点:

  • 真实的网络请求,开发体验最佳
  • 无代码侵入,业务代码无感知
  • 支持浏览器和 Node.js 环境
  • 可以部分 Mock,部分使用真实 API
  • 支持 TypeScript,类型安全
  • 开源免费,社区活跃

缺点:

  • 学习成本略高,需要理解 Service Worker
  • 配置相对复杂
  • 需要单独定义 Handler

适用场景:

  • 追求真实开发体验的项目
  • 需要在测试中使用 Mock 的项目
  • 前后端分离开发,前端独立开发

为什么选择 MSW?

本项目选择 MSW 作为 Mock 方案,主要基于以下考虑:

  1. 真实的开发体验 - MSW 在 Service Worker 层拦截请求,保留了真实的网络请求流程,开发者可以在 DevTools 中查看完整的请求和响应,这与生产环境完全一致。

  2. 无侵入性 - 业务代码不需要任何修改,只需在入口文件启动 MSW。切换 Mock 和真实 API 只需修改环境变量,极大地降低了开发和测试的成本。

  3. 灵活性 - 支持部分 Mock,未定义 Mock 的请求会自动转发到真实服务器。这在后端接口部分完成时特别有用,前端可以 Mock 未完成的接口,使用真实的已完成接口。

  4. 测试友好 - 同一套 Mock 数据可以在浏览器和 Node.js 环境中使用,方便单元测试和集成测试。

  5. 类型安全 - 支持 TypeScript,可以与项目的类型系统完美集成,避免 Mock 数据与实际 API 不一致的问题。

  6. 零部署成本 - 不需要启动额外的 Mock 服务器,Mock 数据直接在代码仓库中管理,方便版本控制和团队协作。

最佳实践建议

1. Mock 数据管理

typescript
// 推荐:将 Mock 数据单独管理
// src/mock/data/user.ts
export const userData = {
  userId: 1,
  username: "admin",
  nickname: "管理员",
};

// src/mock/browser/handlers/user.ts
import { userData } from "../data/user";

export const userHandlers = [
  http.get("/api/user/info", () => {
    return HttpResponse.json({
      code: 200,
      data: userData,
    });
  }),
];

2. 环境隔离

typescript
// 开发环境启用 Mock,生产环境禁用
const VITE_ENABLE_MSW = import.meta.env.VITE_ENABLE_MSW === "true";

// 只在开发环境启动 MSW
if (import.meta.env.DEV && VITE_ENABLE_MSW) {
  setupMockServiceWorker();
}

3. 错误场景模拟

typescript
// 模拟网络错误
http.get("/api/user/info", () => {
  return new Response(null, { status: 500 });
});

// 模拟网络超时
http.get("/api/user/info", async () => {
  await delay(30000); // 延迟 30 秒
  return HttpResponse.json({ code: 200 });
});

4. 动态 Mock 数据

typescript
// 使用内存数据库模拟真实场景
let users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

http.get("/api/users", () => {
  return HttpResponse.json({ code: 200, data: users });
});

http.post("/api/users", async ({ request }) => {
  const newUser = await request.json();
  users.push(newUser);
  return HttpResponse.json({ code: 200, data: newUser });
});

7. 组件库

图表组件

  • ArtBarChart - 柱状图
  • ArtLineChart - 折线图
  • ArtRingChart - 环形图
  • ArtRadarChart - 雷达图
  • ArtScatterChart - 散点图
  • ArtKLineChart - K线图
  • ArtHBarChart - 横向柱状图
  • ArtDualBarCompareChart - 双柱对比图

表单组件

  • ArtForm - 表单组件
  • ArtSearchBar - 搜索栏
  • ArtWangEditor - 富文本编辑器
  • ArtDragVerify - 拖动验证
  • ArtExcelImport - Excel 导入
  • ArtExcelExport - Excel 导出
  • ArtButtonMore - 更多按钮
  • ArtButtonTable - 表格按钮

布局组件

  • ArtHeaderBar - 顶部栏
  • ArtSidebarMenu - 侧边栏菜单
  • ArtHorizontalMenu - 水平菜单
  • ArtMixedMenu - 混合菜单
  • ArtBreadcrumb - 面包屑
  • ArtWorkTab - 工作标签页
  • ArtPageContent - 页面内容
  • ArtSettingsPanel - 设置面板
  • ArtScreenLock - 锁屏
  • ArtGlobalSearch - 全局搜索
  • ArtFastEnter - 快速入口
  • ArtNotification - 通知
  • ArtWatermark - 水印

卡片组件

  • ArtStatsCard - 统计卡片
  • ArtDataListCard - 数据列表卡片
  • ArtTimelineListCard - 时间线列表卡片
  • ArtProgressCard - 进度卡片
  • ArtImageCard - 图片卡片
  • ArtBarChartCard - 柱状图卡片
  • ArtLineChartCard - 折线图卡片
  • ArtDonutChartCard - 环形图卡片

表格组件

  • ArtTable - 表格组件
  • ArtTableHeader - 表格头部

媒体组件

  • ArtVideoPlayer - 视频播放器
  • ArtCutterImg - 图片裁剪

文本特效组件

  • ArtCountTo - 数字滚动
  • ArtTextScroll - 文本滚动
  • ArtFestivalTextScroll - 节日文本滚动

开发指南

环境要求

  • Node.js >= 20.19.0
  • pnpm >= 8.8.0

安装依赖

bash
pnpm install
bash
pnpm dev
bash
pnpm build

代码检查

bash
pnpm lint
bash
pnpm fix
bash
pnpm lint:prettier
bash
pnpm lint:stylelint

Git 提交规范

项目使用 Commitizen 规范化提交信息:

bash
pnpm commit

提交类型:

  • feat: 新功能
  • fix: 修复 bug
  • docs: 文档更新
  • style: 代码格式调整
  • refactor: 重构代码
  • perf: 性能优化
  • test: 测试相关
  • chore: 构建/工具相关
  • revert: 回退提交

目录别名

typescript
// vite.config.ts
{
  resolve: {
    alias: {
      '@': 'src',
      '@views': 'src/views',
      '@imgs': 'src/assets/images',
      '@icons': 'src/assets/icons',
      '@utils': 'src/utils',
      '@stores': 'src/store',
      '@styles': 'src/assets/styles'
    }
  }
}

自动导入

项目配置了自动导入功能:

API 自动导入

typescript
// vite.config.ts
AutoImport({
  imports: ["vue", "vue-router", "pinia", "@vueuse/core"],
  dts: "src/types/import/auto-imports.d.ts",
  resolvers: [ElementPlusResolver()],
});

自动导入的 API:

  • vue: ref, reactive, computed, watch, onMounted 等
  • vue-router: useRouter, useRoute 等
  • pinia: defineStore, storeToRefs 等
  • @vueuse/core: useMouse, useStorage 等

组件自动导入

typescript
// vite.config.ts
Components({
  dts: "src/types/import/components.d.ts",
  resolvers: [ElementPlusResolver()],
});

Element Plus 组件自动按需导入,无需手动 import。


部署指南

环境变量配置

bash
# 应用部署基础路径
VITE_BASE_URL = /

# API 请求基础路径
VITE_API_URL = /

# 是否启用 MSW Mock
VITE_ENABLE_MSW = true

# 是否删除 console
VITE_DROP_CONSOLE = false
bash
# 应用部署基础路径
VITE_BASE_URL = /

# API 地址前缀
VITE_API_URL = https://api.example.com

# 是否删除 console
VITE_DROP_CONSOLE = true

构建优化

代码分割

typescript
// vite.config.ts
{
  build: {
    target: 'es2015',
    outDir: 'dist',
    chunkSizeWarningLimit: 2000,
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,    // 删除 console
        drop_debugger: true    // 删除 debugger
      }
    }
  }
}

Gzip 压缩

typescript
// vite.config.ts
viteCompression({
  verbose: false, // 不输出压缩结果
  disable: false, // 启用压缩
  algorithm: "gzip", // 压缩算法
  ext: ".gz", // 文件扩展名
  threshold: 10240, // 大于 10KB 才压缩
  deleteOriginFile: false, // 不删除原文件
});

依赖预构建

typescript
// vite.config.ts
optimizeDeps: {
  include: [
    "echarts/core",
    "echarts/charts",
    "echarts/components",
    "echarts/renderers",
    "xlsx",
    "xgplayer",
    "crypto-js",
    "file-saver",
    "vue-img-cutter",
    "element-plus/es",
    "element-plus/es/components/*/style/css",
    "element-plus/es/components/*/style/index",
  ];
}

部署方式

nginx
server {
    listen 80;
    server_name example.com;

    root /var/www/art-design-pro/dist;
    index index.html;

    # 开启 gzip 压缩
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;

    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # SPA 路由支持
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API 代理
    location /api/ {
        proxy_pass http://backend-server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
dockerfile
# 构建阶段
FROM node:20-alpine as build-stage

WORKDIR /app

# 安装 pnpm
RUN npm install -g pnpm

# 复制依赖文件
COPY package.json pnpm-lock.yaml ./

# 安装依赖
RUN pnpm install --frozen-lockfile

# 复制源代码
COPY . .

# 构建应用
RUN pnpm build

# 生产阶段
FROM nginx:alpine as production-stage

# 复制构建产物
COPY --from=build-stage /app/dist /usr/share/nginx/html

# 复制 nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
bash
# 构建镜像
docker build -t art-design-pro .

# 运行容器
docker run -d -p 80:80 art-design-pro

总结

CDesign Pro 是一个功能完善、架构清晰的后台管理系统脚手架。它采用了最新的前端技术栈,提供了完整的权限管理、动态路由、主题配置等功能,适合作为企业级后台管理系统的起点。

核心优势

  1. 现代化技术栈 - 使用 Vue 3 + TypeScript + Vite,开发体验优秀
  2. 完善的架构设计 - 模块化、可扩展、易维护
  3. 丰富的功能特性 - 权限管理、主题配置、国际化等开箱即用
  4. 优秀的开发体验 - 自动导入、热更新、代码规范等工具齐全
  5. 生产就绪 - 构建优化、部署方案完善

适用场景

  • 企业级后台管理系统
  • 管理后台脚手架
  • 中型项目快速启动
  • 学习 Vue 3 + TypeScript 最佳实践

文档版本: 1.0.0
最后更新: 2026-04-11
维护团队: CDesign Pro Team