【Naive UI Admin 学习】基于Vue3 + TypeScript + Naive UI 的中后台前端框架

【Naive UI Admin 学习】基于Vue3 + TypeScript + Naive UI 的中后台前端框架

✨ 基于Vue3 + TypeScript + Naive UI 的中后台前端框架

官网:https://docs.naiveadmin.com/

在线预览:https://gratis.naiveadmin.com


📖 目录

  1. 项目概述
  2. 技术栈分析
  3. 项目结构详解
  4. 核心功能实现
  5. 架构设计原理
  6. 开发指南
  7. 部署与优化
  8. 最佳实践

1. 项目概述

1.1 项目介绍

Naive UI Admin 是一款基于 Vue3.0、Vite、Naive UI 和 TypeScript 的开源中后台管理系统。它融合了最新的前端技术栈,提炼了典型的业务模型和页面,包括二次封装组件、动态菜单、权限校验等功能,助力快速搭建企业级中后台项目。

1.2 核心特性

  • 🚀 最新技术栈:基于 Vue3、Vite、TypeScript 等最新技术栈开发
  • ⚡️ 极速开发:热重载快速,开发体验流畅
  • 📦 组件封装:对日常使用频率较高的组件进行二次封装
  • 🔩 主题配置:丰富的主题配置及黑暗主题适配
  • 🔑 权限管理:完善的前后端权限管理方案
  • 🛠️ 最佳实践:常用页面布局,组件使用示例

1.3 版本信息

json 复制代码
{
  "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)
typescript 复制代码
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)
typescript 复制代码
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 动态路由加载
typescript 复制代码
// 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 路由守卫实现
typescript 复制代码
// 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 用户状态管理
typescript 复制代码
// 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 请求库配置
typescript 复制代码
// 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 整体架构

用户界面层 Views 组件层 Components 业务逻辑层 Hooks/Composables 状态管理层 Pinia Store API 服务层 Alova HTTP 持久化层 Local Storage Mock 数据层 后端 API

5.2 组件设计模式

5.2.1 高阶组件封装

以 Table 组件为例:

vue 复制代码
<!-- 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 设计
typescript 复制代码
// 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 响应式布局
vue 复制代码
<!-- 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 动态主题切换
typescript 复制代码
// 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 快速开始
bash 复制代码
# 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 代码规范

项目集成了完整的代码规范工具:

json 复制代码
{
  "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 提交规范
bash 复制代码
# 功能开发
git commit -m "feat: 添加用户管理功能"

# 问题修复
git commit -m "fix: 修复表格分页问题"

# 样式调整
git commit -m "style: 调整按钮样式"

# 性能优化
git commit -m "perf: 优化表格渲染性能"

6.3 组件开发

6.3.1 创建新组件
typescript 复制代码
// 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
typescript 复制代码
// 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 创建新页面
vue 复制代码
<!-- 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 添加路由
typescript 复制代码
// 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 环境变量配置
bash 复制代码
# .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 构建优化
typescript 复制代码
// 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 路由懒加载
typescript 复制代码
// 动态导入组件
const routes = [
  {
    path: '/dashboard',
    component: () => import('@/views/dashboard/index.vue'),
  },
];
7.2.2 组件懒加载
vue 复制代码
<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 配置
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 复制代码
# 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 类型定义
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 合理使用响应式
typescript 复制代码
// ❌ 不推荐:深度响应式大对象
const state = reactive({
  userList: [], // 大数组
  config: {}, // 复杂配置对象
});

// ✅ 推荐:按需响应式
const userList = shallowRef([]); // 浅响应式
const selectedUser = ref(null); // 单个响应式值
const config = readonly(configData); // 只读数据
8.2.2 组件优化
vue 复制代码
<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 全局错误处理
typescript 复制代码
// 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 异步错误捕获
vue 复制代码
<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 防护
vue 复制代码
<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 权限控制
typescript 复制代码
// 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 作为一个成熟的企业级中后台管理系统模板,具有以下显著优势:

  1. 技术先进性:采用 Vue3 + TypeScript + Vite 等最新技术栈
  2. 架构合理性:清晰的分层架构和组件化设计
  3. 开发效率:丰富的组件库和开箱即用的功能
  4. 可维护性:良好的代码规范和类型安全
  5. 扩展性:灵活的插件机制和主题系统

对于企业级项目开发,Naive UI Admin 提供了一个可靠的基础框架,能够显著提升开发效率和产品质量。开发者可以基于此框架快速构建符合业务需求的管理系统,同时保持代码的可维护性和扩展性。


参考资源