fix(mobile): overnight audit round — IA cleanup, internal codes, exam guard

夜间自迭代体检(生产四tab+7子页,截图+console+接口+文案标志采集)
发现三类问题,本轮修复:

1. 信息架构(采纳用户建议):今日页「我的地图」四宫格三项与底部tab
   重复(问/练),仅学习中心/工作上报有增量 → 整块从今日页移除,
   「学习中心/我的训练概况/工作上报」下沉到「我」页新「常用功能」组
   (图标+标题+说明行,与我的积分同列);今日页保留小贴士条,
   页面高度显著缩短、不再与tab栏功能重复。

2. 内部编码泄漏:派发 reason 里的调度批次码(AUTO22-20260717
   PILOT-04)在今日「今日安排」、练页「最近练过」、学习中心
   「昨日错题重练」三处原样示人 → 新增 services/text.ts
   stripInternalCodes(剥前缀码保留人话,整行为码则回退默认文案),
   三处渲染点统一接入。

3. 功能不通:直接访问 /pages/user/exam/index(无id)会请求
   /api/aihr/mobile/exams/0 → 服务端500 → load() 增加 id 守卫,
   显示「请从学习中心·考试任务进入」且不再发请求。

同时:今日「打字问」描述改为不换行文案(原两行文案窄列截断)。

验证:本地7项断言全过;生产复核 map removed/code clean/
profile entries/exam guard/exams|0 not fired 全 true。
发布备份 h5-before-20260718055449。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 05:56:15 +08:00
co-authored by Claude Fable 5
parent 9d7d621300
commit a90f241def
6 changed files with 150 additions and 85 deletions
+7 -82
View File
@@ -46,7 +46,7 @@
</view>
<view class="voice-copy secondary-copy">
<text class="voice-title secondary-title">打字问</text>
<text class="voice-desc">输入问题获得解答</text>
<text class="voice-desc">输入文字提问</text>
</view>
</button>
</view>
@@ -87,7 +87,7 @@
<view class="section-bar" />
<text>今日安排</text>
</view>
<button class="section-link" @click="openTool('刻意练习')">
<button class="section-link" @click="goPractice">
<text>{{ scheduleCountLabel }}</text>
<text class="section-link-arrow">›</text>
</button>
@@ -134,30 +134,6 @@
</view>
<view class="section-card employee-card map-card">
<view class="section-head">
<view class="section-title">
<view class="section-bar" />
<text>我的地图</text>
</view>
</view>
<view class="map-grid">
<button
v-for="tile in mapTiles"
:key="tile.title"
class="map-tile"
:class="tile.tone"
@click="openTool(tile.title)"
>
<view class="map-symbol" :class="tile.tone">
<text>{{ tile.shortTitle }}</text>
</view>
<view class="map-copy">
<text class="map-title">{{ tile.title }}</text>
<text class="map-desc">{{ tile.desc }}</text>
</view>
<text class="map-arrow">›</text>
</button>
</view>
<button class="tip-card" @click="ask(primaryTipQuestion)">
<uni-icons class="tip-mark" type="help" size="22" color="#d98200" />
<text class="tip-label">小贴士</text>
@@ -165,13 +141,6 @@
<text class="tip-text">{{ tipBody }}</text>
<text class="tip-arrow">›</text>
</button>
<button class="tip-card" @click="openPracticeSummary">
<uni-icons class="tip-mark" type="staff" size="22" color="#e60012" />
<text class="tip-label">训练</text>
<text class="tip-divider">|</text>
<text class="tip-text">查看我的训练概况</text>
<text class="tip-arrow">›</text>
</button>
</view>
<button class="question-board-card employee-card" @click="openQuestions">
@@ -193,6 +162,7 @@ import { getMobileHome } from '@/services/home';
import { getPracticeAssignments } from '@/services/practice';
import { getSelectedPositionProfile, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { stripInternalCodes } from '@/services/text';
import { ensureEmployeePosition } from '@/services/position';
import { createPageRequestScope, currentAccountKey } from '@/services/request-scope';
@@ -213,32 +183,6 @@ const chipIcons = ['chatbubble', 'help', 'wallet'];
const chipIcon = (index: number) => chipIcons[index] || 'chatbubble';
const chipColor = (_index: number) => '#e60012';
const mapTiles = computed(() => [
{
title: '问老师傅',
shortTitle: '问',
desc: '随时提问\n快速解决问题',
tone: 'ask'
},
{
title: '刻意练习',
shortTitle: '练',
desc: '情景演练\n提升服务能力',
tone: 'practice'
},
{
title: '学习中心',
shortTitle: '学',
desc: '课程学习\n掌握专业知识',
tone: 'learn'
},
{
title: '工作上报',
shortTitle: '报',
desc: '日报/任务/问题\n及时上报反馈',
tone: 'report'
}
]);
const primaryAssignment = computed(() =>
assignments.value.find((item) => item.status === '待训练' && item.source !== 'daily')
@@ -289,7 +233,7 @@ const primaryScheduleDesc = computed(() => {
const item = primaryAssignment.value;
if (item) {
if (item.source === 'daily') return '提交后显示反馈与参考答案';
return item.reason || '学习投诉处理全流程与沟通话术';
return stripInternalCodes(item.reason || '') || '学习投诉处理全流程与沟通话术';
}
return home.value?.primary?.desc || '进入练习页查看今日任务';
});
@@ -370,10 +314,6 @@ const ask = (query: string) => {
uni.switchTab({ url: '/pages/user/sop/index' });
};
const openPracticeSummary = () => {
uni.setStorageSync(pendingToolKey, 'MY_PRACTICE_SUMMARY');
uni.switchTab({ url: '/pages/user/sop/index' });
};
const goLogin = () => {
rememberLoginRedirect('/pages/user/today/index');
@@ -400,26 +340,11 @@ const openBuilding = (title: string) => {
uni.navigateTo({ url: `/pages/common/building/index?title=${encodeURIComponent(title)}` });
};
const openTool = (title: string) => {
if (title.includes('问') || title.includes('SOP') || title.includes('sop')) {
goSop();
return;
}
if (title.includes('练') || title.includes('训练') || title.includes('开始')) {
uni.switchTab({ url: '/pages/user/practice/index' });
return;
}
if (title.includes('学') || title.includes('案例学习')) {
uni.navigateTo({ url: '/pages/user/learning/index' });
return;
}
if (title.includes('案例') || title.includes('素材') || title.includes('提交')) {
uni.navigateTo({ url: '/pages/user/cases/index' });
return;
}
openBuilding(title);
const goPractice = () => {
uni.switchTab({ url: '/pages/user/practice/index' });
};
onShow(() => {
const nextAccountKey = currentAccountKey();
if (visibleAccountKey && visibleAccountKey !== nextAccountKey) {