vben-admin-一个前端开箱即用的企业级管理系统框架

打印 上一主题 下一主题

主题 1558|帖子 1558|积分 4689

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Vben Admin企业级管理系统框架

目次


  • 框架先容
  • 核心特点
  • 项目结构
  • 快速开始
  • 核心功能详解
  • 最佳实践
  • 常见问题与解决方案
  • 资源与社区
1. 框架先容

1.1 什么是Vben Admin

Vben Admin是一个基于Vue 3、Vite、TypeScript和Ant Design Vue的企业级中后台管理系统框架。它提供了一套完备的解决方案,资助开发者快速搭建当代化的管理系统。
1.2 框架优势



  • 开箱即用:提供丰富的组件和功能,减少重复开发
  • 高度可定制:支持主题定制、布局定制、组件定制等
  • 性能良好:基于Vue 3和Vite,提供极速的开发体验和良好的性能
  • 类型安全:全面支持TypeScript,提供类型安全和更好的开发体验
  • 社区活跃:拥有活跃的社区和持续的更新维护
1.3 适用场景



  • 企业级中后台管理系统
  • 数据可视化平台
  • 内容管理系统
  • 工作流管理系统
  • 权限管理系统
2. 核心特点

2.1 技术栈先进



  • Vue 3:接纳最新的Vue 3框架,享受Composition API带来的开发体验提拔
  • TypeScript:全面支持TypeScript,提供类型安全和更好的开发体验
  • Vite:使用Vite作为构建工具,提供极速的开发体验
  • Ant Design Vue:基于Ant Design设计规范,提供丰富的UI组件
  • Pinia:使用Pinia进行状态管理,提供更好的TypeScript支持
  • Vue Router:使用Vue Router进行路由管理,支持动态路由
2.2 功能完满



  • 权限管理:基于角色的访问控制(RBAC),支持动态路由和权限验证
  • 主题设置:支持多主题切换,可自定义主题色和布局
  • 国际化:内置国际化解决方案,支持多语言切换
  • 组件封装:封装了常用的业务组件,如表格、表单、图表等
  • Mock数据:内置Mock数据服务,方便前后端分离开发
  • 错误处置处罚:内置全局错误处置处罚机制,提供友爱的错误提示
  • 日志系统:内置日志系统,方便问题排查和用户举动分析
2.3 开发体验



  • 代码规范:接纳ESLint、Prettier等工具包管代码质量
  • Git提交规范:使用commitlint规范Git提交信息
  • 自动导入:支持组件和API的自动导入,减少重复代码
  • 热更新:支持模块热更新,提高开发服从
  • TypeScript支持:提供完备的类型定义,提高开发服从和代码质量
  • 开发工具:提供丰富的开发工具,如调试工具、性能分析工具等
3. 项目结构

  1. ├── build                 # 构建相关配置
  2. ├── mock                  # Mock数据
  3. ├── public                # 静态资源
  4. ├── src                   # 源代码
  5. │   ├── api               # API接口
  6. │   ├── assets            # 静态资源
  7. │   ├── components        # 公共组件
  8. │   ├── design            # 样式文件
  9. │   ├── enums             # 枚举定义
  10. │   ├── hooks             # Vue Hooks
  11. │   ├── layouts           # 布局组件
  12. │   ├── locales           # 国际化资源
  13. │   ├── router            # 路由配置
  14. │   ├── settings          # 项目配置
  15. │   ├── store              # 状态管理
  16. │   ├── utils              # 工具函数
  17. │   ├── views              # 页面组件
  18. │   ├── App.vue            # 根组件
  19. │   └── main.ts            # 入口文件
  20. ├── types                  # 类型定义
  21. ├── .env                   # 环境变量
  22. ├── .eslintrc.js           # ESLint配置
  23. ├── .prettierrc            # Prettier配置
  24. ├── package.json           # 项目依赖
  25. ├── tsconfig.json          # TypeScript配置
  26. └── vite.config.ts         # Vite配置
复制代码
3.1 核心目次阐明

src/api

API接口定义,按模块划分,每个模块对应一个文件。
  1. // src/api/sys/user.ts
  2. import { defHttp } from '/@/utils/http/axios';
  3. enum Api {
  4.   UserList = '/system/user/list',
  5.   UserInfo = '/system/user/info',
  6.   UserAdd = '/system/user/add',
  7.   UserEdit = '/system/user/edit',
  8.   UserDelete = '/system/user/delete',
  9. }
  10. export const getUserList = (params) => defHttp.get({ url: Api.UserList, params });
  11. export const getUserInfo = (id) => defHttp.get({ url: Api.UserInfo, params: { id } });
  12. export const addUser = (data) => defHttp.post({ url: Api.UserAdd, data });
  13. export const editUser = (data) => defHttp.post({ url: Api.UserEdit, data });
  14. export const deleteUser = (id) => defHttp.delete({ url: Api.UserDelete, params: { id } });
