news 2026/10/1 8:32:08

umi后台管理项目实战:从工程搭建到生产构建

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
umi后台管理项目实战:从工程搭建到生产构建

1. 引言

本文以 umi 为技术底座,完整讲解一个后台管理项目的实战落地过程,覆盖从基本工程搭建、登录注册、菜单权限配置、页面跳转、用户操作埋点,到开发调试与生产构建的完整链路。通过本文,你可以掌握一套可复用的 umi 后台管理项目开发范式。

2. 基本工程搭建

2.1 初始化项目

umi 提供了官方脚手架,可以快速生成一个带路由、布局和权限模型的基础工程。推荐使用 pnpm 作为包管理器,初始化命令如下:

# 使用 pnpm 创建 umi 项目 pnpm create umi 选择需要的功能:antd、dva、qiankun、pro-layout 等 进入项目目录 cd my-admin 安装依赖 pnpm install 启动开发服务 pnpm start

初始化完成后,项目会生成src、config、public等目录,其中config/config.ts是核心配置文件,负责路由、插件和构建相关的全局配置。

2.2 目录结构规划

一个清晰的后台管理项目目录结构,可以显著降低后续维护成本。推荐按业务模块划分:

src/ ├── pages/ # 页面组件,按路由组织 ├── components/ # 通用业务组件 ├── layouts/ # 全局布局 ├── services/ # 接口请求层 ├── models/ # 全局状态管理 ├── utils/ # 工具函数 ├── constants/ # 常量定义 └── access.ts # 权限定义

2.3 配置路由与布局

umi 采用约定式路由与配置式路由相结合的方式。后台管理项目通常使用配置式路由,以便统一管理菜单和权限。在config/routes.ts中定义路由结构:

export default [ { path: '/user', layout: false, routes: [ { path: '/user/login', component: './User/Login' }, { path: '/user/register', component: './User/Register' }, ], }, { path: '/', component: '@/layouts/BasicLayout', routes: [ { path: '/dashboard', name: '工作台', icon: 'DashboardOutlined', component: './Dashboard' }, { path: '/system', name: '系统管理', icon: 'SettingOutlined' }, ], }, ];

3. 登录注册

3.1 登录页实现

登录页是后台管理系统的入口。使用 antd 的Form组件实现表单校验,配合ProForm可以进一步简化开发。核心逻辑包括:表单校验、调用登录接口、存储 Token、跳转首页。

import { ProForm, ProFormText } from '@ant-design/pro-components'; import { history } from 'umi'; import { login } from '@/services/auth'; const LoginPage = () => { const handleSubmit = async (values: { username: string; password: string }) => { const { token } = await login(values); localStorage.setItem('token', token); history.push('/dashboard'); }; return ( <ProForm onFinish={handleSubmit}> <ProFormText name="username" label="用户名" rules={[{ required: true }]} /> <ProFormText.Password name="password" label="密码" rules={[{ required: true }]} /> </ProForm> ); }; export default LoginPage;

3.2 注册页与验证码

注册页通常需要额外的校验逻辑,例如确认密码、图形验证码或短信验证码。建议将验证码逻辑封装为独立组件,便于复用:

const CaptchaInput = ({ onSend }: { onSend: (phone: string) => Promise<void> }) => { const [countdown, setCountdown] = useState(0); const handleSend = async (phone: string) => { await onSend(phone); setCountdown(60); const timer = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { clearInterval(timer); return 0; } return prev - 1; }); }, 1000); }; return ( <Space> <Input placeholder="手机号" /> <Button disabled={countdown > 0} onClick={() => handleSend('')}> {countdown > 0 ? ${countdown}s 后重发 : '发送验证码'} </Button> </Space> ); };

3.3 Token 管理与请求拦截

登录成功后,需要统一管理 Token。推荐在request.ts中封装请求拦截器,自动携带 Token,并统一处理 401 未授权场景:

import { request } from 'umi'; import { history } from 'umi'; const authRequest = request.extend({ requestInterceptors: [(url, options) => { const token = localStorage.getItem('token'); if (token) { options.headers = { ...options.headers, Authorization: Bearer ${token} }; } return { url, options }; }], responseInterceptors: [(response) => { if (response.status === 401) { localStorage.removeItem('token'); history.push('/user/login'); } return response; }], }); export default authRequest;

4. 菜单权限配置

4.1 权限模型设计

后台管理系统的权限通常分为菜单权限和操作权限两层。菜单权限决定用户能看到哪些页面,操作权限决定用户能执行哪些按钮操作。推荐使用基于角色的权限模型(RBAC):

  • 用户:系统使用者,可关联一个或多个角色。
  • 角色:权限的集合,例如管理员、运营、访客。
  • 菜单/操作:具体的页面入口或按钮动作。

