对于跨区域用工的企业,HR 系统往往要同时处理多国劳动合同、不同币种薪资、跨时区协作以及各地数据合规要求。Remote.com 把这一复杂场景做成了标准产品,但很多企业仍有自建需求。借助 Vue 3 的工程化能力,我们可以把这类平台拆解成可维护的模块,从项目初始化、状态管理、国际化、接口层到部署逐步落地。

一、用 Vite 建立可维护的工程基础
全球团队 HR 平台的前端工程量不小,涉及员工档案、合同模板、审批流、报表等模块。如果一开始不做工程化约束,后续迭代很容易出现路径混乱、代码风格不一、类型错误难以追踪的问题。Vite 作为 Vue 3 官方推荐的构建工具,启动速度快、配置简洁,配合 TypeScript 能显著提升开发体验。项目初始化时建议直接使用 create-vue 脚手架,选择 TypeScript、ESLint、Prettier 等选项,避免手动拼装带来的版本兼容问题。
路径别名是大型项目的基础配置之一。把 src 目录映射为 @ 前缀,可以让深层组件引用公共模块时不用写一长串相对路径。环境变量管理同样重要,不同国家的 API 地址、合规开关、调试标志都可以放在 .env 文件中,通过 import.meta.env 读取。下面是 vite.config.ts 的核心配置示例:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
server: {
port: 5173
}
});
配置好路径和环境变量后,还需要把 lint 和格式化命令固定到 package.json 中。这样每个开发者提交前都能执行统一检查,避免代码审查时陷入格式争论。下面是一个可用的脚本片段:
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"lint": "eslint src --ext .ts,.vue",
"format": "prettier --write \"src/**/*.{ts,vue,json}\""
}
这套基础配置的目的不是堆砌工具,而是让团队在跨国协作场景下保持一致的代码质量。尤其当 HR 平台需要频繁调整字段和流程时,类型检查能提前暴露很多低级错误。
二、设计全球员工数据模型与 Pinia 状态管理
全球团队 HR 的核心数据远比普通员工表复杂。每位员工可能拥有不同国家的合同、不同币种的薪资、不同的时区偏好,甚至同一人在不同阶段出现多种雇佣关系。把这类数据直接散落在组件里会导致状态难以追踪,接口重复请求也频繁发生。因此需要先在 TypeScript 层定义清晰的数据模型,再把跨页面共享的状态放入 Pinia store 统一管理。
员工模型至少应包含身份、地区、时区、币种和合同类型字段。下面给出一个基础接口定义:
interface Employee {
id: string;
fullName: string;
countryCode: string;
timezone: string;
currency: string;
contractType: 'fulltime' | 'contractor';
startDate: string;
status: 'active' | 'onboarding' | 'offboarding';
}
Pinia 的优势在于可以按业务域拆分 store,并利用 actions 封装请求和缓存逻辑。员工 store 不需要每次进入页面都重新拉取全量数据,可以设置一个短时缓存,减少后端压力。下面是一个带缓存机制的员工 store:
import { defineStore } from 'pinia';
export const useEmployeeStore = defineStore('employees', {
state: function () {
return {
list: [] as Employee[],
loading: false,
lastFetchAt: 0
};
},
actions: {
async fetchEmployees(force: boolean) {
const now = Date.now();
if (!force && now - this.lastFetchAt < 60 * 1000) {
return;
}
this.loading = true;
try {
const data = await getEmployees();
this.list = data;
this.lastFetchAt = Date.now();
} finally {
this.loading = false;
}
}
}
});
除了员工主数据,合同、薪资、假期等域也需要独立 store。这样不同国家的合规规则变化时,只需要调整对应 store 的过滤逻辑,而不会影响其他模块。状态管理分离后,组件层只负责展示和交互,可测试性也大幅提升。
三、国际化与核心组件封装
面向全球团队的 HR 平台必须支持多语言界面。vue-i18n 是 Vue 3 生态中最成熟的国际化方案。项目中可以把英文作为默认语言,中文、德文、西班牙文等作为扩展语言包。配置时开启 legacy: false 以使用组合式 API 风格,这样在 setup 语法中调用 useI18n 更加自然。下面是一个最小化的 i18n 初始化示例:
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import zh from './locales/zh.json';
const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'zh',
messages: {
en: en,
zh: zh
}
});
export default i18n;
时区选择器是全球团队 HR 平台的高频组件。员工在入职时需要设置自己的时区,HR 在安排跨国会议时也要参考不同地区的工作时间。把时区列表抽成组合式函数,可以让多个页面复用同一份数据源。下面是一个简单的 useTimezones 函数:
import { computed } from 'vue';
export function useTimezones() {
const zones = [
'UTC',
'Europe/London',
'America/New_York',
'Asia/Shanghai',
'Asia/Tokyo'
];
const options = computed(function () {
return zones.map(function (zone) {
return { label: zone, value: zone };
});
});
return { options };
}
动态表单是另一个难点。不同国家对员工入职工资单、社保信息、税务编号的要求完全不同。可以通过一个渲染函数根据 countryCode 返回不同的表单控件集合,避免为每个国家写一套独立组件。下面给出一个简化版示例:
import { h } from 'vue';
export function renderContractFields(countryCode: string) {
const fields = [];
if (countryCode === 'DE') {
fields.push(h('input', { type: 'text', placeholder: 'Tax ID' }));
} else {
fields.push(h('input', { type: 'text', placeholder: 'Social Security Number' }));
}
return fields;
}
组件封装的关键是保持数据源稳定、接口清晰。时区选择器和动态表单如果设计得当,后续增加新国家时只需补充配置,无需改动使用方页面。
四、接口层与合规权限控制
全球 HR 数据涉及大量敏感信息,前端必须统一 API 请求层,把认证、错误处理、数据脱敏等逻辑集中管理。使用 axios 创建实例并在拦截器中注入 token 是常见做法。下面是一个 request.ts 示例:
import axios from 'axios';
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 15000
});
request.interceptors.request.use(
function (config) {
const token = window.localStorage.getItem('hr_token');
if (token) {
config.headers.Authorization = 'Bearer ' + token;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
request.interceptors.response.use(
function (response) {
if (response.data && response.data.code !== 0) {
return Promise.reject(new Error(response.data.message));
}
return response.data;
},
function (error) {
if (error.response && error.response.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default request;
合规权限控制同样重要。不同国家的 HR 法规要求不同字段的可见性和编辑权限。前端可以根据用户角色和员工 countryCode 做字段级过滤,例如德国合同显示税务编号,美国合同显示社会安全号码,但不能把税务编号发给无权限的 HR。这类逻辑可以放在 store 的 getter 或专门的权限模块中,避免散落在组件里。
另一个容易被忽略的问题是数据最小化展示。列表页默认只返回必要字段,敏感字段需要二次确认或额外权限才能查看。配合后端接口的角色校验,前端可以进一步降低误操作风险。统一请求层可以在这里承担拦截和脱敏职责。
五、性能优化与部署策略
全球团队 HR 平台如果同时服务多个地区的用户,首屏加载速度会直接影响体验。Vue 3 项目可以通过路由懒加载把每个页面拆成独立的 chunk,按需加载。下面是一个使用普通函数实现懒加载的路由配置:
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/employees',
component: function () {
return import('@/views/Employees.vue');
}
},
{
path: '/contracts',
component: function () {
return import('@/views/Contracts.vue');
}
}
];
const router = createRouter({
history: createWebHistory(),
routes: routes
});
export default router;
构建产物部署后,还可以通过 CDN 和缓存策略进一步优化。静态资源使用长期缓存,HTML 文件使用短缓存或不缓存,这样版本更新时用户能及时拿到新代码。下面是一段 nginx 配置片段:
location /assets/ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
}
CI/CD 方面,建议在合并到主分支时自动执行类型检查、lint、单元测试和构建。构建成功后把 dist 目录上传到静态托管服务,再触发部署通知。这样全球团队 HR 平台的迭代可以保持高频且稳定,避免因为一次部署失误影响多地业务运行。
当工程化基础、状态管理、国际化、接口层和部署策略都落地后,一个可维护的全球团队 HR 平台就能逐步成型。实际项目中还需要根据具体业务继续补充审计日志、消息通知和多租户隔离等能力,但上述架构已经提供了稳定的扩展骨架。