复制代码
src/components

公共组件,包括基础组件和业务组件。
  1. <!-- src/components/Button/src/BasicButton.vue -->
  2. <template>
  3.   <a-button v-bind="getBindValue" :class="getButtonClass" @click="handleClick">
  4.     <template v-if="icon">
  5.       <component :is="icon" />
  6.     </template>
  7.     <slot></slot>
  8.   </a-button>
  9. </template>
  10. <script lang="ts">
  11. import { defineComponent, computed, unref } from 'vue';
  12. import { Button } from 'ant-design-vue';
  13. import { useAttrs } from '/@/hooks/component/useAttrs';
  14. import { useDesign } from '/@/hooks/web/useDesign';
  15. export default defineComponent({
  16.   name: 'BasicButton',
  17.   components: { [Button.name]: Button },
  18.   props: {
  19.     type: {
  20.       type: String,
  21.       default: 'default',
  22.     },
  23.     icon: {
  24.       type: String,
  25.       default: '',
  26.     },
  27.   },
  28.   emits: ['click'],
  29.   setup(props, { emit }) {
  30.     const { prefixCls } = useDesign('basic-button');
  31.     const { getAttrs } = useAttrs();
  32.     const getBindValue = computed(() => ({ ...getAttrs(), ...props }));
  33.     const getButtonClass = computed(() => [prefixCls]);
  34.     function handleClick(e: MouseEvent) {
  35.       emit('click', e);
  36.     }
  37.     return {
  38.       getBindValue,
  39.       getButtonClass,
  40.       handleClick,
  41.     };
  42.   },
  43. });
  44. </script>
复制代码
src/hooks

Vue Hooks,提供可复用的逻辑。
  1. // src/hooks/web/usePermission.ts
  2. import { useUserStoreWithOut } from '/@/store/modules/user';
  3. export function usePermission() {
  4.   const userStore = useUserStoreWithOut();
  5.   const { role } = userStore.getUserInfo;
  6.   function hasPermission(value: string | string[]): boolean {
  7.     if (!value) {
  8.       return true;
  9.     }
  10.     if (!role) {
  11.       return false;
  12.     }
  13.     if (Array.isArray(value)) {
  14.       return value.some((item) => role.includes(item));
  15.     }
  16.     return role.includes(value);
  17.   }
  18.   return {
  19.     hasPermission,
  20.   };
  21. }
复制代码
src/router

路由设置,包括静态路由和动态路由。
  1. // src/router/routes/index.ts
  2. import type { AppRouteRecordRaw } from '/@/router/types';
  3. import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic';
  4. import { PageEnum } from '/@/enums/pageEnum';
  5. import { t } from '/@/hooks/web/useI18n';
  6. // 根路由
  7. export const RootRoute: AppRouteRecordRaw = {
  8.   path: '/',
  9.   name: 'Root',
  10.   redirect: PageEnum.BASE_HOME,
  11.   meta: {
  12.     title: 'Root',
  13.   },
  14. };
  15. // 登录路由
  16. export const LoginRoute: AppRouteRecordRaw = {
  17.   path: '/login',
  18.   name: 'Login',
  19.   component: () => import('/@/views/sys/login/Login.vue'),
  20.   meta: {
  21.     title: t('routes.basic.login'),
  22.   },
  23. };
  24. // 基础路由
  25. export const basicRoutes = [
  26.   LoginRoute,
  27.   RootRoute,
  28.   ...PAGE_NOT_FOUND_ROUTE,
  29.   ...REDIRECT_ROUTE,
  30. ];
复制代码
src/store

