UltiPro 是全球知名的人力资本管理产品,涵盖人事档案、薪酬、考勤、绩效、招聘等一整套人才管理模块。国内不少企业也在自研类似的人才管理系统,而这类系统的前端特点是:模块多、表单多、权限复杂、生命周期长。如果工程化基础没打好,项目往往在半年后变成谁都改不动的泥球。Vue 3 配合 Vite 和组合式 API,为这类系统提供了很好的技术底座,下面从工程搭建、架构设计、核心模块实现三个层面展开。

一、工程搭建:Vite 脚手架与目录结构设计
人才管理系统通常是中大型项目,目录结构必须一开始就规划清楚。推荐按业务域划分目录,而不是简单按文件类型划分。用 Vite 创建项目非常快,执行 npm create vite@latest 即可生成基础骨架,再根据需要补充 ESLint、Prettier、husky 等规范工具。
npm create vite@latest hcm-system -- --template vue-ts cd hcm-system npm install npm install pinia vue-router element-plus
目录结构建议按模块聚合,把人事档案、薪酬、绩效等业务域各自收敛到一个文件夹内,公共能力下沉到 shared 层:
src/ ├── api/ # 接口层,按业务域拆分 │ ├── employee.ts │ └── payroll.ts ├── modules/ # 业务模块,按域聚合 │ ├── employee/ │ │ ├── views/ │ │ ├── components/ │ │ └── routes.ts │ └── performance/ ├── composables/ # 组合式函数 │ ├── usePermission.ts │ └── useDict.ts ├── router/ ├── stores/ └── shared/
这种结构最大的好处是支持模块级懒加载和独立维护。当薪酬模块需要重构时,开发者只需关注 modules/payroll 目录,不会牵一发动全身。同时配合路由懒加载,首屏体积能显著下降,对于功能上百个页面的人才管理系统来说尤为关键。
二、权限路由与动态菜单的实现
人才管理系统的权限颗粒度通常到按钮级别。比如 HR 主管能审批调薪,普通 HR 只能提交申请。Vue 3 中推荐用路由守卫加自定义指令的组合方案。路由元信息中声明权限标识:
// modules/employee/routes.ts
export default [
{
path: '/employee',
component: () => import('./views/EmployeeList.vue'),
meta: {
title: '员工档案',
roles: ['hr_manager', 'hr_specialist']
}
}
]
登录后从后端拉取用户角色与权限点,存入 Pinia,再在全局路由守卫里做拦截判断:
router.beforeEach(async (to) => {
const userStore = useUserStore()
if (!userStore.token) {
return { path: '/login', query: { redirect: to.fullPath } }
}
if (!userStore.routes.length) {
await userStore.fetchRoutes() // 拉取有权限的菜单
userStore.routes.forEach((r) => router.addRoute(r))
return to.fullPath // 重新进入以匹配动态路由
}
})
按钮级权限则用一个自定义指令实现,模板里写 v-permission="'salary:approve'",指令内部检查权限点不存在时移除该元素。相比 v-if 判断,指令方式让权限逻辑集中管理,页面代码更干净。需要注意的是,前端权限控制只是体验层面的拦截,真正的安全必须由后端接口校验兜底,这一点在涉及薪酬数据的人才管理系统里尤其不能省。
三、表单配置化:解决人才系统里最繁重的工作
人事系统里表单数量惊人:入职登记表、转正评估表、绩效面谈表、离职面谈表,加起来往往上百个。如果每个表单都手写一遍,工作量巨大且难以维护。配置化表单是工程化程度的重要体现:把表单结构描述成 JSON,由统一的渲染组件生成界面。
// 表单配置示例:员工转正评估表
export const confirmFormSchema = [
{ field: 'name', label: '姓名', type: 'input', disabled: true },
{ field: 'dept', label: '部门', type: 'select',
options: () => getDeptList() },
{ field: 'score', label: '试用期评分', type: 'rate', max: 5,
rules: [{ required: true, message: '请打分' }] },
{ field: 'comment', label: '评语', type: 'textarea', maxlength: 500 }
]
渲染组件扫描配置,动态生成对应的表单项,并通过 h 函数或动态组件完成渲染。这套方案的好处在于:新增表单只需加一份配置文件,后端甚至可以下发配置实现动态表单;缺点是渲染组件本身的复杂度较高,需要处理好联动、校验、默认值等细节。建议渲染层不要过度追求大而全,优先覆盖 input、select、date、rate、upload 这几个高频类型,冷门类型允许业务方传自定义组件兜底。
另一个值得做的工程化封装是字典管理。人事系统里充满枚举值:员工状态、合同类型、学历、职级。用 useDict 组合式函数统一缓存和获取,避免每个页面重复请求:
// composables/useDict.ts
const dictCache = new Map()
export async function useDict(type: string) {
if (!dictCache.has(type)) {
dictCache.set(type, await api.getDict(type))
}
return dictCache.get(type)
}
四、构建优化与长期维护
系统页面越多,构建产物越要精细控制。首先开启路由级懒加载,让每个业务模块单独成包;其次配置 Vite 的 manualChunks,把 element-plus、echarts 这类大依赖拆成独立 chunk,利用浏览器缓存;最后对图片和图标做按需引入,Element Plus 配合 unplugin-vue-components 可以自动完成组件按需注册,无需手动 import。
长期维护方面,建议尽早接入 TypeScript 严格模式和接口层的类型契约。人才管理系统的字段经常变更,如果接口返回类型没有约束,字段改名后页面报错只能靠人肉排查。可以在 CI 中跑 vue-tsc --noEmit 做类型检查,配合 husky 在提交前执行 lint,把问题挡在合并之前。此外单元测试优先覆盖权限逻辑、薪酬计算等核心纯函数,页面层测试投入产出比一般,可以后置。
总结来看,用 Vue 3 开发 UltiPro 这类人才管理系统,核心是三件事:清晰的模块化目录、可靠的权限体系、可复用的配置化表单。工程化不是一次性工作,而是随着业务增长持续演进的过程,前期在架构上多花一周,后期维护能省下数月时间。