全球雇佣平台 Atlas 需要同时支撑多国合同签署、多币种薪酬计算、多语言界面切换以及复杂的合规审批流。这类业务落到前端层面,意味着大量的业务模块、频繁的跨团队协作和严苛的首屏性能要求。如果继续沿用单应用、单仓库、手工配置的传统模式,项目很快就会陷入构建缓慢、依赖冲突、发布互相阻塞的泥潭。本文结合 Vue 3 生态,完整梳理 Atlas 平台的工程化实践。

一、项目架构设计与 Monorepo 组织
Atlas 平台的第一步是确定整体架构。我们采用 Monorepo 管理所有前端资产,工具选择 pnpm workspace。相比 npm 或 yarn 的 workspace,pnpm 的硬链接机制能显著减少 node_modules 的磁盘占用,其严格的依赖隔离也避免了幽灵依赖问题——在多团队协作场景下,谁能引用什么包必须是显式声明,否则一次底层组件库升级就可能引发连锁故障。
目录结构按「应用、共享包、工具配置」三层划分,业务应用包括雇主端、雇员端、运营后台三个独立入口,共享层沉淀公共组件、hooks、工具函数与设计规范。这样的划分让每个应用的构建产物足够小,也让复用逻辑有了唯一归属。
atlas-frontend/ ├── apps/ │ ├── employer/ # 雇主端(合同、薪酬、审批) │ ├── employee/ # 雇员端(入职、薪资单、个税) │ └── admin/ # 运营后台(合规审核、配置) ├── packages/ │ ├── ui/ # 基于无头组件二次封装的业务组件库 │ ├── composables/ # 跨端复用的组合式函数 │ ├── i18n/ # 多语言词条与格式化规则 │ ├── api-client/ # 接口层封装(含多环境网关) │ └── shared-config/ # eslint、tsconfig、vite 预设 ├── pnpm-workspace.yaml └── turbo.json
构建编排交给 Turborepo。它基于任务依赖图做增量构建与缓存,只有真正变化的包才会重新构建。在接入 CI 后,一次全量构建从 11 分钟压缩到 4 分钟左右,本地开发时的任务并行也明显加快。需要注意的是,缓存 key 依赖文件哈希,务必把生成的类型声明文件纳入 outputs 声明,否则下游包可能拿到过期的类型。
二、Vite 构建优化与环境管理
Vue 3 天然契合 Vite,但中大型平台仍需精细调优。开发环境的主要矛盾是依赖预构建:Employer 端引入了 echarts、lodash-es、dayjs 等几十个依赖,冷启动时 optimizeDeps 扫描会拖慢首屏。我们把稳定的三方库显式写入 optimizeDeps.include,并锁定预构建缓存目录到仓库外的固定路径,冷启动从 8 秒降到 2 秒以内。
生产构建方面,三个关键配置如下:手动分包把体积大的依赖拆成独立 chunk,配合 gzip 预压缩;rollup 的 visualizer 插件帮助定位体积异常的模块;sourcemap 只在 staging 环境生成,避免生产泄露源码。
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig(({ mode }) => ({
plugins: [vue()],
resolve: {
alias: { '@': '/src' }
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia'],
charts: ['echarts'],
utils: ['lodash-es', 'dayjs']
}
}
},
sourcemap: mode === 'staging'
},
optimizeDeps: {
include: ['echarts', 'lodash-es', 'dayjs', 'axios']
}
}))
环境变量管理容易被忽视却极其重要。Atlas 对接了沙箱、预发、生产三套网关,加上不同国家的数据合规要求,我们约定 .env.development、.env.staging、.env.production 三份基础配置,敏感变量统一走 CI 注入而非入库。所有自定义变量必须以 VITE_ 为前缀,并通过一份 env.d.ts 提供 TypeScript 提示,防止拼错变量名引发的线上事故。
三、国际化与多币种的工程化落点
全球雇佣平台绕不开 i18n。词库量在 Atlas 中已超过一万条,如果全部打包进主包,体积不可接受。方案是「语言包按需异步加载 + 词条分级」:公共框架词条随主包发布,业务模块词条随路由懒加载,长尾的合规文案则由服务端下发。这样首屏只携带用户当前语言的基础词条。
// router/i18n-guard.ts 按路由懒加载语言包
const loadedModules = new Set<string>()
router.beforeEach(async (to) => {
const mod = String(to.meta.i18nModule ?? 'common')
if (loadedModules.has(mod)) return true
const dict = await import(`../locales/${locale}/${mod}.json`)
i18n.global.mergeLocaleMessage(locale, dict.default)
loadedModules.add(mod)
return true
})
多币种比多语言更棘手,因为汇率、符号位置、小数位数在不同国家规则不同(日元无小数、部分货币符号后置)。我们封装统一的 formatMoney 工具,内部基于 Intl.NumberFormat,传入币种与地区即可输出规范格式,禁止业务代码手工拼接货币符号。日期与姓名展示同理:中东地区姓名顺序、泰国佛历日历都要通过 Intl 相关 API 处理,而不是写死规则。
时区是薪酬模块的高频坑。薪资单的「发放日」必须以雇员所在时区计算,而审批记录的时间戳要用雇主时区展示。所有接口时间统一传 ISO 8601 带时区偏移的字符串,前端展示层再按当前视图时区格式化,任何直接用字符串截取日期的做法都会被 code review 拦下。
四、状态管理、组件封装与质量保障
状态选型上,Atlas 全线使用 Pinia。薪酬计算表单这类复杂跨组件状态交给 Pinia 的 store 管理,配合 storeToRefs 保持响应式解构;纯展示组件则通过 defineModel 与 v-model 双向绑定,避免状态无谓上提。Pinia 的模块天然按需注册,与路由懒加载配合良好,不会像 Vuex 那样需要集中注册所有模块。
组件库 packages/ui 采用「无头逻辑 + 业务皮肤」策略:交互逻辑基于公司内部的无头 hooks 实现,样式令牌(颜色、圆角、间距)全部走 CSS 变量,雇主端与运营后台共享同一套逻辑但视觉风格独立。表单类组件如上传合同、签署流程组件,因为强依赖后端契约,统一封装时把请求层也一并内置,业务方只需传业务参数。
<template>
<AtlasSignFlow
contract-id="ct_9001"
:signers="signers"
@completed="onSignDone"
/>
</template>
<script setup lang="ts"">
import { ref } from 'vue'
const signers = ref([
{ role: 'employer', country: 'DE' },
{ role: 'employee', country: 'BR' }
])
const onSignDone = (result: { contractId: string }) => {
// 签署完成后刷新合同状态
}
</script>
质量保障依赖四道防线:ESLint 与 Stylelint 在提交前通过 husky + lint-staged 强制执行;TypeScript 开启 strict 模式,接口类型由后端 OpenAPI 自动生成;单元测试覆盖 composables 与金额计算等核心纯函数,使用 Vitest;端到端测试用 Playwright 跑通「创建合同、多国签署、生成薪资单」主链路。CI 流水线按「lint → 类型检查 → 单测 → 构建 → e2e」串行执行,任一环节失败即阻断合并,保证主干始终可发布。
回过头看,Vue 3 的 Composition API 与 Vite 只是工程化的基础能力,真正决定平台可持续迭代的是边界划分:包之间依赖单向、契约靠类型驱动、公共能力有明确沉淀路径。当业务从三个国家扩展到三十个国家时,前端要做的只是新增语言包与合规模块,而不是重构架构,这才是工程化投入的回报所在。