状态管理,使用Pinia进行状态管理。
  1. // src/store/modules/user.ts
  2. import { defineStore } from 'pinia';
  3. import { store } from '/@/store';
  4. import { UserInfo } from '/@/api/sys/model/userModel';
  5. import { getAuthCache, setAuthCache } from '/@/utils/auth';
  6. import { TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum';
  7. interface UserState {
  8.   userInfo: Nullable<UserInfo>;
  9.   token?: string;
  10.   roleList: string[];
  11.   sessionTimeout?: boolean;
  12. }
  13. export const useUserStore = defineStore({
  14.   id: 'app-user',
  15.   state: (): UserState => ({
  16.     userInfo: null,
  17.     token: undefined,
  18.     roleList: [],
  19.     sessionTimeout: false,
  20.   }),
  21.   getters: {
  22.     getUserInfo(): Nullable<UserInfo> {
  23.       return this.userInfo || getAuthCache<UserInfo>(USER_INFO_KEY);
  24.     },
  25.     getToken(): string {
  26.       return this.token || getAuthCache<string>(TOKEN_KEY);
  27.     },
  28.     getRoleList(): string[] {
  29.       return this.roleList.length > 0 ? this.roleList : [];
  30.     },
  31.     getSessionTimeout(): boolean {
  32.       return !!this.sessionTimeout;
  33.     },
  34.   },
  35.   actions: {
  36.     setToken(token: string | undefined) {
  37.       this.token = token;
  38.       setAuthCache(TOKEN_KEY, token);
  39.     },
  40.     setUserInfo(info: UserInfo | null) {
  41.       this.userInfo = info;
  42.       setAuthCache(USER_INFO_KEY, info);
  43.     },
  44.     setRoleList(roleList: string[]) {
  45.       this.roleList = roleList;
  46.     },
  47.     setSessionTimeout(flag: boolean) {
  48.       this.sessionTimeout = flag;
  49.     },
  50.     resetState() {
  51.       this.userInfo = null;
  52.       this.token = '';
  53.       this.roleList = [];
  54.       this.sessionTimeout = false;
  55.     },
  56.   },
  57. });
  58. // 在setup外使用
  59. export function useUserStoreWithOut() {
  60.   return useUserStore(store);
  61. }
复制代码
src/utils

工具函数,提供常用的工具方法。
  1. // src/utils/http/axios/index.ts
  2. import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios';
  3. import axios from 'axios';
  4. import { clone } from 'lodash-es';
  5. import { ContentTypeEnum } from '/@/enums/httpEnum';
  6. import { useUserStoreWithOut } from '/@/store/modules/user';
  7. import { joinTimestamp, formatRequestDate } from './helper';
  8. import { createAxiosTransform } from './axiosTransform';
  9. import { checkStatus } from './checkStatus';
  10. import { useGlobSetting } from '/@/hooks/setting';
  11. import { RequestOptions, Result } from '/@/types/axios';
  12. import { AxiosRetry } from './axiosRetry';
  13. import { getToken } from '/@/utils/auth';
  14. const globSetting = useGlobSetting();
  15. const urlPrefix = globSetting.urlPrefix;
  16. export class VAxios {
  17.   private axiosInstance: AxiosInstance;
  18.   private readonly options: CreateAxiosOptions;
  19.   constructor(options: CreateAxiosOptions) {
  20.     this.options = options;
  21.     this.axiosInstance = axios.create(options);
  22.     this.setupInterceptors();
  23.   }
  24.   private createAxios(config: AxiosRequestConfig) {
  25.     this.axiosInstance = axios.create(config);
  26.   }
  27.   private getTransform() {
  28.     const { transform } = this.options;
  29.     if (!transform) {
  30.       return;
  31.     }
  32.     return transform;
  33.   }
  34.   getAxios(): AxiosInstance {
  35.     return this.axiosInstance;
  36.   }
  37.   configAxios(config: AxiosRequestConfig) {
  38.     if (!this.axiosInstance) {
  39.       return;
  40.     }
  41.     this.createAxios(config);
  42.   }
  43.   setupInterceptors() {
  44.     const transform = this.getTransform();
  45.     if (!transform) {
  46.       return;
  47.     }
  48.     const {
  49.       requestInterceptors,
  50.       requestInterceptorsCatch,
  51.       responseInterceptors,
  52.       responseInterceptorsCatch,
  53.     } = transform;
  54.     // 请求拦截器配置处理
  55.     this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
  56.       // 请求之前处理config
  57.       if (requestInterceptors) {
  58.         config = requestInterceptors(config, this.options);
  59.       }
  60.       return config;
  61.     }, undefined);
  62.     // 请求拦截器错误捕获
  63.     requestInterceptorsCatch &&
  64.       this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch);
  65.     // 响应结果拦截器处理
  66.     this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
  67.       // 响应数据处理
  68.       if (responseInterceptors) {
  69.         res = responseInterceptors(res);
  70.       }
  71.       return res;
  72.     }, undefined);
  73.     // 响应结果拦截器错误捕获
  74.     responseInterceptorsCatch &&
  75.       this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
  76.   }
  77.   // 请求方法
  78.   request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
  79.     let conf: AxiosRequestConfig = clone(config);
  80.     const transform = this.getTransform();
  81.     const { requestOptions } = this.options;
  82.     const opt: RequestOptions = Object.assign({}, requestOptions, options);
  83.     const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {};
  84.     if (beforeRequestHook) {
  85.       conf = beforeRequestHook(conf, opt);
  86.     }
  87.     conf.requestOptions = opt;
  88.     conf = this.supportFormData(conf);
  89.     return new Promise((resolve, reject) => {
  90.       this.axiosInstance
  91.         .request<any, AxiosResponse<Result>>(conf)
  92.         .then((res: AxiosResponse<Result>) => {
  93.           if (transformResponseHook) {
  94.             try {
  95.               res = transformResponseHook(res, opt);
  96.             } catch (err) {
  97.               reject(err || new Error('request error!'));
  98.             }
  99.             return res;
  100.           }
  101.           resolve(res as unknown as Promise<T>);
  102.         })
  103.         .catch((e: Error | AxiosError) => {
  104.           if (requestCatchHook && e) {
  105.             reject(requestCatchHook(e, opt));
  106.             return;
  107.           }
  108.           if (Axios.isAxiosError(e)) {
  109.             // 这里对错误信息进行细分处理
  110.             const error = this.handleAxiosError(e);
  111.             reject(error);
  112.           } else {
  113.             let error = new Error(e.message) as Error & { code?: string };
  114.             error.code = 'NO_AXIOS_ERROR';
  115.             reject(error);
  116.           }
  117.         });
  118.     });
  119.   }
  120.   // 支持FormData
  121.   supportFormData(config: AxiosRequestConfig) {
  122.     const { requestOptions } = config;
  123.     const contentType = requestOptions?.headers?.['Content-Type'];
  124.     const isFormData =
  125.       contentType !== ContentTypeEnum.FORM_URLENCODED &&
  126.       contentType !== ContentTypeEnum.FORM_DATA;
  127.     if (isFormData && config.method?.toUpperCase() === 'GET') {
  128.       config.params = Object.assign({}, config.params || {});
  129.       Object.keys(config.params).forEach((key) => {
  130.         if (
  131.           config.params[key] !== null &&
  132.           typeof config.params[key] === 'object' &&
  133.           !Array.isArray(config.params[key])
  134.         ) {
  135.           config.params[key] = JSON.stringify(config.params[key]);
  136.         }
  137.       });
  138.     } else {
  139.       config.data = config.data || config.params;
  140.     }
  141.     return config;
  142.   }
  143.   // 处理Axios错误
  144.   private handleAxiosError(error: AxiosError) {
  145.     const { response } = error;
  146.     let errorMessage = '请求错误';
  147.     if (response) {
  148.       const { status, data } = response;
  149.       errorMessage = checkStatus(status, data);
  150.     }
  151.     const errorObj = new Error(errorMessage) as Error & { code?: string };
  152.     errorObj.code = error.response?.status?.toString() || 'NO_STATUS';
  153.     return errorObj;
  154.   }
  155. }
  156. // 创建axios实例
  157. export const createAxios = (opt?: Partial<CreateAxiosOptions>) => {
  158.   return new VAxios(
  159.     deepMerge(
  160.       {
  161.         timeout: 10 * 1000,
  162.         headers: { 'Content-Type': ContentTypeEnum.JSON },
  163.         // 数据处理方式
  164.         transform: clone(createAxiosTransform()),
  165.         // 配置项,下面的选项都可以在独立的接口请求中覆盖
  166.         requestOptions: {
  167.           // 默认将prefix 添加到url
  168.           joinPrefix: true,
  169.           // 是否返回原生响应头 比如:需要获取响应头时使用该属性
  170.           isReturnNativeResponse: false,
  171.           // 需要对返回数据进行处理
  172.           isTransformResponse: true,
  173.           // post请求的时候添加参数到url
  174.           joinParamsToUrl: false,
  175.           // 格式化提交参数时间
  176.           formatDate: true,
  177.           // 消息提示类型
  178.           errorMessageMode: 'message',
  179.           // 接口地址
  180.           apiUrl: globSetting.apiUrl,
  181.           // 请求拼接地址
  182.           urlPrefix: urlPrefix,
  183.           // 是否加入时间戳
  184.           joinTime: true,
  185.           // 忽略重复请求
  186.           ignoreCancelToken: true,
  187.           // 是否携带token
  188.           withToken: true,
  189.           retryRequest: {
  190.             isOpenRetry: true,
  191.             count: 5,
  192.             waitTime: 100,
  193.           },
  194.         },
  195.       },
  196.       opt || {},
  197.     ),
  198.   );
  199. };
  200. // 导出axios实例
  201. export const defHttp = createAxios();
