fix(mobile): require employee position before user flow

This commit is contained in:
2026-07-09 17:09:26 +08:00
parent 8b92a7e9ac
commit 8f73cb0c84
13 changed files with 473 additions and 33 deletions
+9 -1
View File
@@ -177,6 +177,7 @@ import { answerDailyDrill, finishPractice, getPracticeAssignments, getPracticePr
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { searchKnowledge } from '@/services/knowledge';
import { resetPageScroll } from '@/services/navigation';
import { ensureEmployeePosition } from '@/services/position';
import { chooseSpeechAudio, transcribeSpeechBlob, transcribeSpeechFile } from '@/services/speech';
type PracticeStatus = 'idle' | 'starting' | 'active' | 'submitting' | 'finished';
@@ -675,11 +676,18 @@ const finish = async (force = false) => {
}
};
onShow(() => {
const handleShow = async () => {
resetPageScroll();
refreshAuthState();
if (loggedIn.value && !(await ensureEmployeePosition('/pages/user/practice/index'))) {
return;
}
void loadAssignments();
void loadPrepCard();
};
onShow(() => {
void handleShow();
});
</script>
+88 -1
View File
@@ -14,6 +14,26 @@
<button v-else class="secondary-button account-action" @click="logout">退出</button>
</view>
<view v-if="loggedIn" class="panel position-panel">
<view class="position-current">
<view>
<text class="panel-title">岗位身份</text>
<text class="position-title">{{ positionProfile.value }} · {{ positionProfile.heroTitle }}</text>
</view>
</view>
<view class="position-actions">
<button
v-for="item in positionCards"
:key="item.value"
class="position-chip"
:class="{ active: item.value === positionProfile.value }"
@click="changePosition(item.value)"
>
{{ item.value }}
</button>
</view>
</view>
<view v-if="profile" class="panel">
<text class="panel-title">成长摘要</text>
<view class="score-row">
@@ -117,9 +137,20 @@
import { ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import type { CompetencyProfile, PracticeRecord, PromotionEvidence } from '@/types/api';
import { clearAuth, getAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import {
clearAuth,
getAuth,
getPositionProfile,
getSelectedPositionProfile,
isLoggedIn,
rememberLoginRedirect,
saveSelectedPosition,
userPositionOptions,
type UserPosition
} from '@/services/auth';
import { getCompetencyProfile, getPracticeHistory, getPromotionEvidence } from '@/services/practice';
import { resetPageScroll } from '@/services/navigation';
import { ensureEmployeePosition } from '@/services/position';
const loggedIn = ref(false);
const phone = ref('');
@@ -128,11 +159,14 @@ const message = ref('');
const profile = ref<CompetencyProfile | null>(null);
const history = ref<PracticeRecord[]>([]);
const evidence = ref<PromotionEvidence | null>(null);
const positionProfile = ref(getSelectedPositionProfile());
const positionCards = userPositionOptions.map((item) => getPositionProfile(item));
const refreshAuth = () => {
const auth = getAuth();
loggedIn.value = isLoggedIn();
phone.value = auth.phone;
positionProfile.value = getSelectedPositionProfile();
};
const clearProfileData = () => {
@@ -147,6 +181,10 @@ const loadData = async () => {
clearProfileData();
return;
}
if (!(await ensureEmployeePosition('/pages/user/profile/index'))) {
return;
}
refreshAuth();
loading.value = true;
message.value = '';
try {
@@ -183,6 +221,13 @@ const logout = () => {
uni.showToast({ title: '已退出', icon: 'none' });
};
const changePosition = (position: UserPosition) => {
const saved = saveSelectedPosition(position);
if (!saved) return;
positionProfile.value = getSelectedPositionProfile();
uni.showToast({ title: '岗位已更新', icon: 'none' });
};
onShow(() => {
resetPageScroll();
void loadData();
@@ -214,6 +259,48 @@ onShow(() => {
min-width: 84px;
}
.position-current {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.position-title {
display: block;
margin-top: 8px;
color: #2563eb;
font-size: 16px;
font-weight: 900;
}
.position-actions {
display: grid;
margin-top: 14px;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 8px;
}
.position-chip {
display: flex;
min-height: 38px;
align-items: center;
justify-content: center;
border: 1px solid #e2e8f0;
border-radius: 12px;
background: #f8fafc;
color: #475569;
font-size: 13px;
font-weight: 900;
white-space: nowrap;
}
.position-chip.active {
border-color: #2563eb;
background: #eff6ff;
color: #1d4ed8;
}
.score-row {
display: grid;
margin-top: 16px;
+21 -5
View File
@@ -8,7 +8,7 @@
v-model="query"
class="textarea"
maxlength="200"
placeholder="例如: 业主投诉噪音,生活顾问应该怎么处理?"
:placeholder="quickQuestionPlaceholder"
/>
<view class="chip-row">
<button v-for="item in quickQuestions" :key="item" class="chip" :disabled="loading" @click="useQuickQuestion(item)">
@@ -107,7 +107,7 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import type {
AnswerFeedbackVerdict,
@@ -119,10 +119,13 @@ import {
searchKnowledge,
submitAnswerFeedback
} from '@/services/knowledge';
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { getSelectedPositionProfile, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { ensureEmployeePosition } from '@/services/position';
const quickQuestions = ['业主投诉噪音怎么安抚', '停车费催缴怎么说', '生活顾问的一天做什么'];
const positionProfile = ref(getSelectedPositionProfile());
const quickQuestions = computed(() => positionProfile.value.quickQuestions);
const quickQuestionPlaceholder = computed(() => `例如: ${quickQuestions.value[0] || '岗位SOP怎么处理?'}`);
const query = ref('');
const result = ref<KnowledgeSearchResponse | null>(null);
const summaryCard = ref<SummaryCardResponse | null>(null);
@@ -143,6 +146,7 @@ const resetResultState = () => {
const refreshAuthState = () => {
loggedIn.value = isLoggedIn();
positionProfile.value = getSelectedPositionProfile();
};
const handleRequestError = (error: unknown, fallback: string, wasLoggedIn = loggedIn.value) => {
@@ -169,6 +173,10 @@ const search = async () => {
goLogin();
return;
}
if (!(await ensureEmployeePosition('/pages/user/sop/index'))) {
return;
}
positionProfile.value = getSelectedPositionProfile();
loading.value = true;
message.value = '';
resetResultState();
@@ -256,9 +264,13 @@ const goLogin = () => {
uni.navigateTo({ url: '/pages/auth/login/index' });
};
onShow(() => {
const handleShow = async () => {
resetPageScroll();
refreshAuthState();
if (loggedIn.value && !(await ensureEmployeePosition('/pages/user/sop/index'))) {
return;
}
positionProfile.value = getSelectedPositionProfile();
if (!loggedIn.value) {
resetResultState();
}
@@ -276,6 +288,10 @@ onShow(() => {
} else {
message.value = '登录后继续查询该问题';
}
};
onShow(() => {
void handleShow();
});
</script>
+14 -6
View File
@@ -4,8 +4,8 @@
<view class="mentor-hero">
<view class="hero-copy">
<text class="hero-role">生活顾问</text>
<text class="hero-title">客服管家</text>
<text class="hero-role">{{ positionProfile.value }}</text>
<text class="hero-title">{{ positionProfile.heroTitle }}</text>
<text class="hero-sub">有问题,随时问老师傅</text>
</view>
<image class="mentor-image" src="/static/mentor.png" mode="aspectFit" />
@@ -95,9 +95,9 @@
<view v-else class="empty">暂无工具入口</view>
</view>
<button class="tip-card" @click="ask('催费话术')">
<button class="tip-card" @click="ask(primaryTipQuestion)">
<text class="tip-mark">贴</text>
<text>今日小贴士:催费沟通先共情,再说明,再给方案。</text>
<text>{{ positionProfile.tip }}</text>
<text class="tip-arrow">›</text>
</button>
</view>
@@ -109,14 +109,17 @@ import { onShow } from '@dcloudio/uni-app';
import type { PracticeAssignment, RoleHome } from '@/types/api';
import { getMobileHome } from '@/services/home';
import { getPracticeAssignments } from '@/services/practice';
import { getAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { getAuth, getSelectedPositionProfile, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { ensureEmployeePosition } from '@/services/position';
const home = ref<RoleHome | null>(null);
const assignments = ref<PracticeAssignment[]>([]);
const message = ref('');
const loggedIn = ref(false);
const quickQuestions = ['催费话术', '投诉应对', '收费标准'];
const positionProfile = ref(getSelectedPositionProfile());
const quickQuestions = computed(() => positionProfile.value.quickQuestions);
const primaryTipQuestion = computed(() => quickQuestions.value[0] || '岗位SOP');
const pendingSopQueryKey = 'aihr_sop_pending_query';
const pendingAssignmentKey = 'aihr_pending_assignment_id';
@@ -176,12 +179,17 @@ const mentorBubbleText = computed(() =>
const loadHome = async () => {
loggedIn.value = isLoggedIn();
positionProfile.value = getSelectedPositionProfile();
message.value = '';
assignments.value = [];
if (!loggedIn.value) {
home.value = null;
return;
}
if (!(await ensureEmployeePosition('/pages/user/today/index'))) {
return;
}
positionProfile.value = getSelectedPositionProfile();
const [homeResult, assignmentResult] = await Promise.allSettled([
getMobileHome('user'),
getPracticeAssignments(true)