4.2 动态路由与菜单生成

umi 支持通过access.ts定义权限规则,并结合initialState实现动态菜单。登录后从接口获取用户权限列表,动态生成菜单:

// access.ts export default function access(initialState: { currentUser?: API.CurrentUser }) { const { currentUser } = initialState || {}; return { canAdmin: currentUser && currentUser.roles.includes('admin'), canEdit: currentUser && currentUser.permissions.includes('edit'), }; }
// 动态菜单配置 const menuData = [ { path: '/dashboard', name: '工作台', icon: 'DashboardOutlined' }, { path: '/system/user', name: '用户管理', icon: 'UserOutlined', access: 'canAdmin' }, { path: '/system/role', name: '角色管理', icon: 'TeamOutlined', access: 'canAdmin' }, { path: '/system/menu', name: '菜单管理', icon: 'MenuOutlined', access: 'canAdmin' }, ];

4.3 按钮级权限控制

除了菜单权限,后台管理还经常需要控制页面内的按钮是否可见。可以通过自定义 Hook 或高阶组件实现:

import { useAccess } from 'umi'; const UserTable = () => { const access = useAccess(); return ( <Table columns={[ { title: '用户名', dataIndex: 'username' }, { title: '操作', render: (_, record) => ( <Space> {access.canEdit && <Button type="link">编辑</Button>} {access.canAdmin && <Button type="link" danger>删除</Button>} </Space> ) } ]} /> ); };

5. 页面跳转

5.1 声明式跳转

umi 提供Link组件和history对象两种跳转方式。声明式跳转适合菜单、面包屑等静态场景:

import { Link } from 'umi'; const Breadcrumb = () => ( <Link to="/system/user">用户管理</Link> );

5.2 编程式跳转

在表单提交、按钮点击等交互场景中,通常使用编程式跳转,并支持携带查询参数:

import { history } from 'umi'; // 跳转到详情页并携带 id history.push(/system/user/detail?id=${userId}); // 带 state 跳转,适合传递复杂对象 history.push('/system/user/detail', { userId, from: 'list' }); // 返回上一页 history.back();

5.3 路由守卫与重定向

未登录用户访问受保护页面时,需要自动重定向到登录页。umi 的wrappers机制可以方便地实现路由守卫。下面给出一个更完整的守卫实现,支持免登录白名单、登录后重定向回原页面,以及全局路由鉴权配置:

// wrappers/auth.tsx import { Redirect, useLocation } from 'umi'; import { isLogin } from '@/utils/auth'; // 免登录白名单,这些页面无需登录即可访问 const WHITE_LIST = ['/user/login', '/user/register', '/user/forgot']; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { const location = useLocation(); // 白名单页面直接放行 if (WHITE_LIST.includes(location.pathname)) { return <>{children}</>; } // 未登录则重定向到登录页,并携带来源路径,登录成功后跳回原页面 if (!isLogin()) { const redirect = encodeURIComponent(location.pathname + location.search); return <Redirect to={`/user/login?redirect=${redirect}`} />; } return <>{children}</>; }; export default AuthWrapper;
// utils/auth.ts export const TOKEN_KEY = 'token'; export const isLogin = () => Boolean(localStorage.getItem(TOKEN_KEY)); export const getToken = () => localStorage.getItem(TOKEN_KEY); export const setToken = (token: string) => localStorage.setItem(TOKEN_KEY, token); export const clearToken = () => localStorage.removeItem(TOKEN_KEY);
// 路由配置中应用守卫 { path: '/dashboard', component: './Dashboard', wrappers: ['@/wrappers/auth'], }

登录成功后,根据redirect参数跳回原页面,实现免登录重定向闭环:

// 登录页提交成功后 const { token } = await login(values); setToken(token); const redirect = new URLSearchParams(location.search).get('redirect'); history.push(redirect || '/dashboard');

6. 页面用户操作埋点

6.1 埋点方案设计

用户操作埋点用于采集用户在页面上的行为数据,为产品决策提供依据。常见的埋点类型包括:

  • 页面浏览:PV/UV、停留时长、来源页面。
  • 按钮点击:按钮名称、所在页面、点击次数。
  • 表单提交:提交成功率、失败原因。
  • 搜索行为:关键词、搜索结果数量。

6.2 统一埋点工具封装

建议封装统一的埋点工具函数,避免在每个页面重复写上报逻辑:

// utils/tracker.ts interface TrackEvent { event: string; page: string; params?: Record<string, unknown>; } export const track = ({ event, page, params }: TrackEvent) => { // 上报到埋点服务 fetch('/api/track', { method: 'POST', body: JSON.stringify({ event, page, params, timestamp: Date.now(), userId: localStorage.getItem('userId'), }), }); };

6.3 页面浏览埋点

