跳到主要内容Vben Admin:基于 Vue 3 的企业级中后台管理系统框架 | 极客日志TypeScriptSaaS大前端
Vben Admin:基于 Vue 3 的企业级中后台管理系统框架
Vben Admin 是基于 Vue 3、Vite 和 TypeScript 构建的企业级中后台管理框架。它提供权限管理、主题配置、国际化等核心功能,支持动态路由和组件封装。通过 Pinia 状态管理和完善的开发工具链,帮助开发者快速搭建现代化系统,适用于数据可视化、工作流等多种场景。
Eee_1230 浏览 Vben Admin 企业级管理系统框架
什么是 Vben Admin
Vben Admin 是一个基于 Vue 3、Vite、TypeScript 和 Ant Design Vue 的企业级中后台管理系统框架。它提供了一套完整的解决方案,帮助开发者快速搭建现代化的管理系统。
框架优势
- 开箱即用:提供丰富的组件和功能,减少重复开发
- 高度可定制:支持主题定制、布局定制、组件定制等
- 性能优异:基于 Vue 3 和 Vite,提供极速的开发体验和优秀的性能
- 类型安全:全面支持 TypeScript,提供类型安全和更好的开发体验
- 社区活跃:拥有活跃的社区和持续的更新维护
适用场景
- 企业级中后台管理系统
- 数据可视化平台
- 内容管理系统
- 工作流管理系统
- 权限管理系统
核心特点
技术栈先进
- Vue 3:采用最新的 Vue 3 框架,享受 Composition API 带来的开发体验提升
- TypeScript:全面支持 TypeScript,提供类型安全和更好的开发体验
- Vite:使用 Vite 作为构建工具,提供极速的开发体验
- Ant Design Vue:基于 Ant Design 设计规范,提供丰富的 UI 组件
- Pinia:使用 Pinia 进行状态管理,提供更好的 TypeScript 支持
- Vue Router:使用 Vue Router 进行路由管理,支持动态路由
功能完善
- 权限管理:基于角色的访问控制 (RBAC),支持动态路由和权限验证
- 主题配置:支持多主题切换,可自定义主题色和布局
- 国际化:内置国际化解决方案,支持多语言切换
- 组件封装:封装了常用的业务组件,如表格、表单、图表等
- Mock 数据:内置 Mock 数据服务,方便前后端分离开发
- 错误处理:内置全局错误处理机制,提供友好的错误提示
- 日志系统:内置日志系统,方便问题排查和用户行为分析
开发体验
- 代码规范:采用 ESLint、Prettier 等工具保证代码质量
- Git 提交规范:使用 commitlint 规范 Git 提交信息
- 自动导入:支持组件和 API 的自动导入,减少重复代码
- 热更新:支持模块热更新,提高开发效率
- TypeScript 支持:提供完整的类型定义,提高开发效率和代码质量
- 开发工具:提供丰富的开发工具,如调试工具、性能分析工具等
项目结构
├── build
├── mock
├── public
├── src
│ ├── api
│ ├── assets
│ ├── components
│ ├── design
│ ├── enums
│ ├── hooks
│ ├── layouts
│ ├── locales
│ ├── router
│ ├── settings
│ ├── store
│ ├── utils
│ ├── views
│ ├── App.vue
│ └── main.ts
├── types
├── .
├── .eslintrc.js
├── .prettierrc
├── package.json
├── tsconfig.json
└── vite.config.ts
env
核心目录说明
src/api
API 接口定义,按模块划分,每个模块对应一个文件。
import { defHttp } from '/@/utils/http/axios';
enum Api {
UserList = '/system/user/list',
UserInfo = '/system/user/info',
UserAdd = '/system/user/add',
UserEdit = '/system/user/edit',
UserDelete = '/system/user/delete',
}
export const getUserList = (params) => defHttp.get({ url: Api.UserList, params });
export const getUserInfo = (id) => defHttp.get({ url: Api.UserInfo, params: { id } });
export const addUser = (data) => defHttp.post({ url: Api.UserAdd, data });
export const editUser = (data) => defHttp.post({ url: Api.UserEdit, data });
export const deleteUser = (id) => defHttp.delete({ url: Api.UserDelete, params: { id } });
src/components
<!-- src/components/Button/src/BasicButton.vue -->
<template>
<a-button v-bind="getBindValue" :class="getButtonClass" @click="handleClick">
<template v-if="icon">
<component :is="icon" />
</template>
<slot></slot>
</a-button>
</template>
<script lang="ts">
import { defineComponent, computed, unref } from 'vue';
import { Button } from 'ant-design-vue';
import { useAttrs } from '/@/hooks/component/useAttrs';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'BasicButton',
components: { [Button.name]: Button },
props: {
type: { type: String, default: 'default' },
icon: { type: String, default: '' },
},
emits: ['click'],
setup(props, { emit }) {
const { prefixCls } = useDesign('basic-button');
const { getAttrs } = useAttrs();
const getBindValue = computed(() => ({ ...getAttrs(), ...props }));
const getButtonClass = computed(() => [prefixCls]);
function handleClick(e: MouseEvent) {
emit('click', e);
}
return { getBindValue, getButtonClass, handleClick };
},
});
</script>
src/hooks
import { useUserStoreWithOut } from '/@/store/modules/user';
export function usePermission() {
const userStore = useUserStoreWithOut();
const { role } = userStore.getUserInfo;
function hasPermission(value: string | string[]): boolean {
if (!value) return true;
if (!role) return false;
if (Array.isArray(value)) {
return value.some((item) => role.includes(item));
}
return role.includes(value);
}
return { hasPermission };
}
src/router
import type { AppRouteRecordRaw } from '/@/router/types';
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic';
import { PageEnum } from '/@/enums/pageEnum';
import { t } from '/@/hooks/web/useI18n';
export const RootRoute: AppRouteRecordRaw = {
path: '/',
name: 'Root',
redirect: PageEnum.BASE_HOME,
meta: { title: 'Root' },
};
export const LoginRoute: AppRouteRecordRaw = {
path: '/login',
name: 'Login',
component: () => import('/@/views/sys/login/Login.vue'),
meta: { title: t('routes.basic.login') },
};
export const basicRoutes = [
LoginRoute,
RootRoute,
...PAGE_NOT_FOUND_ROUTE,
...REDIRECT_ROUTE,
];
src/store
import { defineStore } from 'pinia';
import { store } from '/@/store';
import { UserInfo } from '/@/api/sys/model/userModel';
import { getAuthCache, setAuthCache } from '/@/utils/auth';
import { TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum';
interface UserState {
userInfo: Nullable<UserInfo>;
token?: string;
roleList: string[];
sessionTimeout?: boolean;
}
export const useUserStore = defineStore({
id: 'app-user',
state: (): UserState => ({
userInfo: null,
token: undefined,
roleList: [],
sessionTimeout: false,
}),
getters: {
getUserInfo(): Nullable<UserInfo> {
return this.userInfo || getAuthCache<UserInfo>(USER_INFO_KEY);
},
getToken(): string {
return this.token || getAuthCache<string>(TOKEN_KEY);
},
getRoleList(): string[] {
return this.roleList.length > 0 ? this.roleList : [];
},
getSessionTimeout(): boolean {
return !!this.sessionTimeout;
},
},
actions: {
setToken(token: string | undefined) {
this.token = token;
setAuthCache(TOKEN_KEY, token);
},
setUserInfo(info: UserInfo | null) {
this.userInfo = info;
setAuthCache(USER_INFO_KEY, info);
},
setRoleList(roleList: string[]) {
this.roleList = roleList;
},
setSessionTimeout(flag: boolean) {
this.sessionTimeout = flag;
},
resetState() {
this.userInfo = null;
this.token = '';
this.roleList = [];
this.sessionTimeout = false;
},
},
});
export function useUserStoreWithOut() {
return useUserStore(store);
}
src/utils
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios';
import axios from 'axios';
import { clone } from 'lodash-es';
import { ContentTypeEnum } from '/@/enums/httpEnum';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { joinTimestamp, formatRequestDate } from './helper';
import { createAxiosTransform } from './axiosTransform';
import { checkStatus } from './checkStatus';
import { useGlobSetting } from '/@/hooks/setting';
import { RequestOptions, Result } from '/@/types/axios';
import { AxiosRetry } from './axiosRetry';
import { getToken } from '/@/utils/auth';
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix;
export class VAxios {
private axiosInstance: AxiosInstance;
private readonly options: CreateAxiosOptions;
constructor(options: CreateAxiosOptions) {
this.options = options;
this.axiosInstance = axios.create(options);
this.setupInterceptors();
}
private createAxios(config: AxiosRequestConfig) {
this.axiosInstance = axios.create(config);
}
private getTransform() {
const { transform } = this.options;
if (!transform) return;
return transform;
}
getAxios(): AxiosInstance {
return this.axiosInstance;
}
configAxios(config: AxiosRequestConfig) {
if (!this.axiosInstance) return;
this.createAxios(config);
}
setupInterceptors() {
const transform = this.getTransform();
if (!transform) return;
const {
requestInterceptors,
requestInterceptorsCatch,
responseInterceptors,
responseInterceptorsCatch,
} = transform;
this.axiosInstance.interceptors.request.use(
(config: AxiosRequestConfig) => {
if (requestInterceptors) config = requestInterceptors(config, this.options);
return config;
},
undefined,
);
requestInterceptorsCatch &&
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch);
this.axiosInstance.interceptors.response.use(
(res: AxiosResponse<any>) => {
if (responseInterceptors) res = responseInterceptors(res);
return res;
},
undefined,
);
responseInterceptorsCatch &&
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
}
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
let conf: AxiosRequestConfig = clone(config);
const transform = this.getTransform();
const { requestOptions } = this.options;
const opt: RequestOptions = Object.assign({}, requestOptions, options);
const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {};
if (beforeRequestHook) conf = beforeRequestHook(conf, opt);
conf.requestOptions = opt;
conf = this.supportFormData(conf);
return new Promise((resolve, reject) => {
this.axiosInstance
.request<any, AxiosResponse<Result>>(conf)
.then((res: AxiosResponse<Result>) => {
if (transformResponseHook) {
try {
res = transformResponseHook(res, opt);
} catch (err) {
reject(err || new Error('request error!'));
}
return res;
}
resolve(res as unknown as Promise<T>);
})
.catch((e: Error | AxiosError) => {
if (requestCatchHook && e) {
reject(requestCatchHook(e, opt));
return;
}
if (Axios.isAxiosError(e)) {
const error = this.handleAxiosError(e);
reject(error);
} else {
let error = new Error(e.message) as Error & { code?: string };
error.code = 'NO_AXIOS_ERROR';
reject(error);
}
});
});
}
supportFormData(config: AxiosRequestConfig) {
const { requestOptions } = config;
const contentType = requestOptions?.headers?.['Content-Type'];
const isFormData =
contentType !== ContentTypeEnum.FORM_URLENCODED && contentType !== ContentTypeEnum.FORM_DATA;
if (isFormData && config.method?.toUpperCase() === 'GET') {
config.params = Object.assign({}, config.params || {});
Object.keys(config.params).forEach((key) => {
if (config.params[key] !== null && typeof config.params[key] === 'object' && !Array.isArray(config.params[key])) {
config.params[key] = JSON.stringify(config.params[key]);
}
});
} else {
config.data = config.data || config.params;
}
return config;
}
private handleAxiosError(error: AxiosError) {
const { response } = error;
let errorMessage = '请求错误';
if (response) {
const { status, data } = response;
errorMessage = checkStatus(status, data);
}
const errorObj = new Error(errorMessage) as Error & { code?: string };
errorObj.code = error.response?.status?.toString() || 'NO_STATUS';
return errorObj;
}
}
export const createAxios = (opt?: Partial<CreateAxiosOptions>) => {
return new VAxios(
deepMerge(
{
timeout: 10 * 1000,
headers: { 'Content-Type': ContentTypeEnum.JSON },
transform: clone(createAxiosTransform()),
requestOptions: {
joinPrefix: true,
isReturnNativeResponse: false,
isTransformResponse: true,
joinParamsToUrl: false,
formatDate: true,
errorMessageMode: 'message',
apiUrl: globSetting.apiUrl,
urlPrefix: urlPrefix,
joinTime: true,
ignoreCancelToken: true,
withToken: true,
retryRequest: {
isOpenRetry: true,
count: 5,
waitTime: 100,
},
},
},
opt || {},
),
);
};
export const defHttp = createAxios();
src/views
<!-- src/views/dashboard/analysis/index.vue -->
<template>
<div>
<PageHeader title="分析页" />
<a-card :bordered="false">
<a-row :gutter="16">
<a-col :span="6">
<a-statistic title="总销售额" :value="112893" :precision="2" :value-style="{ color: '#3f8600' }">
<template #prefix>
<TrendCharts option="up" />
</template>
</a-statistic>
</a-col>
<a-col :span="6">
<a-statistic title="访问人数" :value="8846" :value-style="{ color: '#cf1322' }">
<template #prefix>
<UserOutlined />
</template>
</a-statistic>
</a-col>
<a-col :span="6">
<a-statistic title="支付笔数" :value="6560" :value-style="{ color: '#3f8600' }">
<template #prefix>
<ShoppingOutlined />
</template>
</a-statistic>
</a-col>
<a-col :span="6">
<a-statistic title="运营活动效果" :value="78" :suffix="%" :value-style="{ color: '#3f8600' }">
<template #prefix>
<LikeOutlined />
</template>
</a-statistic>
</a-col>
</a-row>
<a-divider />
<a-row :gutter="16">
<a-col :span="12"><VisitData /></a-col>
<a-col :span="12"><SalesData /></a-col>
</a-row>
</a-card>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { UserOutlined, ShoppingOutlined, LikeOutlined } from '@ant-design/icons-vue';
import { PageHeader } from '/@/components/Page';
import { TrendCharts } from '/@/components/Charts';
import VisitData from './components/VisitData.vue';
import SalesData from './components/SalesData.vue';
export default defineComponent({
name: 'Analysis',
components: {
PageHeader,
TrendCharts,
VisitData,
SalesData,
UserOutlined,
ShoppingOutlined,
LikeOutlined,
},
});
</script>
快速开始
环境准备
- Node.js >= 14.0.0
- pnpm >= 6.0.0 (推荐使用 pnpm 作为包管理工具)
安装依赖
pnpm install
yarn install
npm install
开发环境运行
pnpm dev
yarn dev
npm run dev
生产环境构建
pnpm build
yarn build
npm run build
代码格式检查
pnpm lint
yarn lint
npm run lint
核心功能详解
权限管理
Vben Admin 采用基于角色的权限管理系统,支持以下功能:
- 动态路由:根据用户角色动态生成路由
- 权限指令:提供 v-auth 指令控制元素显示
- 权限函数:提供 hasPermission 函数判断权限
- 菜单权限:根据用户角色显示对应菜单
<button v-auth="'user:add'">添加用户</button>
import { usePermission } from '/@/hooks/web/usePermission';
const { hasPermission } = usePermission();
if (hasPermission('user:edit')) {
}
5.1.1 权限指令实现
import type { App } from 'vue';
import { usePermission } from '/@/hooks/web/usePermission';
function isAuth(el: Element, binding: any) {
const { hasPermission } = usePermission();
const { value } = binding;
if (value && value instanceof Array && value.length > 0) {
const hasAuth = value.some((item) => {
return hasPermission(item);
});
if (!hasAuth) el.parentNode && el.parentNode.removeChild(el);
} else {
throw new Error('请设置操作权限标签值');
}
}
export function setupAuthDirective(app: App) {
app.directive('auth', {
mounted(el: Element, binding: any) {
isAuth(el, binding);
},
updated(el: Element, binding: any) {
isAuth(el, binding);
},
});
}
5.1.2 动态路由实现
import type { AppRouteRecordRaw } from '/@/router/types';
import { usePermissionStoreWithOut } from '/@/store/modules/permission';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { toRaw } from 'vue';
import { unref } from 'vue';
import { getDynamicParams } from '/@/router/helper';
export function filterAsyncRoutes(routes: AppRouteRecordRaw[], roles: string[]) {
const res: AppRouteRecordRaw[] = [];
routes.forEach((route) => {
const tmp = { ...route };
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles);
}
res.push(tmp);
}
});
return res;
}
function hasPermission(roles: string[], route: AppRouteRecordRaw) {
if (route.meta && route.meta.roles) {
return roles.some((role) => route.meta?.roles?.includes(role));
} else {
return true;
}
}
export async function generateDynamicRoutes() {
const permissionStore = usePermissionStoreWithOut();
const userStore = useUserStoreWithOut();
const { roles } = userStore.getUserInfo;
let accessedRoutes;
if (roles.includes('admin')) {
accessedRoutes = permissionStore.getRoutes;
} else {
accessedRoutes = filterAsyncRoutes(toRaw(permissionStore.getRoutes), roles);
}
return accessedRoutes;
}
主题配置
- 预设主题:提供多种预设主题
- 自定义主题:支持自定义主题色和布局
- 暗黑模式:支持暗黑模式切换
- 主题持久化:主题配置可持久化到本地存储
import { useTheme } from '/@/hooks/web/useTheme';
const { setTheme } = useTheme();
setTheme('dark');
5.2.1 主题配置实现
import { useDesign } from '/@/hooks/web/useDesign';
import { useStorage } from '/@/hooks/web/useStorage';
import { ThemeEnum } from '/@/enums/appEnum';
const { prefixCls } = useDesign('app');
const { setStorage, getStorage } = useStorage();
export function useTheme() {
const getTheme = () => {
return getStorage(prefixCls + 'theme') || ThemeEnum.LIGHT;
};
const setTheme = (theme: ThemeEnum) => {
setStorage(prefixCls + 'theme', theme);
document.documentElement.setAttribute('data-theme', theme);
};
return { getTheme, setTheme };
}
国际化
- 语言包:内置中文和英文语言包
- 动态加载:支持按需加载语言包
- 语言持久化:语言选择可持久化到本地存储
import { useLocale } from '/@/hooks/web/useLocale';
const { setLocale } = useLocale();
setLocale('en');
5.3.1 国际化实现
import type { App } from 'vue';
import { createI18n } from 'vue-i18n';
import { useLocaleStoreWithOut } from '/@/store/modules/locale';
import { localeSetting } from '/@/settings/localeSetting';
import { setHtmlPageLang } from '/@/utils/locale';
import { getLocale } from './helper';
const { fallback, availableLocales } = localeSetting;
export let i18n: ReturnType<typeof createI18n>;
async function createI18nOptions() {
const locale = getLocale();
const defaultLocal = await import(`./lang/${locale}.ts`);
const message = defaultLocal.default?.message ?? {};
return {
legacy: false,
locale,
fallbackLocale: fallback,
messages: {
[locale]: message,
},
};
}
export async function setupI18n(app: App) {
const options = await createI18nOptions();
i18n = createI18n(options) as ReturnType<typeof createI18n>;
app.use(i18n);
setHtmlPageLang(options.locale);
return i18n;
}
组件封装
- 表格组件:支持自定义列、排序、筛选、分页等
- 表单组件:支持动态表单、表单验证、自定义校验等
- 图表组件:集成 ECharts,提供常用图表组件
- 上传组件:支持单文件、多文件上传,支持图片预览等
<!-- 表格组件使用示例 -->
<template>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button type="primary" @click="handleCreate">新增</a-button>
</template>
<template #action="{ record }">
<TableAction
:actions="[
{
icon: 'clarity:note-edit-line',
tooltip: '编辑',
onClick: handleEdit.bind(null, record),
},
{
icon: 'ant-design:delete-outlined',
color: 'error',
tooltip: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</BasicTable>
</template>
<script lang="ts">
import { defineComponent, ref, unref } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useMessage } from '/@/hooks/web/useMessage';
import { columns, searchFormSchema } from './user.data';
import { getUserList, deleteUser } from '/@/api/sys/user';
export default defineComponent({
name: 'UserManagement',
components: { BasicTable, TableAction },
setup() {
const { createMessage } = useMessage();
const [registerTable, { reload }] = useTable({
title: '用户列表',
api: getUserList,
columns,
formConfig: {
labelWidth: 120,
schemas: searchFormSchema,
},
useSearchForm: true,
showTableSetting: true,
bordered: true,
actionColumn: {
width: 80,
title: '操作',
dataIndex: 'action',
slots: { customRender: 'action' },
fixed: false,
},
});
function handleCreate() {
// 处理创建用户
}
function handleEdit(record: Recordable) {
// 处理编辑用户
}
async function handleDelete(record: Recordable) {
await deleteUser(record.id);
createMessage.success('删除成功');
reload();
}
return { registerTable, handleCreate, handleEdit, handleDelete };
},
});
</script>
5.4.1 表格组件实现
import type { BasicTableProps, TableActionType } from '../types/table';
import { ref, onMounted, unref } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
import { getDynamicProps } from '/@/utils';
export function useTable(
getProps?: (props: Partial<BasicTableProps>) => Partial<BasicTableProps>,
) {
const tableRef = ref<Nullable<TableActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false);
const innerPropsRef = ref<Partial<BasicTableProps>>({});
function register(instance: TableActionType) {
isProdMode() && tableRef.value && tableRef.value.setProps(getDynamicProps(getProps, unref(innerPropsRef)));
tableRef.value = instance;
}
function getTableInstance() {
const table = unref(tableRef);
if (!table) error('表格实例尚未注册,请在 onMounted 后调用');
return table;
}
const methods: TableActionType = {
reload: async (opt?: any) => {
getTableInstance().reload(opt);
},
setProps: (props: Partial<BasicTableProps>) => {
getTableInstance().setProps(props);
},
};
onMounted(() => {
loadedRef.value = true;
});
return [register, methods];
}
状态管理
使用 Pinia 进行状态管理,提供更好的 TypeScript 支持:
- 模块化:按功能模块划分状态
- 类型安全:提供完整的类型定义
- 持久化:支持状态持久化到本地存储
- 开发工具:支持 Vue DevTools 调试
import { useUserStoreWithOut } from '/@/store/modules/user';
const userStore = useUserStoreWithOut();
const { userInfo } = userStore.getUserInfo;
5.5.1 状态管理实现
import type { App } from 'vue';
import { createPinia } from 'pinia';
import { registerStores } from '/@/store/helper';
export function setupStore(app: App) {
const store = createPinia();
app.use(store);
registerStores(store);
return store;
}
最佳实践
项目初始化
git clone https://github.com/vbenjs/vue-vben-admin.git
cd vue-vben-admin
pnpm install
pnpm dev
开发规范
- 命名规范:组件名使用 PascalCase,文件名使用 kebab-case
- 目录结构:按功能模块划分目录,保持结构清晰
- 代码风格:遵循 ESLint 和 Prettier 配置的代码风格
- Git 提交:遵循 commitlint 规范的提交信息格式
性能优化
- 按需加载:路由和组件按需加载,减少首屏加载时间
- 缓存优化:合理使用 keep-alive 缓存组件
- 打包优化:配置 splitChunks 分割代码,减少包体积
- 图片优化:使用 webp 格式和懒加载优化图片加载
6.3.1 按需加载实现
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('/@/views/dashboard/index.vue'),
},
];
6.3.2 打包优化实现
export default defineConfig({
build: {
target: 'es2015',
outDir: 'dist',
assetsDir: 'assets',
minify: 'terser',
terserOptions: {
compress: {
keep_infinity: true,
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
antd: ['ant-design-vue'],
},
},
},
},
});
项目部署
- 环境配置:根据环境配置不同的变量
- 构建优化:优化构建配置,减少构建时间
- 部署脚本:提供部署脚本,简化部署流程
- 监控告警:集成监控告警系统,及时发现问题
6.4.1 环境配置实现
VITE_PORT=3100
VITE_USE_MOCK=true
VITE_USE_PWA=false
VITE_PUBLIC_PATH=/
VITE_PROXY=[["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
VITE_GLOB_APP_TITLE=Vben Admin Dev
VITE_GLOB_APP_SHORT_NAME=Vben Admin Dev
VITE_USE_CDN=false
VITE_DROP_CONSOLE=false
VITE_BUILD_COMPRESS="gzip"
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE=false
VITE_LEGACY=false
VITE_USE_IMAGEMIN=false
VITE_GENERATE_UI=false
常见问题与解决方案
7.1 权限问题
- 检查用户角色是否正确
- 检查路由权限配置
- 检查菜单权限配置
- 使用权限调试工具排查问题
7.2 性能问题
- 使用 Chrome DevTools 分析性能瓶颈
- 优化组件渲染,减少不必要的渲染
- 使用虚拟滚动优化长列表
- 优化 API 请求,减少不必要的请求
7.3 兼容性问题
- 检查浏览器兼容性配置
- 使用 polyfill 解决兼容性问题
- 针对特定浏览器添加兼容性样式
- 使用 babel-preset-env 配置合适的浏览器目标
7.4 构建问题
- 检查依赖版本兼容性
- 检查构建配置是否正确
- 清理构建缓存,重新构建
- 使用更稳定的 Node.js 版本
资源与社区
8.1 官方资源
8.2 社区资源
8.3 学习资源
总结
Vben Admin 是一个功能完善、易于使用的企业级管理系统框架,它基于最新的前端技术栈,提供了一套完整的解决方案,帮助开发者快速搭建现代化的管理系统。通过使用 Vben Admin,开发者可以专注于业务逻辑的开发,而不必花费大量时间在基础架构的搭建上。
无论是小型项目还是大型企业应用,Vben Admin 都能提供良好的支持,是开发企业级管理系统的理想选择。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online