复制代码
src/views

页面组件,按模块划分。
  1. <!-- src/views/dashboard/analysis/index.vue -->
  2. <template>
  3.   <div>
  4.     <PageHeader title="分析页" />
  5.     <a-card :bordered="false">
  6.       <a-row :gutter="16">
  7.         <a-col :span="6">
  8.           <a-statistic
  9.             title="总销售额"
  10.             :value="112893"
  11.             :precision="2"
  12.             :value-style="{ color: '#3f8600' }"
  13.           >
  14.             <template #prefix>
  15.               <TrendCharts option="up" />
  16.             </template>
  17.           </a-statistic>
  18.         </a-col>
  19.         <a-col :span="6">
  20.           <a-statistic
  21.             title="访问人数"
  22.             :value="8846"
  23.             :value-style="{ color: '#cf1322' }"
  24.           >
  25.             <template #prefix>
  26.               <UserOutlined />
  27.             </template>
  28.           </a-statistic>
  29.         </a-col>
  30.         <a-col :span="6">
  31.           <a-statistic
  32.             title="支付笔数"
  33.             :value="6560"
  34.             :value-style="{ color: '#3f8600' }"
  35.           >
  36.             <template #prefix>
  37.               <ShoppingOutlined />
  38.             </template>
  39.           </a-statistic>
  40.         </a-col>
  41.         <a-col :span="6">
  42.           <a-statistic
  43.             title="运营活动效果"
  44.             :value="78"
  45.             :suffix="'%'"
  46.             :value-style="{ color: '#3f8600' }"
  47.           >
  48.             <template #prefix>
  49.               <LikeOutlined />
  50.             </template>
  51.           </a-statistic>
  52.         </a-col>
  53.       </a-row>
  54.       <a-divider />
  55.       <a-row :gutter="16">
  56.         <a-col :span="12">
  57.           <VisitData />
  58.         </a-col>
  59.         <a-col :span="12">
  60.           <SalesData />
  61.         </a-col>
  62.       </a-row>
  63.     </a-card>
  64.   </div>
  65. </template>
  66. <script lang="ts">
  67. import { defineComponent } from 'vue';
  68. import { UserOutlined, ShoppingOutlined, LikeOutlined } from '@ant-design/icons-vue';
  69. import { PageHeader } from '/@/components/Page';
  70. import { TrendCharts } from '/@/components/Charts';
  71. import VisitData from './components/VisitData.vue';
  72. import SalesData from './components/SalesData.vue';
  73. export default defineComponent({
  74.   name: 'Analysis',
  75.   components: {
  76.     PageHeader,
  77.     TrendCharts,
  78.     VisitData,
  79.     SalesData,
  80.     UserOutlined,
  81.     ShoppingOutlined,
  82.     LikeOutlined,
  83.   },
  84. });
  85. </script>
