fix(mobile): distinguish auth failure from HR mismatch on position sync page

问题:登录态失效(token 冻结/无效,后端返 401)时,同步岗位页把认证错误
误显示成「人力系统中未匹配到当前手机号的在职岗位,请联系 HR」,员工被
困在该页无路可退。

修复:
- api.ts: 新增 ApiError(statusCode/bizCode) 类型化错误与 isAuthApiError,
  readPayload/parsePayloadText/XHR 路径统一抛出带状态码的错误,消息不变
- position.ts: fetchOrgPositionResult 返回四态判别结果
  matched/unmatched/unauthorized/error;hydratePositionFromOrg 行为不变
- position/index.vue: 按四态分支处理——
  · unauthorized -> 清除登录态并自动回登录页(保留 redirect)
  · error -> 提示「网络异常…请重试」,保留重试按钮
  · unmatched -> 才显示联系 HR 文案
  并新增「退出重新登录」链接(确认弹窗后清登录态回登录页),
  避免任何异常场景下用户被困无退出路径

验证(构建产物 + 生产 API,Playwright 五场景全过,截图见
.data/visual-audit-20260718/position-fix/):
S1 固定码真实登录->今日页(保洁岗匹配) S2 无效token->自动回登录
S3 断网->网络异常提示留在本页 S4 有效token->自动通过
S5 退出链接->确认->回登录且登录态已清

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 03:39:20 +08:00
co-authored by Claude Fable 5
parent 3baed336c7
commit 0ec0e585f7
3 changed files with 94 additions and 16 deletions
+50 -6
View File
@@ -27,6 +27,10 @@
<button class="confirm-button" :disabled="saving" @click="autoFillFromOrg">
{{ saving ? '同步中…' : '重新同步岗位' }}
</button>
<view class="logout-row">
<text class="logout-link" @click="logoutAndRelogin">退出重新登录</text>
</view>
</view>
</template>
@@ -34,12 +38,13 @@
import { computed, ref } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import {
clearAuth,
getAuth,
isLoggedIn,
rememberLoginRedirect,
type UserPosition
} from '@/services/auth';
import { goToMobileTarget, hydratePositionFromOrg } from '@/services/position';
import { fetchOrgPositionResult, goToMobileTarget } from '@/services/position';
import { resetPageScroll } from '@/services/navigation';
const redirect = ref('/pages/user/today/index');
@@ -52,22 +57,49 @@ const finish = () => {
goToMobileTarget(redirect.value || '/pages/user/today/index');
};
const goLogin = () => {
rememberLoginRedirect(redirect.value);
uni.redirectTo({ url: '/pages/auth/login/index' });
};
const logoutAndRelogin = () => {
uni.showModal({
title: '退出重新登录',
content: '将清除当前登录状态并返回登录页,确认退出?',
confirmText: '退出',
cancelText: '再想想',
success: (res) => {
if (!res.confirm) return;
clearAuth();
goLogin();
}
});
};
const autoFillFromOrg = async () => {
if (saving.value) return;
if (!isLoggedIn()) {
rememberLoginRedirect(redirect.value);
uni.redirectTo({ url: '/pages/auth/login/index' });
goLogin();
return;
}
saving.value = true;
message.value = '';
try {
const officialPosition = await hydratePositionFromOrg();
if (officialPosition) {
selectedPosition.value = officialPosition;
const result = await fetchOrgPositionResult();
if (result.status === 'matched') {
selectedPosition.value = result.position;
finish();
return;
}
if (result.status === 'unauthorized') {
clearAuth();
goLogin();
return;
}
if (result.status === 'error') {
message.value = '网络异常,暂时没有同步到岗位。请检查网络后点击「重新同步岗位」重试。';
return;
}
message.value = '人力系统中未匹配到当前手机号的在职岗位,请联系 HR 核对手机号、在职状态和岗位信息。';
} finally {
saving.value = false;
@@ -208,4 +240,16 @@ onShow(() => {
.confirm-button[disabled] {
opacity: 0.45;
}
.logout-row {
margin-top: 22px;
text-align: center;
}
.logout-link {
color: #8a93a0;
font-size: 15px;
text-decoration: underline;
padding: 10px 14px;
}
</style>