fix(aihr): expose enabled scenarios to supervisors

This commit is contained in:
2026-07-14 13:55:09 +08:00
parent c73234c823
commit f0662bedcd
7 changed files with 81 additions and 14 deletions
@@ -122,7 +122,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
import type { PracticeAlert, PracticeAssignment } from '@/types/api';
import { clearAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { createPracticeAssignment, getPracticeAlerts, getPracticeTeamSnapshot } from '@/services/practice';
import { createPracticeAssignment, getPracticeAlerts, getPracticeScenarios, getPracticeTeamSnapshot } from '@/services/practice';
import SupervisorTabBar from '@/components/SupervisorTabBar.vue';
import {
createAsyncGenerationGuard,
@@ -130,6 +130,7 @@ import {
isSupervisorAccessDenied,
manualPracticeAssignments,
supervisorScenarioOptions,
type SupervisorScenarioOption,
type SupervisorScenarioId
} from '@/services/supervisor-view';
@@ -137,6 +138,7 @@ const basePath = '/pages/supervisor/assign/index';
const targetExtPartyId = ref('');
const targetName = ref('');
const assignmentScenarioId = ref<SupervisorScenarioId>('fee-parking');
const scenarioOptions = ref<SupervisorScenarioOption[]>([...supervisorScenarioOptions]);
const assignmentReason = ref('');
const assignments = ref<PracticeAssignment[]>([]);
const alerts = ref<PracticeAlert[]>([]);
@@ -174,7 +176,7 @@ const queryText = (value: unknown) => {
};
const isScenarioId = (value: string): value is SupervisorScenarioId =>
supervisorScenarioOptions.some((item) => item.id === value);
scenarioOptions.value.some((item) => item.id === value);
const refreshAuthState = () => {
loggedIn.value = isLoggedIn();
@@ -240,6 +242,31 @@ const loadAssignments = async (force = false) => {
}
};
const loadScenarioOptions = async () => {
try {
const rows = await getPracticeScenarios('生活顾问');
const nextOptions = rows
.filter((item) => item.enabled !== false && item.id && item.name)
.map((item) => ({ id: item.id, name: item.name }));
if (nextOptions.length) {
scenarioOptions.value = nextOptions;
if (!isScenarioId(assignmentScenarioId.value)) {
assignmentScenarioId.value = nextOptions[0].id;
}
return;
}
scenarioOptions.value = [...supervisorScenarioOptions];
if (!isScenarioId(assignmentScenarioId.value)) {
assignmentScenarioId.value = supervisorScenarioOptions[0].id;
}
} catch {
scenarioOptions.value = [...supervisorScenarioOptions];
if (!isScenarioId(assignmentScenarioId.value)) {
assignmentScenarioId.value = supervisorScenarioOptions[0].id;
}
}
};
const loadPage = () => {
refreshAuthState();
message.value = '';
@@ -247,7 +274,7 @@ const loadPage = () => {
clearPrivateState();
return;
}
void loadAssignments();
void Promise.all([loadAssignments(), loadScenarioOptions()]);
};
const assignPractice = async () => {
@@ -261,7 +288,7 @@ const assignPractice = async () => {
assigning.value = true;
message.value = '';
try {
const scenario = supervisorScenarioOptions.find((item) => item.id === assignmentScenarioId.value);
const scenario = scenarioOptions.value.find((item) => item.id === assignmentScenarioId.value);
await createPracticeAssignment({
extPartyId,
traineeName: targetName.value.trim() || `手机用户${extPartyId.slice(-4)}`,
@@ -322,9 +349,7 @@ onLoad((query) => {
targetName.value = queryText(query?.name);
assignmentReason.value = queryText(query?.reason);
const requestedScenario = queryText(query?.scenarioId);
assignmentScenarioId.value = isScenarioId(requestedScenario)
? requestedScenario
: inferSupervisorScenarioId(assignmentReason.value);
assignmentScenarioId.value = requestedScenario || inferSupervisorScenarioId(assignmentReason.value);
});
onShow(() => {
@@ -118,7 +118,7 @@
暂不派发
</button>
<button
v-for="item in supervisorScenarioOptions"
v-for="item in scenarioOptions"
:key="item.id"
class="chip scenario-chip"
:class="{ active: nextScenarioId === item.id }"
@@ -165,7 +165,7 @@ import type { ReviewDetail, ReviewDialogue } from '@/types/api';
import { authenticatedFileUrl, releaseAuthenticatedFileUrl } from '@/services/api';
import { clearAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { resetPageScroll } from '@/services/navigation';
import { getPracticeReviewDetail, markPracticeReviewed } from '@/services/practice';
import { getPracticeReviewDetail, getPracticeScenarios, markPracticeReviewed } from '@/services/practice';
import SupervisorTabBar from '@/components/SupervisorTabBar.vue';
import {
createAsyncGenerationGuard,
@@ -173,6 +173,7 @@ import {
isSupervisorAccessDenied,
reviewCompletionAction,
supervisorScenarioOptions,
type SupervisorScenarioOption,
type SupervisorScenarioId
} from '@/services/supervisor-view';
@@ -182,6 +183,7 @@ const detail = ref<ReviewDetail | null>(null);
const advice = ref('');
const incentivePoint = ref('');
const nextScenarioId = ref<SupervisorScenarioId | ''>('');
const scenarioOptions = ref<SupervisorScenarioOption[]>([...supervisorScenarioOptions]);
const nextReason = ref('');
const loggedIn = ref(false);
const accessDenied = ref(false);
@@ -309,9 +311,23 @@ const loadDetail = async () => {
}
};
const loadScenarioOptions = async () => {
try {
const rows = await getPracticeScenarios('生活顾问');
const nextOptions = rows
.filter((item) => item.enabled !== false && item.id && item.name)
.map((item) => ({ id: item.id, name: item.name }));
if (nextOptions.length) {
scenarioOptions.value = nextOptions;
}
} catch {
scenarioOptions.value = [...supervisorScenarioOptions];
}
};
const selectNextScenario = (scenarioId: SupervisorScenarioId) => {
nextScenarioId.value = scenarioId;
const scenario = supervisorScenarioOptions.find((item) => item.id === scenarioId);
const scenario = scenarioOptions.value.find((item) => item.id === scenarioId);
nextReason.value = `复盘后建议补练${scenario?.name || '专项训练'}`;
};
@@ -402,6 +418,7 @@ onShow(() => {
clearDetail();
return;
}
void loadScenarioOptions();
if (!detail.value && !loading.value) void loadDetail();
});
+9 -1
View File
@@ -13,7 +13,8 @@ import type {
PracticeAssignmentRequest,
DailyDrillAnswerResponse,
PracticeAlert,
PromotionEvidence
PromotionEvidence,
PracticeScenarioOption
} from '@/types/api';
import { apiRequest } from './api';
import { getAuth, getSelectedPosition } from './auth';
@@ -35,6 +36,13 @@ export const getPracticePrepCard = (scenarioId = defaultScenarioId) =>
timeout: 15000
});
export const getPracticeScenarios = (position = getSelectedPosition()) =>
apiRequest<PracticeScenarioOption[]>({
url: `/api/train/practice/scenarios?position=${encodeURIComponent(position || '生活顾问')}`,
method: 'GET',
timeout: 15000
});
export const startPractice = (scenarioId = defaultScenarioId, assignmentId?: number) =>
apiRequest<PracticeStartResponse>({
url: '/api/train/practice/start',
+8 -3
View File
@@ -30,13 +30,18 @@ export interface TeamProfile {
risk: 'high' | 'medium' | 'low';
}
export const supervisorScenarioOptions = [
export interface SupervisorScenarioOption {
id: string;
name: string;
}
export const supervisorScenarioOptions: SupervisorScenarioOption[] = [
{ id: 'fee-parking', name: '催费沟通' },
{ id: 'complaint-water', name: '投诉接待' },
{ id: 'repair-elevator-delay', name: '电梯故障' }
] as const;
];
export type SupervisorScenarioId = typeof supervisorScenarioOptions[number]['id'];
export type SupervisorScenarioId = string;
export interface AsyncGenerationGuard {
capture: () => number;
+8
View File
@@ -252,6 +252,14 @@ export interface PracticeTeamSnapshot {
mistakes: PracticeMistakeAggregate[];
}
export interface PracticeScenarioOption {
id: string;
name: string;
enabled?: boolean;
position?: string;
scenarioType?: string;
}
export interface ReviewDialogue {
role: string;
label: string;