全球化的 HR 合规系统是一类非常典型的复杂中后台业务:它要同时支持几十个国家和地区的劳动法差异、多语言多时区、不同岗位的权限隔离,以及频繁变化的监管规则。前端如果缺乏工程化设计,很快就会陷入代码混乱、表单复制粘贴、校验逻辑四处散落的泥潭。本文以 Vue 3 全家桶为基础,围绕工程化实践展开,讲清楚如何把一个合规系统拆成可持续演进的架构,并给出关键环节的代码实现。

一、项目架构与工程化基础:从单体到模块化
合规系统的第一个特点是业务域多:员工档案、合同管理、薪酬合规、假勤规则、审计日志等模块相互独立又彼此关联。如果全部塞进一个 src 目录,随着国家维度的增加,文件数量会呈爆炸式增长。推荐的做法是采用 Monorepo 结构,把公共能力下沉到 packages,把国家特定的合规规则做成独立插件模块。
目录结构可以设计为:
hr-compliance/ ├── packages/ │ ├── shared/ # 公共组件与工具函数 │ ├── compliance-core/ # 合规规则引擎核心 │ └── country-packs/ # 各国合规规则包 │ ├── de/ # 德国:工作时长、最低工资 │ ├── cn/ # 中国:社保基数、加班上限 │ └── br/ # 巴西:13薪、FGTS ├── apps/ │ └── web/ # 主应用,基于 Vite └── pnpm-workspace.yaml
这种结构的核心价值在于:compliance-core 只定义规则接口,具体国家规则由 country-packs 按需注入。当某个国家的法规变化时,只需要发布对应国家包的新版本,主应用无需改动。Vite 的按需加载能力配合动态 import,还能让不同地区的用户只下载自己国家的规则代码,显著减小首屏体积。
在依赖管理上建议使用 pnpm workspace,并在 CI 中对各包独立跑类型检查与单元测试。合规类系统对回归风险极其敏感,一个国家规则的改动波及另一个国家是最常见的事故来源,包级别的隔离配合自动化测试能把这个风险压到最低。
二、多语言、多时区与合规文案管理
HR 合规系统的国际化不只是翻译界面,更难的是合规文案本身有法律效力。德国的员工同意条款、巴西的劳动通知、中国的入职声明,措辞必须与当地法务确认过的版本完全一致。直接把这些文案塞进 vue-i18n 的 JSON 文件会有两个问题:法务无法审阅代码仓库里的文件,文案变更要走完整的代码发布流程。
更好的方案是把合规文案放到独立的配置服务中,前端启动时拉取并注入 i18n。示例:
import { createI18n } from 'vue-i18n'
// 合规文案从远程配置中心拉取,法务修改后可热更新
async function loadComplianceMessages(locale) {
const res = await fetch(`/api/compliance-texts/${locale}`)
return res.json()
}
export async function setupI18n(locale) {
const messages = await loadComplianceMessages(locale)
return createI18n({
legacy: false,
locale,
fallbackLocale: 'en',
messages: { [locale]: messages }
})
}时区处理是另一个高频踩坑点。员工的入职日期、合同生效日、加班记录都必须以当地时区存储和展示,而审计日志又要求统一的 UTC 时间。推荐统一使用 Temporal API 或 date-fns-tz,在 Pinia 中维护一个全局的 regionStore,所有时间格式化函数强制传入该 store 中的时区,禁止在组件里直接调用原生 Date 的本地方法,从规范层面杜绝时区错乱。
三、动态表单与校验规则引擎:合规差异的核心战场
不同国家对员工信息的要求差异极大:德国需要税号与宗教税选项,巴西需要 CPF 与种族自申报字段,中国需要身份证与户籍类型。如果为每个国家写一套表单页面,维护成本会失控。正确的思路是配置驱动的动态表单:字段定义由后端的合规规则接口下发,前端只负责渲染与校验。
// 校验规则从国家包中获取,与字段配置分离
import { useCompliance } from '@hr/compliance-core'
const employeeSchema = {
country: 'de',
fields: [
{ key: 'tax_id', type: 'string', label: 'Tax Number', required: true },
{ key: 'birth_date', type: 'date', label: 'Birth Date', required: true,
validators: ['adult', 'workingAge'] },
{ key: 'salary', type: 'number', label: 'Monthly Salary', required: true,
validators: ['minWage'] }
]
}
const { validate } = useCompliance(employeeSchema.country)
const errors = await validate({ tax_id: '...', salary: 2100 })校验器注册机制让每个国家包可以扩展自己的规则,比如德国包注册 minWage 为 2024 欧元,巴西包则按联邦最低工资标准实现。前端使用 Vue 3 的 provide/inject 把校验器集合注入组件树,表单组件通过统一的 FieldRenderer 渲染,新增一个国家的成本就从开发一套页面降低到编写一份字段配置和一个规则包。
还要注意校验时机:合规系统通常需要在字段失焦时做轻校验、提交时做全量校验、保存草稿时跳过必填但保留格式校验。这些策略同样应该配置化,而不是硬编码在表单组件里,否则每个国家的交互差异又会把代码撕碎。
四、权限隔离、审计与可观测性
合规系统对权限的要求远超普通中后台。欧盟员工的数据受 GDPR 约束,HR 只能查看自己负责的法人实体下的员工;薪资数据需要单独授权;每一次敏感字段的查看都要留痕。前端层面,路由守卫配合 Pinia 的权限 store 是基础方案,但更关键的是数据层面的字段级脱敏:同一个员工详情页,无权限用户看到的手机号是脱敏后的,且请求参数中会携带字段可见性声明,由后端审计日志记录这次访问。
// 基于声明的字段访问,便于后端审计
export function useEmployeeView(employeeId) {
const { permissions } = usePermissionStore()
const requestedFields = ['name', 'department']
if (permissions.has('hr:salary:read')) {
requestedFields.push('salary')
}
return api.get(`/employees/${employeeId}`, {
params: { fields: requestedFields.join(',') }
})
}可观测性方面,建议在全局错误处理中对合规规则校验失败做分类上报,区分是用户输入问题还是规则配置错误。规则配置错误往往意味着某个国家的合规出了漏洞,这类告警的优先级应该高于普通前端异常。同时为关键操作(入职、解雇、合同变更)接入操作日志埋点,前端埋点必须包含操作人、法人实体、国家代码三个维度,方便事后追溯。
总结
全球 HR 合规系统的前端难点从来不是某个炫技的交互,而是如何用工程化手段管理爆炸式增长的规则差异。核心思路可以归纳为三条:用 Monorepo 做物理隔离,用配置驱动的表单与规则引擎做逻辑隔离,用声明式的权限与审计做合规兜底。Vue 3 的组合式 API、依赖注入与响应式系统恰好为这套方案提供了顺手的底层能力。只要架构方向正确,新增一个国家的支持成本可以稳定控制在配置与规则包的编写上,而不是重构整个系统。