【Naive UI Admin 学习】基于Vue3 + TypeScript + Naive UI 的中后台前端框架
✨ 基于Vue3 + TypeScript + Naive UI 的中后台前端框架
📖 目录
1. 项目概述
1.1 项目介绍
Naive UI Admin 是一款基于 Vue3.0、Vite、Naive UI 和 TypeScript 的开源中后台管理系统。它融合了最新的前端技术栈,提炼了典型的业务模型和页面,包括二次封装组件、动态菜单、权限校验等功能,助力快速搭建企业级中后台项目。
1.2 核心特性
- 🚀 最新技术栈:基于 Vue3、Vite、TypeScript 等最新技术栈开发
- ⚡️ 极速开发:热重载快速,开发体验流畅
- 📦 组件封装:对日常使用频率较高的组件进行二次封装
- 🔩 主题配置:丰富的主题配置及黑暗主题适配
- 🔑 权限管理:完善的前后端权限管理方案
- 🛠️ 最佳实践:常用页面布局,组件使用示例
1.3 版本信息
{
"name": "naive-ui-admin",
"version": "2.1.0",
"author": "Ahjung",
"license": "MIT"
}
2. 技术栈分析
2.1 核心技术栈
技术 | 版本 | 用途 |
---|---|---|
Vue | ^3.5.18 | 前端框架 |
TypeScript | ^4.9.5 | 类型系统 |
Vite | ^5.4.19 | 构建工具 |
Naive UI | ^2.42.0 | UI 组件库 |
Vue Router | ^4.5.1 | 路由管理 |
Pinia | ^2.3.1 | 状态管理 |
2.2 工具链配置
构建配置 (vite.config.ts)
export default ({ command, mode }: ConfigEnv): UserConfig => {
return {
// 路径别名配置
resolve: {
alias: [
{ find: /\/#\//, replacement: pathResolve('types') + '/' },
{ find: '@', replacement: pathResolve('src') + '/' }
]
},
// 构建分包策略
build: {
rollupOptions: {
output: {
manualChunks: {
'naive-ui': ['naive-ui'],
'lodash-es': ['lodash-es'],
'vue-router': ['vue-router'],
// ... 更多分包配置
}
}
}
}
}
}
TypeScript 配置
项目采用严格的 TypeScript 配置,确保代码质量和类型安全。
3. 项目结构详解
3.1 整体目录结构
naive-ui-admin/
├── src/ # 源代码目录
│ ├── api/ # API 接口定义
│ ├── assets/ # 静态资源
│ ├── components/ # 全局组件
│ ├── config/ # 配置文件
│ ├── directives/ # 自定义指令
│ ├── enums/ # 枚举定义
│ ├── hooks/ # 组合式函数
│ ├── layout/ # 布局组件
│ ├── router/ # 路由配置
│ ├── store/ # 状态管理
│ ├── styles/ # 样式文件
│ ├── utils/ # 工具函数
│ └── views/ # 页面组件
├── mock/ # Mock 数据
├── types/ # 类型定义
└── build/ # 构建脚本
3.2 核心目录详解
3.2.1 组件目录 (src/components)
components/
├── Application/ # 应用程序组件
├── CountTo/ # 数字动画组件
├── Form/ # 表单组件
│ ├── src/
│ │ ├── BasicForm.vue # 基础表单
│ │ ├── hooks/ # 表单钩子
│ │ └── types/ # 表单类型
├── Table/ # 表格组件
│ ├── src/
│ │ ├── Table.vue # 基础表格
│ │ ├── hooks/ # 表格钩子
│ │ └── components/ # 表格子组件
├── Modal/ # 模态框组件
├── Upload/ # 上传组件
└── Lockscreen/ # 锁屏组件
3.2.2 布局目录 (src/layout)
layout/
├── components/
│ ├── Header/ # 头部组件
│ ├── Menu/ # 菜单组件
│ ├── Footer/ # 底部组件
│ ├── Logo/ # Logo 组件
│ ├── Main/ # 主内容区
│ └── TagsView/ # 标签页组件
├── index.vue # 主布局
└── parentLayout.vue # 父级布局
4. 核心功能实现
4.1 应用初始化流程
4.1.1 主入口 (src/main.ts)
async function bootstrap() {
const app = createApp(App);
// 1. 挂载状态管理
setupStore(app);
// 2. 注册 Naive UI 组件
setupNaive(app);
// 3. 挂载 Naive UI 脱离上下文的 API
setupNaiveDiscreteApi();
// 4. 注册全局自定义指令
setupDirectives(app);
// 5. 挂载路由
setupRouter(app);
// 6. 等待路由准备就绪
await router.isReady();
// 7. 挂载应用
app.mount('#app', true);
}
4.2 路由系统
4.2.1 动态路由加载
// src/router/index.ts
const modules = import.meta.glob<IModuleType>('./modules/**/*.ts', { eager: true });
const routeModuleList: RouteRecordRaw[] = Object.keys(modules).reduce((list, key) => {
const mod = modules[key].default ?? {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
return [...list, ...modList];
}, []);
4.2.2 路由守卫实现
// src/router/guards.ts
export function createRouterGuards(router: Router) {
router.beforeEach(async (to, from, next) => {
// 1. 开启加载状态
const Loading = window['$loading'] || null;
Loading && Loading.start();
// 2. 白名单检查
if (whitePathList.includes(to.path as PageEnum)) {
next();
return;
}
// 3. token 验证
const token = storage.get(ACCESS_TOKEN);
if (!token) {
// 重定向到登录页
next({ path: LOGIN_PATH, replace: true });
return;
}
// 4. 动态路由生成
if (!asyncRouteStore.getIsDynamicRouteAdded) {
const userInfo = await userStore.getInfo();
const routes = await asyncRouteStore.generateRoutes(userInfo);
// 动态添加路由
routes.forEach((item) => {
router.addRoute(item as unknown as RouteRecordRaw);
});
}
next();
});
}
4.3 状态管理 (Pinia)
4.3.1 用户状态管理
// src/store/modules/user.ts
export const useUserStore = defineStore({
id: 'app-user',
state: (): IUserState => ({
token: storage.get(ACCESS_TOKEN, ''),
username: '',
avatar: '',
permissions: [],
info: storage.get(CURRENT_USER, {}),
}),
actions: {
// 登录操作
async login(params: any) {
const response = await login(params);
const { result, code } = response;
if (code === ResultEnum.SUCCESS) {
// 存储用户信息
storage.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60);
storage.set(CURRENT_USER, result, 7 * 24 * 60 * 60);
this.setToken(result.token);
this.setUserInfo(result);
}
return response;
},
// 获取用户信息
async getInfo() {
const data = await getUserInfoApi();
const { result } = data;
if (result.permissions && result.permissions.length) {
this.setPermissions(result.permissions);
this.setUserInfo(result);
}
return result;
}
}
});
4.4 HTTP 请求封装
4.4.1 Alova 请求库配置
// src/utils/http/alova/index.ts
export const Alova = createAlova({
baseURL: apiUrl,
statesHook: VueHook,
requestAdapter: mockAdapter,
// 请求拦截器
beforeRequest(method) {
const userStore = useUser();
const token = userStore.getToken;
// 添加 token 到请求头
if (!method.meta?.ignoreToken && token) {
method.config.headers['token'] = token;
}
// 处理 API 请求前缀
if (!isUrl(method.url as string) && urlPrefix) {
method.url = `${urlPrefix}${method.url}`;
}
},
// 响应拦截器
responded: {
onSuccess: async (response, method) => {
const res = await response.json();
const { message, code, result } = res;
if (ResultEnum.SUCCESS === code) {
return result;
}
// 处理登录失效
if (code === 912) {
Modal?.warning({
title: '提示',
content: '登录身份已失效,请重新登录!',
onOk: async () => {
storage.clear();
window.location.href = PageEnum.BASE_LOGIN;
},
});
}
}
}
});
5. 架构设计原理
5.1 整体架构
5.2 组件设计模式
5.2.1 高阶组件封装
以 Table 组件为例:
<!-- src/components/Table/src/Table.vue -->
<template>
<div class="table-toolbar">
<!-- 工具栏 -->
<div class="flex items-center table-toolbar-left">
<template v-if="props.title">
<div class="table-toolbar-left-title">{{ props.title }}</div>
</template>
<slot name="tableTitle"></slot>
</div>
<!-- 操作按钮 -->
<div class="flex items-center table-toolbar-right">
<slot name="toolbar"></slot>
<!-- 斑马纹切换 -->
<n-switch v-model:value="isStriped" @update:value="setStriped" />
<!-- 刷新按钮 -->
<div class="table-toolbar-right-icon" @click="reload">
<n-icon size="18"><ReloadOutlined /></n-icon>
</div>
</div>
</div>
<!-- 表格主体 -->
<n-data-table
v-bind="getBindValues"
:data="dataSource"
:columns="getColumns"
:loading="getLoading"
/>
</template>
5.2.2 组合式 API 设计
// src/components/Table/src/hooks/useDataSource.ts
export function useDataSource(props: any, context: any) {
const dataSource = ref([]);
const loading = ref(false);
const getDataSource = computed(() => {
return unref(dataSource);
});
const fetch = async (params?: any) => {
loading.value = true;
try {
const { api } = props;
if (api && isFunction(api)) {
const result = await api(params);
dataSource.value = result || [];
}
} finally {
loading.value = false;
}
};
return {
getDataSource,
loading,
fetch,
reload: () => fetch()
};
}
5.3 布局系统设计
5.3.1 响应式布局
<!-- src/layout/index.vue -->
<template>
<n-layout class="layout" :position="fixedMenu" has-sider>
<!-- 侧边栏 -->
<n-layout-sider
v-if="!isMobile && isMixMenuNoneSub"
:collapsed="collapsed"
:width="leftMenuWidth"
:inverted="inverted"
>
<Logo :collapsed="collapsed" />
<AsideMenu v-model:collapsed="collapsed" />
</n-layout-sider>
<!-- 移动端抽屉 -->
<n-drawer v-model:show="showSideDrawer" :width="menuWidth">
<n-layout-sider>
<Logo :collapsed="collapsed" />
<AsideMenu />
</n-layout-sider>
</n-drawer>
<!-- 主内容区 -->
<n-layout>
<n-layout-header :position="fixedHeader">
<PageHeader v-model:collapsed="collapsed" />
</n-layout-header>
<n-layout-content class="layout-content">
<TabsView v-if="isMultiTabs" />
<MainView />
</n-layout-content>
</n-layout>
</n-layout>
</template>
5.4 主题系统
5.4.1 动态主题切换
// src/App.vue
const getThemeOverrides = computed(() => {
const appTheme = designStore.appTheme;
const lightenStr = lighten(designStore.appTheme, 6);
return {
common: {
primaryColor: appTheme,
primaryColorHover: lightenStr,
primaryColorPressed: lightenStr,
},
LoadingBar: {
colorLoading: appTheme,
},
};
});
const getDarkTheme = computed(() =>
designStore.darkTheme ? darkTheme : undefined
);
6. 开发指南
6.1 环境准备
6.1.1 系统要求
- Node.js >= 16
- pnpm (推荐) 或 npm/yarn
- Git
6.1.2 快速开始
# 1. 克隆项目
git clone https://github.com/jekip/naive-ui-admin.git
# 2. 进入项目目录
cd naive-ui-admin
# 3. 安装依赖
pnpm install
# 4. 启动开发服务器
pnpm run dev
# 5. 构建生产版本
pnpm run build
6.2 开发规范
6.2.1 代码规范
项目集成了完整的代码规范工具:
{
"scripts": {
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\""
}
}
6.2.2 Git 提交规范
# 功能开发
git commit -m "feat: 添加用户管理功能"
# 问题修复
git commit -m "fix: 修复表格分页问题"
# 样式调整
git commit -m "style: 调整按钮样式"
# 性能优化
git commit -m "perf: 优化表格渲染性能"
6.3 组件开发
6.3.1 创建新组件
// src/components/MyComponent/index.ts
export { default as MyComponent } from './src/MyComponent.vue';
// src/components/MyComponent/src/MyComponent.vue
<template>
<div class="my-component">
<!-- 组件内容 -->
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue';
interface Props {
title?: string;
disabled?: boolean;
}
interface Emits {
(e: 'change', value: any): void;
}
const props = withDefaults(defineProps<Props>(), {
title: '',
disabled: false,
});
const emit = defineEmits<Emits>();
</script>
6.3.2 使用组合式 API
// src/hooks/useMyFeature.ts
import { ref, computed } from 'vue';
export function useMyFeature() {
const state = ref(false);
const toggleState = () => {
state.value = !state.value;
};
const stateText = computed(() =>
state.value ? '已开启' : '已关闭'
);
return {
state,
stateText,
toggleState,
};
}
6.4 页面开发
6.4.1 创建新页面
<!-- src/views/example/index.vue -->
<template>
<div class="example-page">
<n-card title="示例页面">
<BasicTable @register="registerTable" />
</n-card>
</div>
</template>
<script lang="ts" setup>
import { BasicTable, useTable } from '@/components/Table';
import { getExampleList } from '@/api/example';
const [registerTable] = useTable({
title: '示例列表',
api: getExampleList,
columns: [
{
title: 'ID',
key: 'id',
width: 100,
},
{
title: '名称',
key: 'name',
},
],
});
</script>
6.4.2 添加路由
// src/router/modules/example.ts
import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw = {
path: '/example',
name: 'Example',
component: () => import('@/views/example/index.vue'),
meta: {
title: '示例页面',
icon: 'example-icon',
},
};
export default routes;
7. 部署与优化
7.1 构建配置
7.1.1 环境变量配置
# .env.development
VITE_PUBLIC_PATH = /
VITE_PORT = 3000
VITE_USE_MOCK = true
# .env.production
VITE_PUBLIC_PATH = /naive-ui-admin/
VITE_USE_MOCK = false
7.1.2 构建优化
// vite.config.ts
export default {
build: {
// 分包策略
rollupOptions: {
output: {
manualChunks: {
'naive-ui': ['naive-ui'],
'vue-vendor': ['vue', 'vue-router'],
'utils': ['lodash-es', 'dayjs'],
},
},
},
// 压缩配置
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
},
};
7.2 性能优化
7.2.1 路由懒加载
// 动态导入组件
const routes = [
{
path: '/dashboard',
component: () => import('@/views/dashboard/index.vue'),
},
];
7.2.2 组件懒加载
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(
() => import('./components/HeavyComponent.vue')
);
</script>
7.3 部署方案
7.3.1 Nginx 配置
server {
listen 80;
server_name your-domain.com;
root /var/www/naive-ui-admin/dist;
index index.html;
# 处理 SPA 路由
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
7.3.2 Docker 部署
# Dockerfile
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
8. 最佳实践
8.1 代码组织
8.1.1 文件命名规范
// 文件命名采用 kebab-case
components/
├── user-list/
│ ├── index.ts
│ ├── user-list.vue
│ └── user-list.types.ts
// 组件命名采用 PascalCase
<template>
<UserList />
<ProductDetail />
</template>
8.1.2 TypeScript 类型定义
// types/api.ts
export interface UserInfo {
id: number;
username: string;
email: string;
avatar?: string;
roles: string[];
}
export interface ApiResponse<T = any> {
code: number;
message: string;
result: T;
}
// 使用泛型约束 API 响应
export const getUserList = (): Promise<ApiResponse<UserInfo[]>> => {
return Alova.Get('/api/users');
};
8.2 性能优化技巧
8.2.1 合理使用响应式
// ❌ 不推荐:深度响应式大对象
const state = reactive({
userList: [], // 大数组
config: {}, // 复杂配置对象
});
// ✅ 推荐:按需响应式
const userList = shallowRef([]); // 浅响应式
const selectedUser = ref(null); // 单个响应式值
const config = readonly(configData); // 只读数据
8.2.2 组件优化
<template>
<!-- 使用 v-memo 优化列表渲染 -->
<div
v-for="item in list"
:key="item.id"
v-memo="[item.id, item.name, item.status]"
>
{{ item.name }}
</div>
</template>
<script setup>
// 使用 shallowRef 优化大列表
const list = shallowRef([]);
// 防抖处理搜索
import { debounce } from 'lodash-es';
const handleSearch = debounce((keyword) => {
// 搜索逻辑
}, 300);
</script>
8.3 错误处理
8.3.1 全局错误处理
// src/utils/errorHandler.ts
export class ErrorHandler {
static handle(error: Error, context?: string) {
console.error(`[${context}] Error:`, error);
// 发送错误报告
if (process.env.NODE_ENV === 'production') {
this.reportError(error, context);
}
// 用户友好提示
window.$message?.error('操作失败,请稍后重试');
}
static reportError(error: Error, context?: string) {
// 上报错误到监控平台
}
}
8.3.2 异步错误捕获
<script setup>
import { onErrorCaptured } from 'vue';
// 捕获子组件错误
onErrorCaptured((error, instance, info) => {
console.error('Component error:', error);
return false; // 阻止错误继续传播
});
// API 错误处理
const fetchData = async () => {
try {
const data = await getUserList();
return data;
} catch (error) {
ErrorHandler.handle(error, 'fetchData');
}
};
</script>
8.4 安全最佳实践
8.4.1 XSS 防护
<template>
<!-- ❌ 危险:直接渲染 HTML -->
<div v-html="userInput"></div>
<!-- ✅ 安全:使用文本插值 -->
<div>{{ userInput }}</div>
<!-- ✅ 安全:使用 DOMPurify 清理 HTML -->
<div v-html="sanitizedHtml"></div>
</template>
<script setup>
import DOMPurify from 'dompurify';
const sanitizedHtml = computed(() =>
DOMPurify.sanitize(userInput.value)
);
</script>
8.4.2 权限控制
// src/hooks/usePermission.ts
export function usePermission() {
const userStore = useUserStore();
const hasPermission = (permission: string) => {
const permissions = userStore.getPermissions;
return permissions.includes(permission);
};
const hasAnyPermission = (permissions: string[]) => {
return permissions.some(permission => hasPermission(permission));
};
return {
hasPermission,
hasAnyPermission,
};
}
总结
Naive UI Admin 作为一个成熟的企业级中后台管理系统模板,具有以下显著优势:
- 技术先进性:采用 Vue3 + TypeScript + Vite 等最新技术栈
- 架构合理性:清晰的分层架构和组件化设计
- 开发效率:丰富的组件库和开箱即用的功能
- 可维护性:良好的代码规范和类型安全
- 扩展性:灵活的插件机制和主题系统
对于企业级项目开发,Naive UI Admin 提供了一个可靠的基础框架,能够显著提升开发效率和产品质量。开发者可以基于此框架快速构建符合业务需求的管理系统,同时保持代码的可维护性和扩展性。
参考资源