复制代码
4. 快速开始

4.1 环境准备



  • Node.js >= 14.0.0
  • pnpm >= 6.0.0 (保举使用pnpm作为包管理工具)
4.2 安装依赖

  1. # 使用pnpm
  2. pnpm install
  3. # 使用yarn
  4. yarn install
  5. # 使用npm
  6. npm install
复制代码
4.3 开发环境运行

  1. # 使用pnpm
  2. pnpm dev
  3. # 使用yarn
  4. yarn dev
  5. # 使用npm
  6. npm run dev
复制代码
4.4 生产环境构建

  1. # 使用pnpm
  2. pnpm build
  3. # 使用yarn
  4. yarn build
  5. # 使用npm
  6. npm run build
复制代码
4.5 代码格式检查

  1. # 使用pnpm
  2. pnpm lint
  3. # 使用yarn
  4. yarn lint
  5. # 使用npm
  6. npm run lint
复制代码
5. 核心功能详解

5.1 权限管理

Vben Admin接纳基于角色的权限管理系统,支持以下功能:


  • 动态路由:根据用户角色动态生成路由
  • 权限指令:提供v-auth指令控制元素表现
  • 权限函数:提供hasPermission函数判断权限
  • 菜单权限:根据用户角色表现对应菜单
  1. // 权限指令使用示例
  2. <button v-auth="'user:add'">添加用户</button>
  3. // 权限函数使用示例
  4. import { usePermission } from '/@/hooks/web/usePermission';
  5. const { hasPermission } = usePermission();
  6. if (hasPermission('user:edit')) {
  7.   // 有编辑权限
  8. }
复制代码
5.1.1 权限指令实现

  1. // src/directives/auth/index.ts
  2. import type { App } from 'vue';
  3. import { usePermission } from '/@/hooks/web/usePermission';
  4. function isAuth(el: Element, binding: any) {
  5.   const { hasPermission } = usePermission();
  6.   const { value } = binding;
  7.   if (value && value instanceof Array && value.length > 0) {
  8.     const hasAuth = value.some((item) => {
  9.       return hasPermission(item);
  10.     });
  11.     if (!hasAuth) {
  12.       el.parentNode && el.parentNode.removeChild(el);
  13.     }
  14.   } else {
  15.     throw new Error('请设置操作权限标签值');
  16.   }
  17. }
  18. export function setupAuthDirective(app: App) {
  19.   app.directive('auth', {
  20.     mounted(el: Element, binding: any) {
  21.       isAuth(el, binding);
  22.     },
  23.     updated(el: Element, binding: any) {
  24.       isAuth(el, binding);
  25.     },
  26.   });
  27. }
