Justworks 这类 PEO(专业雇主组织)平台与普通后台最大的不同,是它必须同时处理三方关系:雇员、客户企业和监管机构。前端不仅要展示数据,还得把工资计算规则、税务报送状态、合规文件审核等业务动作固化成可追踪的流程。Vue 3 的响应式系统和组合式 API 很适合按领域拆分这些逻辑,避免把所有判断写进组件。围绕领域建模、动态规则和审计合规三类工程化实践,可以逐步搭建稳定可维护的 PEO 前端。

用领域模型和 Pinia 拆解 PEO 核心数据
PEO 平台的前端工程化首先要解决的不是页面布局,而是领域概念的边界。雇员、雇佣记录、工资批次、税务档案、合规案例这些对象如果只用普通对象传来传去,很容易出现同一字段在不同页面含义不一致的问题。Vue 3 项目里可以用 TypeScript 接口把这些概念固定下来,并通过 Pinia 的 setup store 组织状态变更。
下面是一组精简的领域类型。雇员和合规案例是 PEO 业务中频繁交互的两个核心模型,明确定义字段和联合类型,可以让后续的薪资计算、审计展示少很多防御性判断。
export interface Employee {
id: string;
legalName: string;
employmentType: 'W2' | '1099';
state: string;
department: string;
onboardingStatus: 'pending' | 'active' | 'terminated';
}
export interface ComplianceCase {
id: string;
type: 'tax' | 'insurance' | 'labor';
status: 'open' | 'review' | 'resolved';
dueDate: string;
assignee: string;
}
有了类型之后,Pinia 的 setup store 可以集中管理状态变更,而不是把修改逻辑散落在组件内部。这样做的好处是,当企业客户需要批量导入员工,或者合规专员批量更新案例状态时,可以直接调用 store 中的方法,保证状态修改遵循同样的规则。下面这个示例展示了员工新增和合规案例关闭操作。
import { defineStore } from 'pinia';
import { ref } from 'vue';
import type { Employee, ComplianceCase } from '@/types/domain';
export const usePEOStore = defineStore('peo', () => {
const employees = ref<Employee[]>([]);
const cases = ref<ComplianceCase[]>([]);
function addEmployee(emp: Employee) {
employees.value.push(emp);
}
function resolveCase(id: string) {
const target = cases.value.find(item => item.id === id);
if (target && target.status !== 'resolved') {
target.status = 'resolved';
}
}
return { employees, cases, addEmployee, resolveCase };
});
这种拆分方式还方便后续与 API 层对接。比如在获取到后端返回的原始数据后,先在 composable 中做一次字段清洗和默认值补齐,再提交给 store。这样组件只面向已经整理好的领域对象,不会因为接口返回结构变化而大面积修改 UI 代码。
动态表单与规则引擎支撑合规要求
PEO 平台的合规表单往往不是固定不变的。不同州的税务登记要求不同,不同行业的工伤保险分类也不同,同一客户在不同雇佣阶段需要采集的信息也会变化。如果为每一种情况手写一个表单组件,维护成本会迅速失控。更工程化的做法是把表单结构定义成 schema,由统一渲染器根据 schema 生成字段和校验规则。
在 Vue 3 中,可以结合 vee-validate 和 zod 来处理这类动态表单。字段类型、占位提示、校验规则、默认值都放在一个数组或对象里,组件通过 v-for 渲染。这样新增一个州的合规字段,只需要增加一段配置,不需要改渲染逻辑。下面是一个 W2 员工信息表单的校验 schema。
import { z } from 'zod';
export const w2FormSchema = z.object({
legalName: z.string().min(2, '姓名不能少于 2 个字符'),
ssn: z.string().regex(/^\d{3}-\d{2}-\d{4}$/, '请输入有效的 SSN'),
state: z.enum(['CA', 'NY', 'TX', 'WA']),
salary: z.number().positive('薪资必须大于 0'),
});
export type W2Form = z.infer<typeof w2FormSchema>;
在模板中,字段的渲染需要保持足够抽象。下面是一个简化版的动态表单结构,重点展示如何把标签、输入控件和错误提示拼接起来。实际项目里还可以根据字段类型加入日期选择器、下拉框、多选框等不同控件。
<template>
<form @submit="onSubmit">
<div v-for="field in formFields" :key="field.name" class="form-item">
<label>{{ field.label }}</label>
<input v-model="form[field.name]" :type="field.type" />
<span class="error">{{ errors[field.name] }}</span>
</div>
<button type="submit">提交合规信息</button>
</form>
</template>
除了字段校验,合规规则本身也需要沉淀成可测试的纯函数。例如判断某员工是否满足加州最低工资要求、判断某项保险是否在截止日期前提交。把这些规则从组件中抽出来,放到 utils 或 domain 目录中,既可以单独写单元测试,也方便在实时计算和提交前验证两处复用。规则引擎不一定要引入重量级库,只要保证输入输出明确、不依赖组件状态,就能大幅降低合规逻辑出错的概率。
权限、审计与 CI 门禁让合规落地可验证
合规平台中很多操作需要严格限制访问范围。普通员工可以查看自己的工资单,但不能查看同部门其他人的薪资;客户管理员可以查看本公司的合规案例,但不能修改已归档的税务文件。Vue 3 工程化中,这类控制通常分成三层:路由守卫、按钮级指令、API 层权限校验。前端只能作为体验层面的拦截,最终安全必须由后端保证,但前端的拦截能避免大量无效请求和误操作。
路由守卫适合处理页面级权限。比如只有具备 compliance:review 权限的用户才能进入合规审核页面。我们可以在路由 meta 中声明所需权限,再通过全局守卫判断。下面示例展示了如何做一个可复用的 complianceGuard。
import type { NavigationGuard } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
export const complianceGuard: NavigationGuard = (to) => {
const auth = useAuthStore();
const required = to.meta.permission as string | undefined;
if (required && !auth.hasPermission(required)) {
return { name: 'forbidden' };
}
return true;
};
按钮级权限则可以用自定义指令实现。比如导出员工数据、批量调整薪资这类高风险操作,需要在界面上隐藏或禁用入口。下面是一个 v-permission 指令的简化实现,它会根据当前用户权限移除 DOM 节点。
import type { Directive } from 'vue';
import { useAuthStore } from '@/stores/auth';
export const vPermission: Directive<HTMLElement, string> = {
mounted(el, binding) {
const auth = useAuthStore();
if (!auth.hasPermission(binding.value)) {
el.parentNode?.removeChild(el);
}
},
};
审计日志是合规平台的最后一道工程化保障。前端除了展示操作记录,还要保证日志只读、不可在客户端篡改。可以在提交关键操作前要求二次确认,并在请求成功后展示不可编辑的审计编号。更严格的做法是把操作日志与后端签名关联,前端只负责展示,不做任何修改逻辑。与此同时,CI 门禁应当把类型检查和规则检查作为合并的前置条件。运行 vue-tsc 做类型检查,再执行 ESLint 和单元测试,可以避免把显而易见的类型错误或规则破坏带进主分支。这样每一步操作都有据可查,每一次发布前都有自动化验证,合规才不会只停留在口头要求。