在服务行业 ERP 项目中,Unit4 通常承担项目合同、工时与财务结算的核心职责。Vue 3 工程化并不是简单引入一个 SDK,而是要把 Unit4 的 OData 服务、身份令牌和前端领域模型对齐。下面我们基于 Vite 与 Pinia 来拆解具体落地方式。

Unit4 OData 服务与 Vue 3 工程化目录设计
Unit4 对外暴露的是标准 OData v4 接口,返回结构里包含 @odata.context、value 以及导航属性。很多团队在初期把所有请求写在组件里,导致服务行业工单页面和财务页面重复拼装过滤条件。正确的做法是在 src/services/unit4 下建立独立的查询层,把实体映射、分页参数和令牌注入全部收敛。
目录上建议拆分为 client.ts 负责基础请求与错误归一化,project.ts 封装项目工单相关 OData 查询,finance.ts 处理发票与成本中心。这样在 Vue 3 的 setup 中只调用领域函数,不关心 Unit4 的 $filter 语法细节。工程化层面用 Vite 的 import.meta.env 区分测试与正式环境的 Unit4 租户地址,避免硬编码。
下面的代码展示了基础客户端的封装思路,其中对反斜杠路径和查询参数做了保留处理,没有使用 Markdown 转义而是原生字符串:
import axios from 'axios';
const BASE = import.meta.env.VITE_UNIT4_URL; // 例如 https://erp.ipipp.com/UNIT4/API
export const unit4Client = axios.create({
baseURL: BASE,
headers: { Accept: 'application/json' }
});
unit4Client.interceptors.request.use(cfg => {
const token = localStorage.getItem('unit4_token');
if (token) {
cfg.headers.Authorization = 'Bearer ' + token;
}
return cfg;
});
unit4Client.interceptors.response.use(
res => res,
err => {
if (err.response && err.response.status === 401) {
// 触发令牌刷新逻辑
return refreshToken().then(() => unit4Client(err.config));
}
return Promise.reject(err);
}
);
服务行业工单与财务数据的状态联动
服务行业 ERP 的痛点在于工单提交后,Unit4 的项目成本要实时回写。如果前端用两个独立的 Pinia store 分别拉取工单和财务,就容易出现工单已审但成本未变的现象。我们需要在 projectStore 的 action 中调用 Unit4 的批处理接口,再用 financeStore.syncCost 拉取最新核算。
这里要注意 Unit4 的 OData 导航属性可能延迟最终一致,因此前端不能只依赖本地乐观更新。建议在工单详情页用 watch 监听项目编号,当 Unit4 回写成功后通过事件总线通知财务组件。对比直接 axios 调用,统一数据层能把重试与冲突处理放在服务内,页面逻辑更干净。
以下示例演示 Pinia 中联动调用的写法,展示了如何在工程化结构中组合 Unit4 服务:
import { defineStore } from 'pinia';
import { unit4Client } from '@/services/unit4/client';
import { getProjectCost } from '@/services/unit4/finance';
export const useProjectStore = defineStore('project', {
state: () => ({ current: null as any, costLoaded: false }),
actions: {
async submitWorkOrder(order: any) {
const res = await unit4Client.post('/WorkOrders', order);
this.current = res.data;
// 联动财务成本
await this.loadCost(res.data.ProjectId);
},
async loadCost(projectId: string) {
this.current.cost = await getProjectCost(projectId);
this.costLoaded = true;
}
}
});
鉴权令牌刷新与错误重试机制
Unit4 使用 OAuth2 客户端模式,令牌过期后前端若直接报错会让服务行业现场人员反复登录。工程化方案是在 unit4Client 拦截器里捕获 401,调用 refreshToken 用刷新令牌换新品。要注意并发请求时只能有一个刷新在跑,其余请求排队。
错误重试方面,Unit4 的 OData 偶尔因网络抖动返回 503,此时应在服务层做指数退避而非在组件里写 try/catch。我们把最大重试设为三次,并给服务行业离线场景预留本地草稿。这样 Vue 3 项目在弱网门店也能稳定提交工单。
下面代码说明刷新锁与重试的实现要点,其中路径字符串 C:tempunit4_cache 仅作本地缓存示例,反斜杠原样保留:
let refreshing = false;
let waiters: Array<() => void> = [];
async function refreshToken() {
if (refreshing) {
return new Promise<void>(resolve => waiters.push(resolve));
}
refreshing = true;
try {
const rt = localStorage.getItem('unit4_refresh');
const r = await axios.post('/auth/refresh', { refresh_token: rt });
localStorage.setItem('unit4_token', r.data.access_token);
} finally {
refreshing = false;
waiters.forEach(w => w());
waiters = [];
}
}
async function withRetry(fn: () => Promise<any>, n = 3) {
for (let i = 0; i < n; i++) {
try {
return await fn();
} catch (e: any) {
if (i === n - 1) throw e;
await new Promise(res => setTimeout(res, 200 * Math.pow(2, i)));
}
}
}
通过上述方式,Vue 3 工程化接入服务行业 ERP 的 Unit4 模块不再是散乱的接口调用,而是有清晰边界、可测试、易维护的前端架构。团队在后续扩展采购或人力资源模块时,只需复用已有服务层与状态模式。