feat(aihr): add employee mistake book audit
This commit is contained in:
@@ -68,6 +68,39 @@
|
||||
<view v-else class="empty">暂无今日任务</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loggedIn" class="panel mistake-panel">
|
||||
<view class="panel-head">
|
||||
<view>
|
||||
<text class="panel-title">错题本</text>
|
||||
<text class="panel-caption">从低分和红线回合中自动归集</text>
|
||||
</view>
|
||||
<button class="ghost-button compact-button" :disabled="mistakeLoading" @click="loadMistakes">
|
||||
{{ mistakeLoading ? '加载中' : '刷新' }}
|
||||
</button>
|
||||
</view>
|
||||
<view v-if="mistakes.length" class="mistake-list">
|
||||
<view v-for="item in mistakes" :key="item.id" class="mistake-row">
|
||||
<view class="mistake-copy">
|
||||
<view class="mistake-meta">
|
||||
<text class="mistake-cause">{{ item.cause }}</text>
|
||||
<text class="mistake-score">{{ item.score }} 分</text>
|
||||
</view>
|
||||
<text class="mistake-title">{{ item.scenarioName }}</text>
|
||||
<text class="mistake-desc">{{ item.time }} · {{ item.summary }}</text>
|
||||
<text class="mistake-status">再练状态:{{ item.retryStatus }}</text>
|
||||
</view>
|
||||
<button
|
||||
v-if="item.retryAssignmentId"
|
||||
class="secondary-button compact-button"
|
||||
@click="openMistake(item)"
|
||||
>
|
||||
去再练
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="!mistakeLoading" class="empty">完成训练后,低分和红线回合会自动出现在这里</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loggedIn" class="panel">
|
||||
<view class="panel-head">
|
||||
<text class="panel-title">岗位 SOP</text>
|
||||
@@ -145,9 +178,10 @@ import { onShow } from '@dcloudio/uni-app';
|
||||
import type {
|
||||
KnowledgeSearchResponse,
|
||||
PracticeAssignment,
|
||||
PracticeMistake,
|
||||
PracticePrepCard
|
||||
} from '@/types/api';
|
||||
import { getPracticeAssignments, getPracticePrepCard } from '@/services/practice';
|
||||
import { getPracticeAssignments, getPracticeMistakes, getPracticePrepCard } from '@/services/practice';
|
||||
import { searchKnowledge } from '@/services/knowledge';
|
||||
import { getSelectedPositionProfile, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||||
import { buildLearningTopics, selectLearningTopic } from '@/services/learning';
|
||||
@@ -161,10 +195,12 @@ const pendingSopQueryKey = 'aihr_sop_pending_query';
|
||||
const loggedIn = ref(false);
|
||||
const message = ref('');
|
||||
const assignments = ref<PracticeAssignment[]>([]);
|
||||
const mistakes = ref<PracticeMistake[]>([]);
|
||||
const prepCard = ref<PracticePrepCard | null>(null);
|
||||
const knowledgeResult = ref<KnowledgeSearchResponse | null>(null);
|
||||
const activeTopic = ref('岗前 SOP');
|
||||
const assignmentLoading = ref(false);
|
||||
const mistakeLoading = ref(false);
|
||||
const prepLoading = ref(false);
|
||||
const knowledgeLoading = ref(false);
|
||||
const positionProfile = ref(getSelectedPositionProfile());
|
||||
@@ -235,6 +271,19 @@ const loadAssignments = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadMistakes = async () => {
|
||||
if (!loggedIn.value) return;
|
||||
mistakeLoading.value = true;
|
||||
try {
|
||||
mistakes.value = await getPracticeMistakes();
|
||||
} catch (error) {
|
||||
mistakes.value = [];
|
||||
handleRequestError(error, '错题本加载失败');
|
||||
} finally {
|
||||
mistakeLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadPrepCard = async () => {
|
||||
if (!loggedIn.value) return;
|
||||
prepLoading.value = true;
|
||||
@@ -267,6 +316,7 @@ const loadPage = async () => {
|
||||
message.value = '';
|
||||
if (!loggedIn.value) {
|
||||
assignments.value = [];
|
||||
mistakes.value = [];
|
||||
prepCard.value = null;
|
||||
knowledgeResult.value = null;
|
||||
return;
|
||||
@@ -275,7 +325,7 @@ const loadPage = async () => {
|
||||
return;
|
||||
}
|
||||
refreshAuthState();
|
||||
await loadAssignments();
|
||||
await Promise.all([loadAssignments(), loadMistakes()]);
|
||||
activeTopic.value = selectLearningTopic(assignments.value, defaultTopic.value);
|
||||
await Promise.all([
|
||||
loadKnowledge(activeTopic.value),
|
||||
@@ -293,6 +343,12 @@ const openAssignment = (item: PracticeAssignment) => {
|
||||
uni.switchTab({ url: '/pages/user/practice/index' });
|
||||
};
|
||||
|
||||
const openMistake = (item: PracticeMistake) => {
|
||||
if (!item.retryAssignmentId) return;
|
||||
uni.setStorageSync(pendingAssignmentKey, String(item.retryAssignmentId));
|
||||
uni.switchTab({ url: '/pages/user/practice/index' });
|
||||
};
|
||||
|
||||
const goPractice = () => {
|
||||
if (firstPracticeAssignment.value) {
|
||||
uni.setStorageSync(pendingAssignmentKey, String(firstPracticeAssignment.value.id));
|
||||
@@ -320,6 +376,72 @@ onShow(() => {
|
||||
padding-bottom: 42px;
|
||||
}
|
||||
|
||||
.panel-caption {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: #7b8494;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mistake-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mistake-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
border: 1px solid #fee2e2;
|
||||
border-radius: 12px;
|
||||
background: #fff8f8;
|
||||
}
|
||||
|
||||
.mistake-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mistake-meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mistake-cause {
|
||||
color: #bd0010;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mistake-score {
|
||||
color: #7b8494;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mistake-title,
|
||||
.mistake-desc,
|
||||
.mistake-status {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mistake-title {
|
||||
margin-top: 4px;
|
||||
color: #1f2937;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mistake-desc,
|
||||
.mistake-status {
|
||||
margin-top: 4px;
|
||||
color: #7b8494;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.login-panel {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
PracticeFinishResponse,
|
||||
PracticePrepCard,
|
||||
PracticeRecord,
|
||||
PracticeMistake,
|
||||
PracticeTeamSnapshot,
|
||||
PracticeStartResponse,
|
||||
PracticeTurnResponse,
|
||||
@@ -88,6 +89,15 @@ export const getPracticeHistoryDetail = (id: number) =>
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
export const getPracticeMistakes = () => {
|
||||
const extPartyId = encodeURIComponent(currentExtPartyId());
|
||||
return apiRequest<PracticeMistake[]>({
|
||||
url: `/api/aihr/mobile/practice/mistakes?extPartyId=${extPartyId}`,
|
||||
method: 'GET',
|
||||
timeout: 15000
|
||||
});
|
||||
};
|
||||
|
||||
export const getCompetencyProfile = () => {
|
||||
const extPartyId = encodeURIComponent(currentExtPartyId());
|
||||
return apiRequest<CompetencyProfile>({
|
||||
|
||||
@@ -193,6 +193,19 @@ export interface PracticeRecord {
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface PracticeMistake {
|
||||
id: number;
|
||||
sessionId: string;
|
||||
scenarioId: string;
|
||||
scenarioName: string;
|
||||
score: number;
|
||||
cause: string;
|
||||
retryAssignmentId?: number | null;
|
||||
retryStatus: string;
|
||||
time: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface PracticeTeamMember {
|
||||
extPartyId: string;
|
||||
name: string;
|
||||
|
||||
@@ -135,3 +135,16 @@ test('训练页保留任务卡、对话区、回复区和结果区', async () =>
|
||||
assert.match(source, /reply-card/);
|
||||
assert.match(source, /result-card/);
|
||||
});
|
||||
|
||||
test('学习页展示真实错题本并可进入已有再练任务', async () => {
|
||||
const [source, practiceService] = await Promise.all([
|
||||
pageSource('../src/pages/user/learning/index.vue'),
|
||||
pageSource('../src/services/practice.ts')
|
||||
]);
|
||||
|
||||
assert.match(source, /错题本/);
|
||||
assert.match(source, /getPracticeMistakes/);
|
||||
assert.match(source, /retryAssignmentId/);
|
||||
assert.match(source, /pendingAssignmentKey/);
|
||||
assert.match(practiceService, /\/api\/aihr\/mobile\/practice\/mistakes/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user