Merge branch 'codex/brd-release-20260715' into codex/multi-tenant-knowledge-platform
# Conflicts: # AGENTS.md # README.md # backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java # backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java # backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java # backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrUploadQueueService.java # backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrOrgSyncServiceTest.java # backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java # backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrUploadQueueServiceTest.java # docs/20260708/uni-app用户侧前端重建计划.md # docs/API_INTEGRATION.md # docs/BRD_IMPLEMENTATION_AUDIT.md # docs/BRD_PRODUCTION_MIGRATION_RUNBOOK.md # docs/DEMO_ACCEPTANCE.md # docs/DEV_SETUP.md # docs/FIGMA_IMPLEMENTATION_MATRIX.md # docs/README.md # frontend/src/views/knowledge/processing.vue # mobile-uni/src/pages/supervisor/assign/index.vue # mobile-uni/src/pages/supervisor/index/index.vue # mobile-uni/src/pages/supervisor/profile/index.vue # mobile-uni/src/pages/supervisor/review/index.vue # mobile-uni/src/pages/supervisor/reviews/index.vue # mobile-uni/src/pages/supervisor/team/index.vue # mobile-uni/src/pages/user/cases/index.vue # mobile-uni/src/pages/user/practice/index.vue # mobile-uni/src/pages/user/profile/index.vue # mobile-uni/src/pages/user/sop/index.vue # mobile-uni/src/pages/user/today/index.vue # mobile-uni/src/types/api.ts # scripts/tests/aihr-schema-migrations.test.sh
This commit is contained in:
@@ -21,13 +21,18 @@ const {
|
||||
actionablePracticeAlerts,
|
||||
buildSupervisorMetrics,
|
||||
buildTeamProfiles,
|
||||
buildTrainingTrend,
|
||||
createAsyncGenerationGuard,
|
||||
filterReviewRecords,
|
||||
filterTeamProfiles,
|
||||
inferSupervisorScenarioId,
|
||||
isReviewEditable,
|
||||
isSupervisorAccessDenied,
|
||||
paginateTeamProfiles,
|
||||
reviewCompletionAction,
|
||||
supervisorScenarioOptions
|
||||
} = supervisorView;
|
||||
const { maskPhone } = await vite.ssrLoadModule('/src/services/auth.ts');
|
||||
|
||||
const assignPageSource = await readFile(
|
||||
new URL('../src/pages/supervisor/assign/index.vue', import.meta.url),
|
||||
@@ -152,6 +157,20 @@ test('低于 80 分为高风险,有待复盘为中风险,其余为低风险'
|
||||
assert.equal(profiles.find((item) => item.extPartyId === 'low')?.risk, 'low');
|
||||
});
|
||||
|
||||
test('待复盘列表支持全部、低分和待处理筛选', () => {
|
||||
const rows = [
|
||||
review({ id: 1, score: 68, status: '待复盘' }),
|
||||
review({ id: 2, score: 88, status: '待复盘' }),
|
||||
review({ id: 3, score: 92, status: '已复盘' })
|
||||
];
|
||||
|
||||
assert.equal(filterReviewRecords(rows, 'all').length, 3);
|
||||
assert.deepEqual(filterReviewRecords(rows, 'low').map((item) => item.id), [1]);
|
||||
assert.deepEqual(filterReviewRecords(rows, 'pending').map((item) => item.id), [1, 2]);
|
||||
assert.match(reviewsPageSource, /reviewFilters/);
|
||||
assert.match(reviewsPageSource, /visibleReviews/);
|
||||
});
|
||||
|
||||
test('主管页面共享三个业务场景及文本推荐规则', () => {
|
||||
assert.deepEqual(supervisorScenarioOptions.map((item) => item.id), [
|
||||
'fee-parking',
|
||||
@@ -159,7 +178,8 @@ test('主管页面共享三个业务场景及文本推荐规则', () => {
|
||||
'repair-elevator-delay'
|
||||
]);
|
||||
assert.match(practiceServiceSource, /getPracticeScenarios/);
|
||||
assert.match(assignPageSource, /v-for="item in scenarioOptions"/);
|
||||
assert.match(assignPageSource, /v-for="item in filteredScenarioOptions"/);
|
||||
assert.match(assignPageSource, /scenarioSearch/);
|
||||
assert.match(assignPageSource, /暂无可派发的已启用场景/);
|
||||
assert.match(reviewPageSource, /scenarioOptions/);
|
||||
assert.match(reviewPageSource, /scenarioOptions\.value = \[\]/);
|
||||
@@ -207,6 +227,20 @@ test('stable 提示不计入团队预警指标', () => {
|
||||
]).find((item) => item.key === 'alerts')?.value, 0);
|
||||
});
|
||||
|
||||
test('主管工作台展示真实训练趋势并压缩预警首屏密度', () => {
|
||||
const trend = buildTrainingTrend([
|
||||
review({ id: 1, time: '2026-07-15 09:00:00', score: 70 }),
|
||||
review({ id: 2, time: '2026-07-16 09:00:00', score: 82 }),
|
||||
review({ id: 3, time: '2026-07-17 09:00:00', score: 91 })
|
||||
]);
|
||||
|
||||
assert.deepEqual(trend.map((item) => item.value), [70, 82, 91]);
|
||||
assert.match(dashboardPageSource, /class="panel training-trend-panel"/);
|
||||
assert.match(dashboardPageSource, /trainingTrend/);
|
||||
assert.match(dashboardPageSource, /visibleAlerts/);
|
||||
assert.doesNotMatch(dashboardPageSource, /<text class="metric-desc"/);
|
||||
});
|
||||
|
||||
test('画像按时间显示最新训练,但任一未复盘低分仍保持高风险', () => {
|
||||
const profiles = buildTeamProfiles([
|
||||
review({
|
||||
@@ -245,6 +279,51 @@ test('组织快照中的稳定成员即使没有训练记录也保留在画像
|
||||
assert.equal(profiles[0].risk, 'low');
|
||||
});
|
||||
|
||||
test('团队画像搜索和分页不把 2316 名成员一次性渲染', () => {
|
||||
const profiles = Array.from({ length: 2316 }, (_, index) => ({
|
||||
key: `member-${index}`,
|
||||
extPartyId: `employee-${index}`,
|
||||
displayName: `员工 ${index}`
|
||||
}));
|
||||
|
||||
assert.equal(paginateTeamProfiles(profiles, 1, 20).items.length, 20);
|
||||
assert.equal(paginateTeamProfiles(profiles, 1, 20).pageCount, 116);
|
||||
assert.deepEqual(
|
||||
filterTeamProfiles(profiles, '员工 2315').map((item) => item.key),
|
||||
['member-2315']
|
||||
);
|
||||
assert.match(teamPageSource, /v-for="member in visibleTeamProfiles"/);
|
||||
assert.match(teamPageSource, /v-if="selectedMember" class="panel member-detail-panel"/);
|
||||
assert.match(teamPageSource, /<template v-else>[\s\S]*class="panel team-panel"/);
|
||||
});
|
||||
|
||||
test('员工和主管个人页只展示脱敏手机号', async () => {
|
||||
const [employeeProfile, supervisorProfile] = await Promise.all([
|
||||
readFile(new URL('../src/pages/user/profile/index.vue', import.meta.url), 'utf8'),
|
||||
readFile(new URL('../src/pages/supervisor/profile/index.vue', import.meta.url), 'utf8')
|
||||
]);
|
||||
|
||||
assert.equal(maskPhone('181368856126'), '181****6126');
|
||||
assert.equal(maskPhone('invalid'), 'invalid');
|
||||
assert.match(employeeProfile, /displayPhone/);
|
||||
assert.match(supervisorProfile, /displayPhone/);
|
||||
assert.doesNotMatch(employeeProfile, /\{\{ phone \|\|/);
|
||||
assert.doesNotMatch(supervisorProfile, /\{\{ phone \|\|/);
|
||||
});
|
||||
|
||||
test('主管个人工作台展示真实指标和三项业务入口', async () => {
|
||||
const source = await readFile(new URL('../src/pages/supervisor/profile/index.vue', import.meta.url), 'utf8');
|
||||
|
||||
assert.match(source, /\{\{ loading \? '--' : teamCount \}\}/);
|
||||
assert.match(source, /\{\{ loading \? '--' : pendingReviewCount \}\}/);
|
||||
assert.match(source, /\{\{ loading \? '--' : alertCount \}\}/);
|
||||
assert.match(source, /主管业务入口/);
|
||||
assert.match(source, /\/pages\/supervisor\/team\/index/);
|
||||
assert.match(source, /\/pages\/supervisor\/reviews\/index/);
|
||||
assert.match(source, /\/pages\/supervisor\/assign\/index/);
|
||||
assert.match(source, /getPracticeTeamSnapshot/);
|
||||
});
|
||||
|
||||
test('组织快照姓名不可读时用稳定身份标识展示', () => {
|
||||
const profiles = buildTeamProfiles([], [], [
|
||||
{ extPartyId: '13900001111', name: '???????' },
|
||||
@@ -297,6 +376,10 @@ test('复盘详情只允许待复盘提交并保留未保存草稿', () => {
|
||||
assert.match(reviewPageSource, /v-else class="panel review-complete"/);
|
||||
assert.match(reviewPageSource, /if \(!editable\.value\)[\s\S]*该记录已完成复盘/);
|
||||
assert.match(reviewPageSource, /onShow\(\(\) => \{[\s\S]*refreshAuthState\(\);[\s\S]*if \(!loggedIn\.value\) \{[\s\S]*accessDenied\.value = false;[\s\S]*clearDetail\(\);\s*return;\s*\}[\s\S]*if \(!detail\.value && !loading\.value\)[\s\S]*loadDetail\(\)/);
|
||||
assert.match(reviewPageSource, /训练摘要/);
|
||||
assert.match(reviewPageSource, /录音与逐句复盘/);
|
||||
assert.match(reviewPageSource, /SOP 依据与改进建议/);
|
||||
assert.match(reviewPageSource, /下一步行动/);
|
||||
});
|
||||
|
||||
test('复盘详情登录和完成导航不堆叠页面', () => {
|
||||
@@ -361,6 +444,20 @@ test('指派页通过团队快照在进入时校验主管权限', () => {
|
||||
assert.doesNotMatch(assignPageSource, /getPracticeAssignments/);
|
||||
});
|
||||
|
||||
test('指派表单和派发进度使用同页分段视图避免堆成长页', () => {
|
||||
assert.match(assignPageSource, /assignView/);
|
||||
assert.match(assignPageSource, /v-if="assignView === 'create'" class="panel assignment-form"/);
|
||||
assert.match(assignPageSource, /v-if="assignView === 'progress'" class="panel history-panel"/);
|
||||
assert.match(assignPageSource, /class="assign-tabs"/);
|
||||
assert.match(assignPageSource, /memberSuggestions/);
|
||||
assert.match(assignPageSource, /selectTarget/);
|
||||
assert.match(assignPageSource, /class="target-suggestions"/);
|
||||
assert.match(assignPageSource, /assignmentDueTime/);
|
||||
assert.match(assignPageSource, /assignmentGoal/);
|
||||
assert.match(assignPageSource, /assignmentRequirements/);
|
||||
assert.match(assignPageSource, /item\.dueTime/);
|
||||
});
|
||||
|
||||
test('指派页保留 H5 查询参数二次解码', () => {
|
||||
assert.match(assignPageSource, /decodeURIComponent\(value\)\.trim\(\)/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user