fix(aihr): expose enabled scenarios to supervisors
This commit is contained in:
@@ -268,3 +268,4 @@
|
|||||||
- 2026-07-14 BRD 4.3.4 每日三题生产 profile 硬门禁:旧回退即使被环境变量显式打开,仍可能在生产 profile 生效;现仅允许 `dev/local` profile 使用训练次数兼容回退,`prod` 或混合 profile 强制关闭。新增 profile 边界测试和 `demo-check` marker;未修改数据库结构或生产数据。
|
- 2026-07-14 BRD 4.3.4 每日三题生产 profile 硬门禁:旧回退即使被环境变量显式打开,仍可能在生产 profile 生效;现仅允许 `dev/local` profile 使用训练次数兼容回退,`prod` 或混合 profile 强制关闭。新增 profile 边界测试和 `demo-check` marker;未修改数据库结构或生产数据。
|
||||||
- 2026-07-14 BRD 6.3 SOP 来源标识修复:SOP 检索成功时前端原先忽略 API 返回的 `records`,后端返回的内置记录也没有来源字段,可能把演示文档显示成真实知识库;现 API 成功态只使用响应记录,内置记录明确标记 `source=demo`,失败/未检索态继续保留已有回退提示。未修改知识库数据或生产环境。
|
- 2026-07-14 BRD 6.3 SOP 来源标识修复:SOP 检索成功时前端原先忽略 API 返回的 `records`,后端返回的内置记录也没有来源字段,可能把演示文档显示成真实知识库;现 API 成功态只使用响应记录,内置记录明确标记 `source=demo`,失败/未检索态继续保留已有回退提示。未修改知识库数据或生产环境。
|
||||||
- 2026-07-14 当前线上资源复核:生产根站、`/h5/` 和 `/prod-api/auth/tenant/list` 均返回 `200`;线上管理端 `index-CJZ3Ax3Z.js` SHA-256 为 `8b9278a26ccb760abce489b12a20b221748c64d4c4153613d2511777bb9677bc`,当前分支构建 `index-B3bSjqdi.js` SHA-256 为 `2ff13b11307631f9cd672247d6d08e395fb674ac5b9c6f7c4fb27832eacdf075`;线上 H5 `index-D4-NrEpb.js` SHA-256 为 `ab15bfd17cabe58e2f34b0ac61ee198a837128ed85dc6bb0ddf77bc3e22dcd9c`,当前分支构建 `index-8gZKJTho.js` SHA-256 为 `c6b01faeba4198cb7ff9f5c2ffa88f1cc97c8ecdf20be86f432ca7a1d673f3e1`。本轮只读核验,未执行生产静态资源同步、后端重启或业务数据写入。
|
- 2026-07-14 当前线上资源复核:生产根站、`/h5/` 和 `/prod-api/auth/tenant/list` 均返回 `200`;线上管理端 `index-CJZ3Ax3Z.js` SHA-256 为 `8b9278a26ccb760abce489b12a20b221748c64d4c4153613d2511777bb9677bc`,当前分支构建 `index-B3bSjqdi.js` SHA-256 为 `2ff13b11307631f9cd672247d6d08e395fb674ac5b9c6f7c4fb27832eacdf075`;线上 H5 `index-D4-NrEpb.js` SHA-256 为 `ab15bfd17cabe58e2f34b0ac61ee198a837128ed85dc6bb0ddf77bc3e22dcd9c`,当前分支构建 `index-8gZKJTho.js` SHA-256 为 `c6b01faeba4198cb7ff9f5c2ffa88f1cc97c8ecdf20be86f432ca7a1d673f3e1`。本轮只读核验,未执行生产静态资源同步、后端重启或业务数据写入。
|
||||||
|
- 2026-07-14 BRD 4.4 场景派发覆盖复核:数据库已有 12 个生活顾问场景,但移动端主管“指派专项”和“复盘后再练”此前只提供 3 个静态场景,导致其余已启用场景无法从主管入口使用;现改为 API 优先加载已启用场景,失败时保留 3 个本地兜底。BRD 所称 7–14 天专项训练营目前仍只有 `source='camp'` 标签,没有训练营计划、天数、每日任务、进度和完成规则,需 HR 确认内容与运营口径后再实现,不以标签冒充训练营闭环。
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
|
|||||||
import type { PracticeAlert, PracticeAssignment } from '@/types/api';
|
import type { PracticeAlert, PracticeAssignment } from '@/types/api';
|
||||||
import { clearAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
import { clearAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||||||
import { resetPageScroll } from '@/services/navigation';
|
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 SupervisorTabBar from '@/components/SupervisorTabBar.vue';
|
||||||
import {
|
import {
|
||||||
createAsyncGenerationGuard,
|
createAsyncGenerationGuard,
|
||||||
@@ -130,6 +130,7 @@ import {
|
|||||||
isSupervisorAccessDenied,
|
isSupervisorAccessDenied,
|
||||||
manualPracticeAssignments,
|
manualPracticeAssignments,
|
||||||
supervisorScenarioOptions,
|
supervisorScenarioOptions,
|
||||||
|
type SupervisorScenarioOption,
|
||||||
type SupervisorScenarioId
|
type SupervisorScenarioId
|
||||||
} from '@/services/supervisor-view';
|
} from '@/services/supervisor-view';
|
||||||
|
|
||||||
@@ -137,6 +138,7 @@ const basePath = '/pages/supervisor/assign/index';
|
|||||||
const targetExtPartyId = ref('');
|
const targetExtPartyId = ref('');
|
||||||
const targetName = ref('');
|
const targetName = ref('');
|
||||||
const assignmentScenarioId = ref<SupervisorScenarioId>('fee-parking');
|
const assignmentScenarioId = ref<SupervisorScenarioId>('fee-parking');
|
||||||
|
const scenarioOptions = ref<SupervisorScenarioOption[]>([...supervisorScenarioOptions]);
|
||||||
const assignmentReason = ref('');
|
const assignmentReason = ref('');
|
||||||
const assignments = ref<PracticeAssignment[]>([]);
|
const assignments = ref<PracticeAssignment[]>([]);
|
||||||
const alerts = ref<PracticeAlert[]>([]);
|
const alerts = ref<PracticeAlert[]>([]);
|
||||||
@@ -174,7 +176,7 @@ const queryText = (value: unknown) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isScenarioId = (value: string): value is SupervisorScenarioId =>
|
const isScenarioId = (value: string): value is SupervisorScenarioId =>
|
||||||
supervisorScenarioOptions.some((item) => item.id === value);
|
scenarioOptions.value.some((item) => item.id === value);
|
||||||
|
|
||||||
const refreshAuthState = () => {
|
const refreshAuthState = () => {
|
||||||
loggedIn.value = isLoggedIn();
|
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 = () => {
|
const loadPage = () => {
|
||||||
refreshAuthState();
|
refreshAuthState();
|
||||||
message.value = '';
|
message.value = '';
|
||||||
@@ -247,7 +274,7 @@ const loadPage = () => {
|
|||||||
clearPrivateState();
|
clearPrivateState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void loadAssignments();
|
void Promise.all([loadAssignments(), loadScenarioOptions()]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const assignPractice = async () => {
|
const assignPractice = async () => {
|
||||||
@@ -261,7 +288,7 @@ const assignPractice = async () => {
|
|||||||
assigning.value = true;
|
assigning.value = true;
|
||||||
message.value = '';
|
message.value = '';
|
||||||
try {
|
try {
|
||||||
const scenario = supervisorScenarioOptions.find((item) => item.id === assignmentScenarioId.value);
|
const scenario = scenarioOptions.value.find((item) => item.id === assignmentScenarioId.value);
|
||||||
await createPracticeAssignment({
|
await createPracticeAssignment({
|
||||||
extPartyId,
|
extPartyId,
|
||||||
traineeName: targetName.value.trim() || `手机用户${extPartyId.slice(-4)}`,
|
traineeName: targetName.value.trim() || `手机用户${extPartyId.slice(-4)}`,
|
||||||
@@ -322,9 +349,7 @@ onLoad((query) => {
|
|||||||
targetName.value = queryText(query?.name);
|
targetName.value = queryText(query?.name);
|
||||||
assignmentReason.value = queryText(query?.reason);
|
assignmentReason.value = queryText(query?.reason);
|
||||||
const requestedScenario = queryText(query?.scenarioId);
|
const requestedScenario = queryText(query?.scenarioId);
|
||||||
assignmentScenarioId.value = isScenarioId(requestedScenario)
|
assignmentScenarioId.value = requestedScenario || inferSupervisorScenarioId(assignmentReason.value);
|
||||||
? requestedScenario
|
|
||||||
: inferSupervisorScenarioId(assignmentReason.value);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
|||||||
@@ -118,7 +118,7 @@
|
|||||||
暂不派发
|
暂不派发
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-for="item in supervisorScenarioOptions"
|
v-for="item in scenarioOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="chip scenario-chip"
|
class="chip scenario-chip"
|
||||||
:class="{ active: nextScenarioId === item.id }"
|
:class="{ active: nextScenarioId === item.id }"
|
||||||
@@ -165,7 +165,7 @@ import type { ReviewDetail, ReviewDialogue } from '@/types/api';
|
|||||||
import { authenticatedFileUrl, releaseAuthenticatedFileUrl } from '@/services/api';
|
import { authenticatedFileUrl, releaseAuthenticatedFileUrl } from '@/services/api';
|
||||||
import { clearAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
import { clearAuth, isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||||||
import { resetPageScroll } from '@/services/navigation';
|
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 SupervisorTabBar from '@/components/SupervisorTabBar.vue';
|
||||||
import {
|
import {
|
||||||
createAsyncGenerationGuard,
|
createAsyncGenerationGuard,
|
||||||
@@ -173,6 +173,7 @@ import {
|
|||||||
isSupervisorAccessDenied,
|
isSupervisorAccessDenied,
|
||||||
reviewCompletionAction,
|
reviewCompletionAction,
|
||||||
supervisorScenarioOptions,
|
supervisorScenarioOptions,
|
||||||
|
type SupervisorScenarioOption,
|
||||||
type SupervisorScenarioId
|
type SupervisorScenarioId
|
||||||
} from '@/services/supervisor-view';
|
} from '@/services/supervisor-view';
|
||||||
|
|
||||||
@@ -182,6 +183,7 @@ const detail = ref<ReviewDetail | null>(null);
|
|||||||
const advice = ref('');
|
const advice = ref('');
|
||||||
const incentivePoint = ref('');
|
const incentivePoint = ref('');
|
||||||
const nextScenarioId = ref<SupervisorScenarioId | ''>('');
|
const nextScenarioId = ref<SupervisorScenarioId | ''>('');
|
||||||
|
const scenarioOptions = ref<SupervisorScenarioOption[]>([...supervisorScenarioOptions]);
|
||||||
const nextReason = ref('');
|
const nextReason = ref('');
|
||||||
const loggedIn = ref(false);
|
const loggedIn = ref(false);
|
||||||
const accessDenied = 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) => {
|
const selectNextScenario = (scenarioId: SupervisorScenarioId) => {
|
||||||
nextScenarioId.value = scenarioId;
|
nextScenarioId.value = scenarioId;
|
||||||
const scenario = supervisorScenarioOptions.find((item) => item.id === scenarioId);
|
const scenario = scenarioOptions.value.find((item) => item.id === scenarioId);
|
||||||
nextReason.value = `复盘后建议补练${scenario?.name || '专项训练'}`;
|
nextReason.value = `复盘后建议补练${scenario?.name || '专项训练'}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -402,6 +418,7 @@ onShow(() => {
|
|||||||
clearDetail();
|
clearDetail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
void loadScenarioOptions();
|
||||||
if (!detail.value && !loading.value) void loadDetail();
|
if (!detail.value && !loading.value) void loadDetail();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import type {
|
|||||||
PracticeAssignmentRequest,
|
PracticeAssignmentRequest,
|
||||||
DailyDrillAnswerResponse,
|
DailyDrillAnswerResponse,
|
||||||
PracticeAlert,
|
PracticeAlert,
|
||||||
PromotionEvidence
|
PromotionEvidence,
|
||||||
|
PracticeScenarioOption
|
||||||
} from '@/types/api';
|
} from '@/types/api';
|
||||||
import { apiRequest } from './api';
|
import { apiRequest } from './api';
|
||||||
import { getAuth, getSelectedPosition } from './auth';
|
import { getAuth, getSelectedPosition } from './auth';
|
||||||
@@ -35,6 +36,13 @@ export const getPracticePrepCard = (scenarioId = defaultScenarioId) =>
|
|||||||
timeout: 15000
|
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) =>
|
export const startPractice = (scenarioId = defaultScenarioId, assignmentId?: number) =>
|
||||||
apiRequest<PracticeStartResponse>({
|
apiRequest<PracticeStartResponse>({
|
||||||
url: '/api/train/practice/start',
|
url: '/api/train/practice/start',
|
||||||
|
|||||||
@@ -30,13 +30,18 @@ export interface TeamProfile {
|
|||||||
risk: 'high' | 'medium' | 'low';
|
risk: 'high' | 'medium' | 'low';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const supervisorScenarioOptions = [
|
export interface SupervisorScenarioOption {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const supervisorScenarioOptions: SupervisorScenarioOption[] = [
|
||||||
{ id: 'fee-parking', name: '催费沟通' },
|
{ id: 'fee-parking', name: '催费沟通' },
|
||||||
{ id: 'complaint-water', name: '投诉接待' },
|
{ id: 'complaint-water', name: '投诉接待' },
|
||||||
{ id: 'repair-elevator-delay', name: '电梯故障' }
|
{ id: 'repair-elevator-delay', name: '电梯故障' }
|
||||||
] as const;
|
];
|
||||||
|
|
||||||
export type SupervisorScenarioId = typeof supervisorScenarioOptions[number]['id'];
|
export type SupervisorScenarioId = string;
|
||||||
|
|
||||||
export interface AsyncGenerationGuard {
|
export interface AsyncGenerationGuard {
|
||||||
capture: () => number;
|
capture: () => number;
|
||||||
|
|||||||
@@ -252,6 +252,14 @@ export interface PracticeTeamSnapshot {
|
|||||||
mistakes: PracticeMistakeAggregate[];
|
mistakes: PracticeMistakeAggregate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PracticeScenarioOption {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
position?: string;
|
||||||
|
scenarioType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReviewDialogue {
|
export interface ReviewDialogue {
|
||||||
role: string;
|
role: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -524,6 +524,9 @@ contains backend/ruoyi-admin/src/main/resources/application-dev.yml "allow-legac
|
|||||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "instr(coalesce(person_name, '')"
|
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "instr(coalesce(person_name, '')"
|
||||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "maskSensitiveText"
|
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "maskSensitiveText"
|
||||||
contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/practice/assignments"
|
contains mobile-uni/src/services/practice.ts "/api/aihr/mobile/practice/assignments"
|
||||||
|
contains mobile-uni/src/services/practice.ts "/api/train/practice/scenarios?position="
|
||||||
|
contains mobile-uni/src/pages/supervisor/assign/index.vue "scenarioOptions"
|
||||||
|
contains mobile-uni/src/pages/supervisor/review/index.vue "scenarioOptions"
|
||||||
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/upload"
|
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/upload"
|
||||||
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/records?limit="
|
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/records?limit="
|
||||||
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/records/"
|
contains frontend/src/api/aihr/case.ts "/api/knowledge/case/records/"
|
||||||
|
|||||||
Reference in New Issue
Block a user