复制代码
5.1.2 动态路由实现

  1. // src/router/helper/permissionHelper.ts
  2. import type { AppRouteRecordRaw } from '/@/router/types';
  3. import { usePermissionStoreWithOut } from '/@/store/modules/permission';
  4. import { useUserStoreWithOut } from '/@/store/modules/user';
  5. import { toRaw } from 'vue';
  6. import { unref } from 'vue';
  7. import { getDynamicParams } from '/@/router/helper';
  8. export function filterAsyncRoutes(routes: AppRouteRecordRaw[], roles: string[]) {
  9.   const res: AppRouteRecordRaw[] = [];
  10.   routes.forEach((route) => {
  11.     const tmp = { ...route };
  12.     if (hasPermission(roles, tmp)) {
  13.       if (tmp.children) {
  14.         tmp.children = filterAsyncRoutes(tmp.children, roles);
  15.       }
  16.       res.push(tmp);
  17.     }
  18.   });
  19.   return res;
  20. }
  21. function hasPermission(roles: string[], route: AppRouteRecordRaw) {
  22.   if (route.meta && route.meta.roles) {
  23.     return roles.some((role) => route.meta?.roles?.includes(role));
  24.   } else {
  25.     return true;
  26.   }
  27. }
  28. export async function generateDynamicRoutes() {
  29.   const permissionStore = usePermissionStoreWithOut();
  30.   const userStore = useUserStoreWithOut();
  31.   const { roles } = userStore.getUserInfo;
  32.   let accessedRoutes;
  33.   if (roles.includes('admin')) {
  34.     accessedRoutes = permissionStore.getRoutes;
  35.   } else {
  36.     accessedRoutes = filterAsyncRoutes(toRaw(permissionStore.getRoutes), roles);
  37.   }
  38.   return accessedRoutes;
  39. }
复制代码
5.2 主题设置

支持多主题切换和自定义主题:


  • 预设主题:提供多种预设主题
  • 自定义主题:支持自定义主题色和布局
  • 暗黑模式:支持暗黑模式切换
  • 主题持久化:主题设置可持久化到本地存储
  1. // 切换主题示例
  2. import { useTheme } from '/@/hooks/web/useTheme';
  3. const { setTheme } = useTheme();
  4. setTheme('dark'); // 切换到暗黑主题
复制代码
5.2.1 主题设置实现

  1. // src/hooks/web/useTheme.ts
  2. import { useDesign } from '/@/hooks/web/useDesign';
  3. import { useStorage } from '/@/hooks/web/useStorage';
  4. import { ThemeEnum } from '/@/enums/appEnum';
  5. const { prefixCls } = useDesign('app');
  6. const { setStorage, getStorage } = useStorage();
  7. export function useTheme() {
  8.   const getTheme = () => {
  9.     return getStorage(prefixCls + 'theme') || ThemeEnum.LIGHT;
  10.   };
  11.   const setTheme = (theme: ThemeEnum) => {
  12.     setStorage(prefixCls + 'theme', theme);
  13.     document.documentElement.setAttribute('data-theme', theme);
  14.   };
  15.   return {
  16.     getTheme,
  17.     setTheme,
  18.   };
  19. }
复制代码
5.3 国际化

内置国际化解决方案,支持多语言切换:


  • 语言包:内置中文和英文语言包
  • 动态加载:支持按需加载语言包
  • 语言持久化:语言选择可持久化到本地存储
  1. // 切换语言示例
  2. import { useLocale } from '/@/hooks/web/useLocale';
  3. const { setLocale } = useLocale();
  4. setLocale('en'); // 切换到英文
复制代码
5.3.1 国际化实现

  1. // src/locales/index.ts
  2. import type { App } from 'vue';
  3. import { createI18n } from 'vue-i18n';
  4. import { useLocaleStoreWithOut } from '/@/store/modules/locale';
  5. import { localeSetting } from '/@/settings/localeSetting';
  6. import { setHtmlPageLang } from '/@/utils/locale';
  7. import { getLocale } from './helper';
  8. const { fallback, availableLocales } = localeSetting;
  9. export let i18n: ReturnType<typeof createI18n>;
  10. async function createI18nOptions() {
  11.   const locale = getLocale();
  12.   const defaultLocal = await import(`./lang/${locale}.ts`);
  13.   const message = defaultLocal.default?.message ?? {};
  14.   return {
  15.     legacy: false,
  16.     locale,
  17.     fallbackLocale: fallback,
  18.     messages: {
  19.       [locale]: message,
  20.     },
  21.   };
  22. }
  23. export async function setupI18n(app: App) {
  24.   const options = await createI18nOptions();
  25.   i18n = createI18n(options) as ReturnType<typeof createI18n>;
  26.   app.use(i18n);
  27.   setHtmlPageLang(options.locale);
  28.   return i18n;
  29. }
复制代码
5.4 组件封装