页面浏览埋点可以在路由切换时统一触发。umi 提供了onRouteChange配置,可以在路由变化时执行埋点逻辑:

// app.tsx import { track } from '@/utils/tracker'; export function onRouteChange({ location }: { location: { pathname: string } }) { track({ event: 'page_view', page: location.pathname, }); }

6.4 按钮点击埋点

按钮点击埋点可以通过封装高阶组件或自定义 Hook 实现,减少业务代码侵入:

// hooks/useTrack.ts import { track } from '@/utils/tracker'; export const useTrack = (page: string) => { const trackClick = (event: string, params?: Record<string, unknown>) => { track({ event, page, params }); }; return { trackClick }; }; // 页面中使用 const { trackClick } = useTrack('/system/user'); const handleDelete = (id: number) => { trackClick('user_delete', { id }); // 执行删除逻辑 };

7. 开发调试

7.1 本地开发代理

开发阶段前后端分离,需要配置代理解决跨域问题。在config/config.ts中配置proxy:

export default { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' }, }, }, };

7.2 调试工具与日志

开发调试时,合理使用浏览器 DevTools 和日志输出可以快速定位问题。建议在utils/logger.ts中封装分级日志工具:

// utils/logger.ts const isDev = process.env.NODE_ENV === 'development'; export const logger = { info: (...args: unknown[]) => { if (isDev) console.info('[INFO]', ...args); }, warn: (...args: unknown[]) => { if (isDev) console.warn('[WARN]', ...args); }, error: (...args: unknown[]) => { console.error('[ERROR]', ...args); }, };

7.3 状态调试

使用 dva 或 valtio 等状态管理方案时,可以借助 Redux DevTools 或自定义中间件查看状态变化。推荐在开发环境开启状态日志,方便追踪数据流:

// models/user.ts export default { namespace: 'user', state: { list: [], loading: false }, reducers: { save(state, { payload }) { logger.info('user/save', payload); return { ...state, ...payload }; }, }, };

8. 开发生产构建

8.1 环境变量管理

不同环境(开发、测试、生产)通常需要不同的接口地址和配置。umi 支持通过.env文件管理环境变量:

# .env.development API_BASE_URL=http://localhost:8080/api .env.production API_BASE_URL=https://api.example.com/api
// 代码中读取环境变量 const apiBase = process.env.API_BASE_URL;

8.2 生产构建配置

生产构建需要关注代码压缩、资源分包、CDN 路径等。umi 默认集成了 webpack 优化,但部分场景需要手动调整:

export default { define: { 'process.env.API_BASE_URL': process.env.API_BASE_URL, }, hash: true, // 生成带 hash 的资源文件名 publicPath: process.env.NODE_ENV === 'production' ? 'https://cdn.example.com/' : '/', chunks: ['vendors', 'umi'], chainWebpack(config) { config.optimization.splitChunks({ cacheGroups: { vendors: { name: 'vendors', test: /[\\/]node_modules[\\/]/, priority: 10, chunks: 'all', }, }, }); }, };

8.3 构建与部署

生产构建命令为pnpm build,构建产物输出到dist目录。部署时需要注意:

  • 将dist目录部署到 Nginx 或对象存储。
  • 配置 SPA 路
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 8:31:22

出海企业希望通过统一平台接入国际主流基础模型,推荐选择哪些云上生成式AI平台?

出海企业希望通过统一平台接入国际主流基础模型&#xff0c;推荐选择哪些云上生成式AI平台&#xff1f;海外业务不必为每一家模型重搭一套接口出海企业建设生成式 AI 应用时&#xff0c;模型选择往往比单一市场更复杂。不同业务可能需要复杂推理、软件开发、长文档分析、Agent …

作者头像 李华
网站建设 2026/10/1 8:30:59

2026年医药集团主数据管理成熟度评估:从“数据围城”到“基准锚”的4级跃迁路径与自评清单

摘要&#xff1a; 医药集团普遍深陷“数据围城”——ERP、MES、LIMS、QMS等系统各自为政&#xff0c;物料编码不统一、供应商资质分散管理、批文信息与产成品脱节&#xff0c;合规风险与运营成本同步攀升。中国医药集团有限公司在《中国信息界》2026年第5期发表的研究指出&…

作者头像 李华
网站建设 2026/10/1 8:29:19

2026年五大私有化经销订货商城推荐:部署与数据控制解析

2026年五大私有化经销订货商城推荐&#xff1a;部署与数据控制解析摘要私有化这三个字&#xff0c;最近几年在选型会上被提到的频率明显变高。渠道价格体系、经销商档案、客户成交数据&#xff0c;这些内容过去放在谁的服务器上&#xff0c;很多企业并不太在意&#xff1b;但当…

作者头像 李华