fix(mobile): drive learning search from assignments

This commit is contained in:
2026-07-10 02:20:56 +08:00
parent 4b951d07a0
commit 0219de6fa8
8 changed files with 213 additions and 26 deletions
+15 -3
View File
@@ -1,7 +1,19 @@
<template>
<view class="page cases-page">
<text class="page-title">案例素材</text>
<text class="page-subtitle">把一线语音整理成可复用培训案例。上传后依次完成 ASR、AI 整理和入库。</text>
<view class="subpage-header">
<button
class="subpage-back-button"
aria-label="返回今日"
title="返回今日"
@click="navigateBackToToday"
>
←
</button>
<view class="subpage-heading">
<text class="page-title">案例素材</text>
<text class="page-subtitle">把一线语音整理成可复用培训案例。上传后依次完成 ASR、AI 整理和入库。</text>
</view>
</view>
<view v-if="message" class="status-message">{{ message }}</view>
@@ -85,7 +97,7 @@ import { onShow } from '@dcloudio/uni-app';
import type { CaseCurateResponse, CaseOrganizeResponse, CaseRecord, CaseSelectedFile, CaseUploadResponse } from '@/types/api';
import { chooseCaseAudio, curateCase, getCaseRecords, organizeCase, uploadCaseAudio } from '@/services/case';
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { navigateBackToToday, resetPageScroll } from '@/services/navigation';
import { ensureEmployeePosition } from '@/services/position';
const loggedIn = ref(false);
+30 -23
View File
@@ -1,8 +1,18 @@
<template>
<view class="page learning-page">
<view class="learning-head">
<text class="page-title">学</text>
<text class="page-subtitle">按你的岗位读取今日任务、SOP 依据和训练前预习卡。</text>
<view class="subpage-header">
<button
class="subpage-back-button"
aria-label="返回今日"
title="返回今日"
@click="navigateBackToToday"
>
←
</button>
<view class="subpage-heading">
<text class="page-title">学</text>
<text class="page-subtitle">按你的岗位读取今日任务、SOP 依据和训练前预习卡。</text>
</view>
</view>
<view v-if="message" class="status-message">{{ message }}</view>
@@ -18,11 +28,11 @@
<view v-if="loggedIn" class="learning-summary">
<view class="summary-item">
<text class="summary-value">{{ pendingCount }}</text>
<text class="summary-label">待学习</text>
<text class="summary-label">待完成</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ completedCount }}</text>
<text class="summary-label">已完成</text>
<text class="summary-label">今日完成</text>
</view>
<view class="summary-item">
<text class="summary-value">{{ referenceCount }}</text>
@@ -32,7 +42,7 @@
<view v-if="loggedIn" class="panel">
<view class="panel-head">
<text class="panel-title">今日学习</text>
<text class="panel-title">今日任务</text>
<button class="ghost-button compact-button" :disabled="assignmentLoading" @click="loadAssignments">
{{ assignmentLoading ? '刷新中' : '刷新' }}
</button>
@@ -55,7 +65,7 @@
</button>
</view>
</view>
<view v-else class="empty">暂无今日学习任务</view>
<view v-else class="empty">暂无今日任务</view>
</view>
<view v-if="loggedIn" class="panel">
@@ -140,7 +150,8 @@ import type {
import { getPracticeAssignments, getPracticePrepCard } from '@/services/practice';
import { searchKnowledge } from '@/services/knowledge';
import { getSelectedPositionProfile, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { buildLearningTopics, selectLearningTopic } from '@/services/learning';
import { navigateBackToToday, resetPageScroll } from '@/services/navigation';
import { ensureEmployeePosition } from '@/services/position';
const defaultScenarioId = 'complaint-water';
@@ -158,13 +169,12 @@ const prepLoading = ref(false);
const knowledgeLoading = ref(false);
const positionProfile = ref(getSelectedPositionProfile());
const topics = computed(() => {
const profile = positionProfile.value;
return [
`${profile.value} 岗前 SOP`,
...profile.quickQuestions
];
});
const defaultTopic = computed(() => `${positionProfile.value.value} 岗前 SOP`);
const topics = computed(() => buildLearningTopics(
assignments.value,
defaultTopic.value,
positionProfile.value.quickQuestions
));
const pendingCount = computed(() => assignments.value.filter((item) => item.status !== '已完成').length);
const completedCount = computed(() => assignments.value.filter((item) => item.status === '已完成').length);
@@ -218,6 +228,7 @@ const loadAssignments = async () => {
try {
assignments.value = await getPracticeAssignments(true);
} catch (error) {
assignments.value = [];
handleRequestError(error, '学习任务加载失败');
} finally {
assignmentLoading.value = false;
@@ -264,12 +275,12 @@ const loadPage = async () => {
return;
}
refreshAuthState();
activeTopic.value = topics.value[0] || activeTopic.value;
await loadAssignments();
activeTopic.value = selectLearningTopic(assignments.value, defaultTopic.value);
await Promise.all([
loadAssignments(),
loadKnowledge(activeTopic.value)
loadKnowledge(activeTopic.value),
loadPrepCard()
]);
await loadPrepCard();
};
const goLogin = () => {
@@ -309,10 +320,6 @@ onShow(() => {
padding-bottom: 42px;
}
.learning-head {
padding-top: 4px;
}
.login-panel {
display: flex;
gap: 14px;
+44
View File
@@ -0,0 +1,44 @@
export interface LearningTopicAssignment {
source?: string | null;
status?: string | null;
scenarioName?: string | null;
}
const normalized = (value?: string | null) => value?.trim() || '';
export const selectLearningTopic = (
assignments: readonly LearningTopicAssignment[],
fallbackTopic: string
) => {
const scenarioAssignments = assignments.filter((item) =>
normalized(item.source) !== 'daily' && Boolean(normalized(item.scenarioName))
);
const selected = scenarioAssignments.find((item) => normalized(item.status) !== '已完成')
|| scenarioAssignments[0];
return normalized(selected?.scenarioName) || fallbackTopic;
};
export const buildLearningTopics = (
assignments: readonly LearningTopicAssignment[],
fallbackTopic: string,
quickQuestions: readonly string[]
) => {
const values = [
fallbackTopic,
...assignments
.filter((item) => normalized(item.source) !== 'daily')
.map((item) => normalized(item.scenarioName)),
...quickQuestions
];
const topics: string[] = [];
values.forEach((value) => {
const topic = normalized(value);
if (topic && !topics.includes(topic)) {
topics.push(topic);
}
});
return topics;
};
+15
View File
@@ -13,3 +13,18 @@ export const resetPageScroll = () => {
setTimeout(scrollTop, 80);
setTimeout(scrollTop, 240);
};
const switchToToday = () => {
uni.switchTab({ url: '/pages/user/today/index' });
};
export const navigateBackToToday = () => {
if (getCurrentPages().length <= 1) {
switchToToday();
return;
}
uni.navigateBack({
delta: 1,
fail: switchToToday
});
};
+30
View File
@@ -49,6 +49,36 @@ button::after {
color: #64748b;
}
.subpage-header {
display: flex;
gap: 12px;
align-items: flex-start;
}
.subpage-heading {
flex: 1;
min-width: 0;
}
.subpage-back-button {
display: flex;
flex: 0 0 44px;
width: 44px;
min-width: 44px;
height: 44px;
min-height: 44px;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #ffffff;
color: #1e293b;
font-size: 24px;
font-weight: 700;
line-height: 1;
}
.panel {
margin-top: 16px;
padding: 18px;