封装了常用的业务组件,提高开发服从:


  • 表格组件:支持自定义列、排序、筛选、分页等
  • 表单组件:支持动态表单、表单验证、自定义校验等
  • 图表组件:集成ECharts,提供常用图表组件
  • 上传组件:支持单文件、多文件上传,支持图片预览等
  1. <!-- 表格组件使用示例 -->
  2. <template>
  3.   <BasicTable @register="registerTable">
  4.     <template #toolbar>
  5.       <a-button type="primary" @click="handleCreate">新增</a-button>
  6.     </template>
  7.     <template #action="{ record }">
  8.       <TableAction
  9.         :actions="[
  10.           {
  11.             icon: 'clarity:note-edit-line',
  12.             tooltip: '编辑',
  13.             onClick: handleEdit.bind(null, record),
  14.           },
  15.           {
  16.             icon: 'ant-design:delete-outlined',
  17.             color: 'error',
  18.             tooltip: '删除',
  19.             popConfirm: {
  20.               title: '是否确认删除',
  21.               confirm: handleDelete.bind(null, record),
  22.             },
  23.           },
  24.         ]"
  25.       />
  26.     </template>
  27.   </BasicTable>
  28. </template>
  29. <script lang="ts">
  30. import { defineComponent, ref, unref } from 'vue';
  31. import { BasicTable, useTable, TableAction } from '/@/components/Table';
  32. import { useMessage } from '/@/hooks/web/useMessage';
  33. import { columns, searchFormSchema } from './user.data';
  34. import { getUserList, deleteUser } from '/@/api/sys/user';
  35. export default defineComponent({
  36.   name: 'UserManagement',
  37.   components: { BasicTable, TableAction },
  38.   setup() {
  39.     const { createMessage } = useMessage();
  40.     const [registerTable, { reload }] = useTable({
  41.       title: '用户列表',
  42.       api: getUserList,
  43.       columns,
  44.       formConfig: {
  45.         labelWidth: 120,
  46.         schemas: searchFormSchema,
  47.       },
  48.       useSearchForm: true,
  49.       showTableSetting: true,
  50.       bordered: true,
  51.       actionColumn: {
  52.         width: 80,
  53.         title: '操作',
  54.         dataIndex: 'action',
  55.         slots: { customRender: 'action' },
  56.         fixed: false,
  57.       },
  58.     });
  59.     function handleCreate() {
  60.       // 处理创建用户
  61.     }
  62.     function handleEdit(record: Recordable) {
  63.       // 处理编辑用户
  64.     }
  65.     async function handleDelete(record: Recordable) {
  66.       await deleteUser(record.id);
  67.       createMessage.success('删除成功');
  68.       reload();
  69.     }
  70.     return {
  71.       registerTable,
  72.       handleCreate,
  73.       handleEdit,
  74.       handleDelete,
  75.     };
  76.   },
  77. });
  78. </script>
复制代码
5.4.1 表格组件实现

  1. // src/components/Table/src/hooks/useTable.ts
  2. import type { BasicTableProps, TableActionType } from '../types/table';
  3. import { ref, onMounted, unref } from 'vue';
  4. import { isProdMode } from '/@/utils/env';
  5. import { error } from '/@/utils/log';
  6. import { getDynamicProps } from '/@/utils';
  7. export function useTable(
  8.   getProps?: (props: Partial<BasicTableProps>) => Partial<BasicTableProps>,
  9. ) {
  10.   const tableRef = ref<Nullable<TableActionType>>(null);
  11.   const loadedRef = ref<Nullable<boolean>>(false);
  12.   const innerPropsRef = ref<Partial<BasicTableProps>>({});
  13.   function register(instance: TableActionType) {
  14.     isProdMode() &&
  15.       tableRef.value &&
  16.       tableRef.value.setProps(getDynamicProps(getProps, unref(innerPropsRef)));
  17.     tableRef.value = instance;
  18.   }
  19.   function getTableInstance() {
  20.     const table = unref(tableRef);
  21.     if (!table) {
  22.       error('表格实例尚未注册,请在onMounted后调用');
  23.     }
  24.     return table;
  25.   }
  26.   const methods: TableActionType = {
  27.     reload: async (opt?: any) => {
  28.       getTableInstance().reload(opt);
  29.     },
  30.     setProps: (props: Partial<BasicTableProps>) => {
  31.       getTableInstance().setProps(props);
  32.     },
  33.     // 其他方法...
  34.   };
  35.   onMounted(() => {
  36.     loadedRef.value = true;
  37.   });
  38.   return [register, methods];
  39. }
复制代码
5.5 状态管理

使用Pinia进行状态管理,提供更好的TypeScript支持:


  • 模块化:按功能模块划分状态
  • 类型安全:提供完备的类型定义
  • 持久化:支持状态持久化到本地存储
  • 开发工具:支持Vue DevTools调试
  1. // 状态管理使用示例
  2. import { useUserStoreWithOut } from '/@/store/modules/user';
  3. const userStore = useUserStoreWithOut();
  4. const { userInfo } = userStore.getUserInfo;
复制代码
5.5.1 状态管理实现

  1. // src/store/index.ts
  2. import type { App } from 'vue';
  3. import { createPinia } from 'pinia';
  4. import { registerStores } from '/@/store/helper';
  5. export function setupStore(app: App) {
  6.   const store = createPinia();
  7.   app.use(store);
  8.   registerStores(store);
  9.   return store;
  10. }
