人力资本管理HCM系统是典型的中后台复杂应用,Ultimate UltiPro作为业界知名的人力资源与人才管理解决方案,其产品形态给我们做自研HCM系统提供了很好的参考:组织人事、薪酬、考勤、绩效、人才盘点等模块高度解耦,同时又共享统一的用户、权限和数据字典体系。要用Vue 3实现类似的产品形态,光会写组件远远不够,工程化能力才是决定项目能否长期演进的关键。本文将从项目结构、状态管理、权限体系、业务组件封装和构建优化五个方面,完整梳理一套可落地的工程化方案。

一、参考UltiPro模块划分设计项目目录结构
UltiPro这类成熟HCM产品的核心特点,是按业务域而非技术层来组织功能。组织与人事是一个域,薪酬核算是另一个域,绩效与人才发展又是一个域。翻译到前端工程上,直接照搬传统的components、views平铺结构,随着模块增多会迅速失控。更合理的做法是采用业务域优先的目录设计,每个业务模块内部自带自己的路由、组件、状态和API定义。
推荐的目录结构如下:
src/ ├── modules/ # 业务模块层,按HCM业务域划分 │ ├── org/ # 组织架构与人事档案 │ │ ├── routes.ts │ │ ├── api/ │ │ ├── components/ │ │ └── store.ts │ ├── payroll/ # 薪酬核算 │ ├── performance/ # 绩效管理 │ └── talent/ # 人才盘点与发展 ├── shared/ # 跨模块共享层 │ ├── components/ # 通用组件:表格、搜索面板、弹窗等 │ ├── composables/ # 组合式函数 │ ├── directives/ │ └── utils/ ├── router/ ├── stores/ # 全局状态:用户、权限、字典 ├── styles/ └── main.ts
这种结构的好处在于边界清晰。开发薪酬模块的同学基本只操作payroll目录,通用能力下沉到shared,通过模块的routes.ts自动收集路由,避免路由文件膨胀成几千行的巨型文件。需要注意的是,模块之间应禁止横向引用,比如payroll要用人事档案选择器,应该把这个选择器提升到shared或者org模块的导出入口,配合ESLint的import规则做约束,否则模块化形同虚设。
二、用Pinia管理HCM全局状态与数据字典
HCM系统有大量全局共享数据:当前登录员工信息、组织树、职位序列、成本中心、枚举字典等。Vue 3生态下Pinia是标配选择,它按store拆分代码天然支持模块化,且对TypeScript的支持远好于Vuex。设计上建议区分三类store:会话store、字典store和UI store。
会话store管理登录态与权限数据,字典store负责缓存各种枚举和组织树数据并支持懒加载刷新,UI store则记录侧边栏折叠、主题这类界面状态。以字典store为例:
import { defineStore } from 'pinia'
export const useDictStore = defineStore('dict', {
state: () => ({
orgTree: [] as OrgNode[],
jobFamilies: [] as DictItem[],
loaded: false
}),
actions: {
async fetchOrgTree(force = false) {
if (this.loaded && !force) return this.orgTree
const res = await getOrgTree()
this.orgTree = buildTree(res.data)
this.loaded = true
return this.orgTree
}
}
})
这个模式的关键点在于缓存语义明确:组件里直接调用fetchOrgTree,第一次触发请求,后续命中内存缓存,强刷场景传force为true即可。HCM的组织树往往很深、员工量上万,还应考虑在shared中封装一个useOrgTreePicker组合式函数,内部处理懒加载子节点、搜索过滤和选中回显,避免每个模块重复实现。
三、动态路由与细粒度权限控制方案
HCM系统的权限极其复杂:HRBP只能看自己对接的事业部薪酬,普通员工只能看自己的工资条,薪酬专员可以核算但不能审批。工程化上推荐后端返回权限点集合,前端根据权限点动态注册路由并渲染菜单。核心流程是:登录后拉取用户信息与权限点,利用Vue Router的addRoute动态挂载模块路由,同时用自定义指令控制按钮级权限。
路由守卫与指令的实现思路如下:
// router/guard.ts
router.beforeEach(async (to) => {
const userStore = useUserStore()
if (!userStore.token) return { path: '/login' }
if (!userStore.routesAdded) {
const perms = await userStore.fetchPermissions()
const allowed = filterRoutesByPerms(moduleRoutes, perms)
allowed.forEach((r) => router.addRoute('layout', r))
userStore.routesAdded = true
return { ...to, replace: true }
}
if (to.meta.permission && !userStore.has(to.meta.permission as string)) {
return { path: '/403' }
}
})
// directives/permission.ts
export const vPermission: Directive = {
mounted(el, binding) {
const userStore = useUserStore()
if (!userStore.has(binding.value)) {
el.parentNode?.removeChild(el)
}
}
}
路由级守卫负责页面可见性,指令负责按钮级控制,两者配合基本能覆盖HCM的绝大多数权限场景。要注意薪酬这类敏感模块,前端隐藏只是体验层手段,真正的数据隔离必须由后端接口校验,前端权限永远不能作为安全边界。
四、封装HCM高频业务组件:表格与表单
HCM系统的页面八成是列表加表单:员工花名册、薪资台账、绩效结果录入。如果每个页面都手写el-table加一堆查询参数,重复代码会非常可观。建议在shared中封装两个核心组件:ProTable和SchemaForm。ProTable接收列定义、请求函数和查询参数模板,内部处理分页、loading、参数重置;SchemaForm基于JSON配置渲染表单项,支持字段联动。
一个员工花名册页面的使用效果大致是:
<template>
<ProTable
:columns="columns"
:request="fetchEmployeeList"
:search-schema="searchSchema"
/>
</template>
<script setup lang="ts">
const searchSchema = [
{ field: 'keyword', label: '姓名/工号', type: 'input' },
{ field: 'orgId', label: '所属部门', type: 'orgTree' },
{ field: 'status', label: '在职状态', type: 'select',
options: () => useDictStore().fetchDict('empStatus') }
]
const columns = [
{ prop: 'empNo', label: '工号', width: 100 },
{ prop: 'name', label: '姓名' },
{ prop: 'orgPath', label: '部门路径' },
{ prop: 'entryDate', label: '入职日期', sortable: true }
]
</script>
封装这类组件时有个容易踩的坑:过度设计。一开始不要追求配置覆盖所有场景,保留插槽逃生口,特殊页面允许直接写原生组件。ProTable的价值在于统一交互规范和减少样板代码,而不是替代开发者的思考能力。另外注意组合式函数的复用粒度,像usePagination、useSelection这类细粒度函数比巨型组件更灵活。
五、构建优化与按需加载策略
HCM系统页面多、依赖的图表和编辑器类库体积大,不做优化首屏会很慢。Vite生产构建下有几件事必须做:路由组件全部懒加载,通过manualChunks把Element Plus、ECharts等大依赖拆成独立chunk;图片类静态资源走CDN;对组织架构图这类重交互模块启用模块预加载提示。
典型的分包配置:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-ui': ['element-plus'],
'vendor-chart': ['echarts'],
'vendor-core': ['vue', 'vue-router', 'pinia']
}
}
}
}
})
除了分包,还要关注依赖预构建和按需引入。Element Plus配合unplugin-vue-components可以自动按需注册组件,ECharts只引入用到的柱状图、折线图模块而不是全量引入,通常能减少一半以上的体积。上线前用rollup-plugin-visualizer分析产物,把超过500KB的chunk单独审视一遍,往往能发现意外引入的全量库。配合打包分析报告做持续监控,才能保证项目在两年后依然保持健康的构建体积。
总结
用Vue 3构建HCM人才管理系统,工程化的核心思路可以概括为:目录结构按业务域划分、全局状态用Pinia分层管理、权限走动态路由加指令控制、高频页面用ProTable和SchemaForm提效、构建产物做分包与按需加载。Ultimate UltiPro这类产品的价值不在于照抄功能,而在于学习它对模块边界的把握。把这些实践落实到项目规范中并配合ESLint、CI检查固化下来,团队的开发效率和代码可维护性都会有明显提升。
Vue 3工程化Ultimate UltiPro人才管理系统修改时间:2026-09-04 04:26:51