| name | Admin.NET-frontend |
| description | Use when building Vue 3 admin dashboard frontends with dynamic routing, permission-based menus, and CRUD page generation. Admin.NET Frontend: Vue 3 + Vite + TypeScript admin UI for Admin.NET backend. |
| tags | ["vue3","typescript","element-plus","vite","admin","frontend"] |
项目地址: https://github.com/znlgis/Admin.NET
前端代码目录: Web/(Vue3 应用)
在线文档: https://adminnet.top/
演示环境: https://demo.adminnet.top(账号:superAdmin.NET / 密码:Admin.NET++010101)
许可证: MIT
概述
Admin.NET 前端基于 vue-next-admin 模板二次开发,采用 Vue 3 Composition API + TypeScript + Element Plus + Vite 5 技术栈。项目提供完整的企业级中后台管理界面,包括登录认证、动态菜单/路由、RBAC 权限控制、多语言、主题切换、SignalR 实时通讯等能力。
核心技术栈:
- 框架: Vue 3.5+(Composition API +
<script setup>)
- 语言: TypeScript 5.x
- 构建: Vite 5+ / pnpm
- UI 组件: Element Plus 2.x
- 路由: Vue Router 5
- 状态管理: Pinia 3
- 国际化: vue-i18n 11
- HTTP 客户端: Axios
- 图表: ECharts 6
- 实时通讯: @microsoft/signalr
项目结构
Web/
├── public/ # 公共静态资源
├── lang/ # 国际化语言文件
├── api_build/ # API 代码自动生成脚本
├── scripts/ # 构建辅助脚本
├── src/
│ ├── api/ # 手写 API 调用(axios 封装)
│ ├── api-services/ # 自动生成的 API 服务(对应后端 Swagger)
│ ├── api-plugins/ # 插件相关的 API 服务
│ ├── assets/ # 静态资源(图片、样式)
│ ├── components/ # 全局公共组件
│ ├── directive/ # 自定义指令(如权限指令)
│ ├── layout/ # 布局组件(侧边栏、头部、标签页等)
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia 状态管理
│ ├── theme/ # 主题样式
│ ├── types/ # TypeScript 类型定义
│ ├── utils/ # 工具函数
│ ├── views/ # 页面视图
│ │ ├── login/ # 登录页
│ │ ├── home/ # 首页/仪表盘
│ │ ├── system/ # 系统管理页面集合
│ │ ├── approvalFlow/ # 审批流程
│ │ ├── mqttx/ # MQTT 客户端
│ │ ├── elive/ # 视频监控
│ │ ├── about/ # 关于页
│ │ └── error/ # 错误页(403/404/500)
│ ├── App.vue # 根组件
│ └── main.ts # 应用入口
├── index.html # HTML 入口
├── package.json # 依赖配置
├── vite.config.ts # Vite 构建配置
├── tsconfig.json # TypeScript 配置
├── eslint.config.mjs # ESLint 配置
├── .prettierrc.cjs # Prettier 配置
├── .env # 通用环境变量
├── .env.development # 开发环境变量
└── .env.production # 生产环境变量
环境准备与运行
前置要求
- Node.js: >= 18.0.0
- 包管理器: pnpm(推荐 10.x)
安装与启动
cd Web
pnpm install
pnpm run dev
pnpm run build
环境变量
VITE_PORT = 2800
VITE_OPEN = false
VITE_APP_TITLE = Admin.NET
VITE_API_URL = http://localhost:5005
VITE_API_URL = /
核心模块
API 服务调用
前端 API 调用分为两种模式:
1. 自动生成的 API 服务(src/api-services/)
通过 api_build/ 脚本从后端 Swagger JSON 自动生成 TypeScript API 客户端:
cd api_build
node index.js
生成的代码按后端服务模块组织,可直接调用:
import { getAPI } from '/@/utils/axios-utils';
import { SysUserApi } from '/@/api-services/api';
const res = await getAPI(SysUserApi).apiSysUserPagePost(queryParams);
await getAPI(SysUserApi).apiSysUserAddPost(userData);
await getAPI(SysUserApi).apiSysUserDeletePost({ id: userId });
2. 手写 API 调用(src/api/)
对于自定义或简单场景,直接使用封装的 axios:
import request from '/@/utils/request';
export const getMyData = (params: object) => {
return request({
url: '/api/myService/list',
method: 'get',
params,
});
};
export const addMyData = (data: object) => {
return request({
url: '/api/myService/add',
method: 'post',
data,
});
};
路由与菜单
Admin.NET 前端采用 动态路由 方案,菜单和路由信息从后端接口获取:
interface RouteMeta {
title: string;
icon?: string;
isHide?: boolean;
isKeepAlive?: boolean;
isAffix?: boolean;
isLink?: string;
isIframe?: boolean;
auth?: string[];
}
添加新页面
- 在
src/views/ 下新建目录和 .vue 文件
- 在后端管理界面中配置菜单(指定路由路径和组件路径)
- 前端自动加载,无需手动注册路由
状态管理(Pinia)
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: {} as UserInfo,
token: '',
}),
actions: {
async login(data: LoginData) {
const res = await loginApi(data);
this.token = res.data.accessToken;
this.userInfo = res.data.userInfo;
},
async logout() {
this.token = '';
this.userInfo = {} as UserInfo;
},
},
});
权限控制
按钮级权限指令
<template>
<!-- 通过 v-auth 指令控制按钮显示 -->
<el-button v-auth="'sysUser:add'" type="primary">新增用户</el-button>
<el-button v-auth="'sysUser:edit'" type="warning">编辑</el-button>
<el-button v-auth="'sysUser:delete'" type="danger">删除</el-button>
</template>
编程式权限判断
import { useUserStore } from '/@/stores/user';
const userStore = useUserStore();
if (userStore.userInfo.authBtnList.includes('sysUser:add')) {
}
国际化(i18n)
Web/lang/
├── zh-cn.ts # 简体中文
├── en.ts # 英语
└── ... # 其他语言
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
SignalR 实时通讯
前端通过 @microsoft/signalr 与后端 Hub 建立长连接:
import * as signalR from '@microsoft/signalr';
const connection = new signalR.HubConnectionBuilder()
.withUrl('/hubs/onlineUser', {
accessTokenFactory: () => token,
})
.withAutomaticReconnect()
.build();
connection.on('onlineUserChanged', (data) => {
console.log('在线用户更新:', data);
});
connection.on('receiveNotice', (notice) => {
ElNotification({ title: notice.title, message: notice.content });
});
await connection.start();
页面开发模板
标准 CRUD 页面
<template>
<div class="my-page-container">
<!-- 搜索区域 -->
<el-card shadow="hover">
<el-form :model="queryParams" :inline="true">
<el-form-item label="名称">
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><ele-Search /></el-icon> 查询
</el-button>
<el-button @click="resetQuery">
<el-icon><ele-Refresh /></el-icon> 重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 操作按钮 -->
<el-card shadow="hover" style="margin-top: 8px">
<el-button v-auth="'myBiz:add'" type="primary" @click="openAddDialog">
<el-icon><ele-Plus /></el-icon> 新增
</el-button>
<!-- 数据表格 -->
<el-table :data="tableData" v-loading="loading" style="width: 100%">
<el-table-column prop="name" label="名称" />
<el-table-column prop="remark" label="备注" />
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button v-auth="'myBiz:edit'" type="warning" size="small"
@click="openEditDialog(scope.row)">编辑</el-button>
<el-button v-auth="'myBiz:delete'" type="danger" size="small"
@click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:currentPage="queryParams.page"
v-model:page-size="queryParams.pageSize"
:total="total"
@current-change="handleQuery"
@size-change="handleQuery"
layout="total, sizes, prev, pager, next, jumper"
/>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { getAPI } from '/@/utils/axios-utils';
import { MyBizApi } from '/@/api-services/api';
const loading = ref(false);
const tableData = ref([]);
const total = ref(0);
const queryParams = ref({
name: '',
page: 1,
pageSize: 20,
});
const handleQuery = async () => {
loading.value = true;
const res = await getAPI(MyBizApi).apiMyBizPagePost(queryParams.value);
tableData.value = res.data.result?.items ?? [];
total.value = res.data.result?.total ?? 0;
loading.value = false;
};
const resetQuery = () => {
queryParams.value = { name: '', page: 1, pageSize: 20 };
handleQuery();
};
const handleDelete = (row: any) => {
ElMessageBox.confirm('确定删除吗?', '提示', { type: 'warning' }).then(async () => {
await getAPI(MyBizApi).apiMyBizDeletePost({ id: row.id });
ElMessage.success('删除成功');
handleQuery();
});
};
onMounted(() => {
handleQuery();
});
</script>
常用组件
| 组件 | 包 / 路径 | 用途 |
|---|
el-table / el-form / el-dialog | Element Plus | 表格、表单、对话框 |
el-tree / el-tree-select | Element Plus | 树形选择(机构、菜单) |
el-upload | Element Plus | 文件上传 |
@wangeditor/editor | wangEditor | 富文本编辑器 |
md-editor-v3 | md-editor-v3 | Markdown 编辑器 |
monaco-editor | Monaco Editor | 代码编辑器 |
echarts / echarts-gl | ECharts 6 | 图表与 3D 可视化 |
vform3-builds | VForm3 | 在线表单构建器 |
vue-plugin-hiprint / print-js | hiprint | 打印与报表 |
@vue-office/pdf / excel / docx | vue-office | Office 文件预览 |
logicflow | LogicFlow | 流程图编辑 |
relation-graph | relation-graph | 关系图可视化 |
json-editor-vue | json-editor-vue | JSON 编辑器 |
vue-grid-layout | vue-grid-layout | 网格拖拽布局 |
vue-draggable-plus | vue-draggable-plus | 拖拽排序 |
splitpanes | splitpanes | 分割面板 |
cropperjs | cropperjs | 图片裁剪 |
xlsx-js-style | xlsx-js-style | Excel 处理 |
mqtt | mqtt 5 | MQTT 物联网通讯 |
sm-crypto-v2 | sm-crypto-v2 | 国密 SM2/SM3/SM4 |
样式与主题
- CSS 预处理器: SASS
- 主题方案: Element Plus 主题变量 + 自定义 CSS 变量
- 暗黑模式: 支持亮色/暗色主题切换
- 布局模式: 支持多种布局(经典、横向、分栏、混合)
:root {
--el-color-primary: #409eff;
--next-bg-main-color: #f0f2f5;
}
构建与部署
生产构建
pnpm run build
Nginx 部署配置
server {
listen 80;
server_name your-domain.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html; # SPA 路由支持
}
location /api {
proxy_pass http://backend-server:5005;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /hubs {
proxy_pass http://backend-server:5005;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # WebSocket 支持
}
}
AI 使用建议
推荐工作流
- 环境准备:Node.js >= 18 + pnpm →
pnpm install → pnpm run dev
- 新增页面:
src/views/ 下新建目录和 .vue 文件 → 后端管理界面配菜单(路由路径 + 组件路径)→ 前端自动加载
- API 对接:后端 Swagger 更新后运行
api_build 脚本自动生成 TypeScript API 客户端 → 用 getAPI(XxxApi) 调用
- 权限控制:后端菜单配置按钮权限标识 → 前端用
v-auth="'xxx:action'" 指令控制按钮显示
- 构建部署:
pnpm run build → dist/ 产物 → Nginx 部署(SPA try_files + API 代理 + WebSocket /hubs)
关键模式与常见陷阱
- 包管理器:必须用
pnpm,项目依赖 pnpm-workspace.yaml,npm/yarn 可能失败
- 环境变量前缀:Vite 项目中自定义环境变量必须以
VITE_ 开头,否则 import.meta.env 读不到
- 路由组件路径:后端菜单配置的组件路径对应
src/views/ 下的文件路径(不含 .vue 后缀),大小写敏感
- 权限标识一致性:前端
v-auth 指令中的标识必须与后端菜单配置中的按钮权限标识完全一致
- API 自动生成:修改后端接口后务必重新运行
api_build 脚本,否则类型定义过期
- SignalR 代理:Nginx 需配置 WebSocket 代理(
/hubs 路径),否则实时通知不工作
如何选择正确方案
| 场景 | 推荐方案 |
|---|
| 配合 Admin.NET 后端 | Admin.NET 前端(开箱即用) |
| 独立 Vue3 后台项目 | 直接用 vue-next-admin 模板 + Element Plus |
| 仅需简单管理界面 | vue-pure-admin / naive-ui-admin |
| 移动端/小程序 | uni-app / Taro |
注意事项
- 包管理器:务必使用
pnpm,项目使用 pnpm-workspace.yaml 管理
- API 自动生成:修改后端接口后需重新运行
api_build 脚本更新前端 API 类型定义
- 权限标识:前端
v-auth 指令中使用的权限标识需与后端菜单配置中的按钮权限一致
- 路由组件路径:后端菜单配置的组件路径对应
src/views/ 下的文件路径(不含后缀)
- 环境变量前缀:Vite 项目中自定义环境变量必须以
VITE_ 开头
- 国密加密:登录密码等敏感数据前端使用
sm-crypto-v2 做 SM2 加密后传输
- SignalR 连接:需正确配置 Nginx 代理 WebSocket(
/hubs 路径)
- Node 版本:要求 Node.js >= 18.0.0
相关技能
参考链接