复制代码
6. 最佳实践

6.1 项目初始化

  1. # 克隆项目
  2. git clone https://github.com/vbenjs/vue-vben-admin.git
  3. # 进入项目目录
  4. cd vue-vben-admin
  5. # 安装依赖
  6. pnpm install
  7. # 启动项目
  8. pnpm dev
复制代码
6.2 开发规范



  • 定名规范:组件名使用PascalCase,文件名使用kebab-case
  • 目次结构:按功能模块划分目次,保持结构清楚
  • 代码风格:遵照ESLint和Prettier设置的代码风格
  • Git提交:遵照commitlint规范的提交信息格式
6.3 性能优化



  • 按需加载:路由和组件按需加载,减少首屏加载时间
  • 缓存优化:合理使用keep-alive缓存组件
  • 打包优化:设置splitChunks分割代码,减少包体积
  • 图片优化:使用webp格式和懒加载优化图片加载
6.3.1 按需加载实现

  1. // 路由按需加载
  2. const routes = [
  3.   {
  4.     path: '/dashboard',
  5.     name: 'Dashboard',
  6.     component: () => import('/@/views/dashboard/index.vue'),
  7.   },
  8. ];
复制代码
6.3.2 打包优化实现

  1. // vite.config.ts
  2. export default defineConfig({
  3.   build: {
  4.     target: 'es2015',
  5.     outDir: 'dist',
  6.     assetsDir: 'assets',
  7.     minify: 'terser',
  8.     terserOptions: {
  9.       compress: {
  10.         keep_infinity: true,
  11.         drop_console: true,
  12.         drop_debugger: true,
  13.       },
  14.     },
  15.     rollupOptions: {
  16.       output: {
  17.         manualChunks: {
  18.           vue: ['vue', 'vue-router', 'pinia'],
  19.           antd: ['ant-design-vue'],
  20.         },
  21.       },
  22.     },
  23.   },
  24. });
复制代码
6.4 项目部署



  • 环境设置:根据环境设置不同的变量
  • 构建优化:优化构建设置,减少构建时间
  • 部署脚本:提供部署脚本,简化部署流程
  • 监控告警:集成监控告警系统,及时发现问题
6.4.1 环境设置实现

  1. // .env.development
  2. VITE_PORT=3100
  3. VITE_USE_MOCK=true
  4. VITE_USE_PWA=false
  5. VITE_PUBLIC_PATH=/
  6. VITE_PROXY=[["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
  7. VITE_GLOB_APP_TITLE=Vben Admin Dev
  8. VITE_GLOB_APP_SHORT_NAME=Vben Admin Dev
  9. VITE_USE_CDN=false
  10. VITE_DROP_CONSOLE=false
  11. VITE_BUILD_COMPRESS="gzip"
  12. VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE=false
  13. VITE_LEGACY=false
  14. VITE_USE_IMAGEMIN=false
  15. VITE_GENERATE_UI=false
复制代码
7. 常见问题与解决方案

7.1 权限问题

问题:用户无法访问某些页面或功能。
解决方案


  • 检查用户角色是否正确
  • 检查路由权限设置
  • 检查菜单权限设置
  • 使用权限调试工具排盘问题
7.2 性能问题

问题:页面加载速度慢或操作响应慢。
解决方案


  • 使用Chrome DevTools分析性能瓶颈
  • 优化组件渲染,减少不必要的渲染
  • 使用虚拟滚动优化长列表
  • 优化API哀求,减少不必要的哀求
7.3 兼容性问题

问题:在某些浏览器中页面表现异常。
解决方案


  • 检查浏览器兼容性设置
  • 使用polyfill解决兼容性问题
  • 针对特定浏览器添加兼容性样式
  • 使用babel-preset-env设置合适的浏览器目的
7.4 构建问题

问题:构建失败或构建产物异常。
解决方案


  • 检查依赖版本兼容性
  • 检查构建设置是否正确
  • 清理构建缓存,重新构建
  • 使用更稳固的Node.js版本
8. 资源与社区

8.1 官方资源



  • Vben Admin官网
  • GitHub仓库
  • 在线演示
8.2 社区资源



  • Vben Admin社区
  • 问题反馈
  • 贡献指南
8.3 学习资源



  • Vue 3文档
  • Vite文档
  • TypeScript文档
  • Ant Design Vue文档
总结

Vben Admin是一个功能完满、易于使用的企业级管理系统框架,它基于最新的前端技术栈,提供了一套完备的解决方案,资助开发者快速搭建当代化的管理系统。通过使用Vben Admin,开发者可以专注于业务逻辑的开发,而不必耗费大量时间在基础架构的搭建上。
无论是小型项目照旧大型企业应用,Vben Admin都能提供精良的支持,是开发企业级管理系统的抱负选择。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连全瓷种植牙齿制作中心

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表