feat(aihr): expose position SOP learning scope

This commit is contained in:
2026-07-14 03:29:59 +08:00
parent d98ff0ff8b
commit f9bce8f0ef
10 changed files with 151 additions and 4 deletions
+49 -1
View File
@@ -108,6 +108,13 @@
{{ knowledgeLoading ? '检索中' : '重查' }}
</button>
</view>
<view v-if="positionSopSummary" class="position-sop-status">
<text class="position-sop-title">
{{ positionSopSummary.position }} · {{ positionSopSummary.topics.length }} 个学习主题
</text>
<text class="position-sop-note">{{ positionSopSummary.message }}</text>
<text v-if="!positionSopSummary.formalApplicabilityConfigured" class="position-sop-warning">当前摘要不代表正式上岗资格</text>
</view>
<view class="chip-row topic-row">
<button
v-for="topic in topics"
@@ -177,12 +184,13 @@ import { computed, ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import type {
KnowledgeSearchResponse,
PositionSopSummary,
PracticeAssignment,
PracticeMistake,
PracticePrepCard
} from '@/types/api';
import { getPracticeAssignments, getPracticeMistakes, getPracticePrepCard } from '@/services/practice';
import { searchKnowledge } from '@/services/knowledge';
import { getPositionSopSummary, searchKnowledge } from '@/services/knowledge';
import { getSelectedPositionProfile, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { buildLearningTopics, selectLearningTopic } from '@/services/learning';
import { navigateBackToToday, resetPageScroll } from '@/services/navigation';
@@ -198,6 +206,7 @@ const assignments = ref<PracticeAssignment[]>([]);
const mistakes = ref<PracticeMistake[]>([]);
const prepCard = ref<PracticePrepCard | null>(null);
const knowledgeResult = ref<KnowledgeSearchResponse | null>(null);
const positionSopSummary = ref<PositionSopSummary | null>(null);
const activeTopic = ref('岗前 SOP');
const assignmentLoading = ref(false);
const mistakeLoading = ref(false);
@@ -311,6 +320,16 @@ const loadKnowledge = async (topic: string) => {
}
};
const loadPositionSopSummary = async () => {
if (!loggedIn.value) return;
try {
positionSopSummary.value = await getPositionSopSummary(positionProfile.value.value);
} catch (error) {
positionSopSummary.value = null;
handleRequestError(error, '岗位学习范围加载失败');
}
};
const loadPage = async () => {
refreshAuthState();
message.value = '';
@@ -319,6 +338,7 @@ const loadPage = async () => {
mistakes.value = [];
prepCard.value = null;
knowledgeResult.value = null;
positionSopSummary.value = null;
return;
}
if (!(await ensureEmployeePosition('/pages/user/learning/index'))) {
@@ -328,6 +348,7 @@ const loadPage = async () => {
await Promise.all([loadAssignments(), loadMistakes()]);
activeTopic.value = selectLearningTopic(assignments.value, defaultTopic.value);
await Promise.all([
loadPositionSopSummary(),
loadKnowledge(activeTopic.value),
loadPrepCard()
]);
@@ -383,6 +404,33 @@ onShow(() => {
font-size: 12px;
}
.position-sop-status {
display: grid;
gap: 4px;
margin-bottom: 12px;
padding: 10px 12px;
border-radius: 10px;
background: #f3f7ff;
}
.position-sop-title {
color: #183b70;
font-size: 13px;
font-weight: 700;
}
.position-sop-note {
color: #60708a;
font-size: 12px;
line-height: 1.5;
}
.position-sop-warning {
color: #9a6700;
font-size: 12px;
line-height: 1.5;
}
.mistake-list {
display: grid;
gap: 10px;
+8
View File
@@ -2,6 +2,7 @@ import type {
AnswerFeedbackVerdict,
KnowledgeSearchResponse,
KnowledgeSnippet,
PositionSopSummary,
SummaryCardResponse
} from '@/types/api';
import { apiRequest } from './api';
@@ -36,6 +37,13 @@ export const searchKnowledge = async (queryText: string, position = getSelectedP
};
};
export const getPositionSopSummary = (position = getSelectedPosition()) =>
apiRequest<PositionSopSummary>({
url: `/api/knowledge/position-sop?position=${encodeURIComponent(position)}&projectType=住宅`,
method: 'GET',
timeout: 15000
});
export const submitAnswerFeedback = (params: {
queryText: string;
fragmentIds: number[];
+17
View File
@@ -103,6 +103,23 @@ export interface KnowledgeSearchResponse {
cautions?: string[];
}
export interface PositionSopTopic {
category: string;
question: string;
reference: string;
status: string;
}
export interface PositionSopSummary {
position: string;
projectType: string;
routingMode: string;
formalApplicabilityConfigured: boolean;
qualificationStatus: string;
message: string;
topics: PositionSopTopic[];
}
export type AnswerFeedbackVerdict = 'up' | 'down';
export interface SummaryCardStep {
@@ -148,3 +148,14 @@ test('学习页展示真实错题本并可进入已有再练任务', async () =>
assert.match(source, /pendingAssignmentKey/);
assert.match(practiceService, /\/api\/aihr\/mobile\/practice\/mistakes/);
});
test('学习页显示岗位 SOP 适配状态而不冒充上岗资格', async () => {
const [source, knowledgeService] = await Promise.all([
pageSource('../src/pages/user/learning/index.vue'),
pageSource('../src/services/knowledge.ts')
]);
assert.match(source, /positionSopSummary/);
assert.match(source, /正式上岗资格/);
assert.match(knowledgeService, /\/api\/knowledge\/position-sop/);
});