diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java
index 080639a7..6569ccab 100644
--- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java
+++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java
@@ -1438,6 +1438,7 @@ public class AihrPracticeSeedService {
COALESCE(ROUND(AVG(dim_compliance)), 0) compliance,
COALESCE(ROUND(AVG(dim_communication)), 0) communication,
COALESCE(ROUND(AVG(dim_emotion)), 0) emotion,
+ COALESCE(ROUND(AVG(dim_marketing)), 0) marketing,
COALESCE(SUM(incentive_point), 0) incentive_points
FROM aihr_practice_session
WHERE tenant_id = ? AND mode = 'mobile' AND (? = '' OR ext_party_id = ?)
@@ -1448,9 +1449,10 @@ public class AihrPracticeSeedService {
rs.getInt("compliance"),
rs.getInt("communication"),
rs.getInt("emotion"),
+ rs.getInt("marketing"),
rs.getInt("incentive_points")
), tenantId(), party, party);
- CompetencySnapshot snapshot = rows.isEmpty() ? new CompetencySnapshot(0, 0, 0, 0, 0, 0, 0) : rows.get(0);
+ CompetencySnapshot snapshot = rows.isEmpty() ? new CompetencySnapshot(0, 0, 0, 0, 0, 0, 0, 0) : rows.get(0);
int trainingMinutes = snapshot.completed() * 8;
int contribution = snapshot.completed() == 0 && snapshot.incentivePoints() == 0
? 0
@@ -1466,9 +1468,16 @@ public class AihrPracticeSeedService {
assessmentScore,
aiLevel,
List.of(
- new DimensionResponse("合规", snapshot.compliance(), "SOP关键点覆盖"),
- new DimensionResponse("沟通", snapshot.communication(), "承诺与表达清晰度"),
- new DimensionResponse("情绪", snapshot.emotion(), "安抚与承接能力")
+ new DimensionResponse("任务完成度", null,
+ "权重40%;需接入工单闭环率、首解率与处理结果,当前待采集"),
+ new DimensionResponse("话术规范性", snapshot.completed() == 0 ? null : average(snapshot.compliance(), snapshot.communication()),
+ "权重25%;当前以合规+沟通训练维度代理,正式绩效口径需HR确认"),
+ new DimensionResponse("情绪管理能力", snapshot.completed() == 0 ? null : snapshot.emotion(),
+ "权重20%;当前以陪练情绪维度代理"),
+ new DimensionResponse("响应时效", null,
+ "权重10%;需接入首响时长、处理周期与跟进频率,当前待采集"),
+ new DimensionResponse("增值转化潜力", snapshot.completed() == 0 ? null : snapshot.marketing(),
+ "权重5%;当前以陪练营销维度代理,正式口径需HR确认")
),
growthPath(snapshot.completed(), assessmentScore)
);
@@ -3546,7 +3555,14 @@ public class AihrPracticeSeedService {
private record RoundSeed(String customer, String coachHint, Integer emotion, Integer trust) {
}
- private record CompetencySnapshot(Integer completed, Integer score, Integer pendingReview, Integer compliance, Integer communication, Integer emotion, Integer incentivePoints) {
+ private Integer average(Integer left, Integer right) {
+ if (left == null || right == null) {
+ return null;
+ }
+ return Math.round((left + right) / 2.0f);
+ }
+
+ private record CompetencySnapshot(Integer completed, Integer score, Integer pendingReview, Integer compliance, Integer communication, Integer emotion, Integer marketing, Integer incentivePoints) {
}
private record SessionAnnotationSource(String scenarioId, String dialogueJson, String annotationsJson) {
diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java
index 85848fb4..2826942e 100644
--- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java
+++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java
@@ -33,6 +33,7 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -464,6 +465,7 @@ public class AihrPracticeSeedServiceTest {
"compliance", "0",
"communication", "0",
"emotion", "0",
+ "marketing", "0",
"incentive_points", "0"
)));
AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null);
@@ -476,7 +478,10 @@ public class AihrPracticeSeedServiceTest {
assertEquals(0, profile.trainingMinutes());
assertEquals(0, profile.contribution());
assertEquals("未评级", profile.aiLevel());
- assertTrue(profile.dimensions().stream().allMatch(item -> Integer.valueOf(0).equals(item.value())));
+ assertEquals(5, profile.dimensions().size());
+ assertTrue(profile.dimensions().stream().allMatch(item -> item.value() == null));
+ assertEquals("任务完成度", profile.dimensions().get(0).label());
+ assertEquals("响应时效", profile.dimensions().get(3).label());
assertTrue(profile.growthPath().stream().allMatch(item -> Integer.valueOf(0).equals(item.progress())));
}
@@ -493,6 +498,7 @@ public class AihrPracticeSeedServiceTest {
"compliance", "82",
"communication", "80",
"emotion", "78",
+ "marketing", "74",
"incentive_points", "20"
)));
AihrPracticeSeedService service = new AihrPracticeSeedService(new ObjectMapper(), jdbcTemplate, null, null);
@@ -506,6 +512,11 @@ public class AihrPracticeSeedServiceTest {
assertEquals("待积累", profile.growthPath().get(1).status());
assertEquals(90, profile.growthPath().get(2).progress());
assertEquals("待积累", profile.growthPath().get(2).status());
+ assertEquals(81, profile.dimensions().get(1).value());
+ assertEquals(78, profile.dimensions().get(2).value());
+ assertEquals(74, profile.dimensions().get(4).value());
+ assertNull(profile.dimensions().get(0).value());
+ assertNull(profile.dimensions().get(3).value());
}
@SuppressWarnings({"unchecked", "rawtypes"})
diff --git a/docs/API_INTEGRATION.md b/docs/API_INTEGRATION.md
index 5c350156..63bb2525 100644
--- a/docs/API_INTEGRATION.md
+++ b/docs/API_INTEGRATION.md
@@ -139,7 +139,7 @@ curl -fsS "$API_BASE/api/aihr/org/sync" \
- 模型配置 API 文件:`frontend/src/api/aihr/model.ts`
- 模型配置页面:`frontend/src/views/system/model/index.vue`
- 用户侧工程:`mobile-uni/`,路由为 `/h5/#/pages/user/today/index`、`/h5/#/pages/user/practice/index`、`/h5/#/pages/user/sop/index`、`/h5/#/pages/user/profile/index`、`/h5/#/pages/candidate/index/index`、`/h5/#/pages/supervisor/index/index`。未登录先走手机号短信登录,登录后 API 优先请求真实业务接口,不复用后台页面路由。语音输入不要新增移动端专用接口,浏览器录音和文件选择兜底都复用 `/api/ai/asr`。
-- 员工端训练:底部“练”进入 `/h5/#/pages/user/practice/index`,展示今日任务、每日三题、专项训练、训练前预习卡和训练对话;每日三题支持文字或录音转写,作答接口 `/api/aihr/mobile/practice/assignments/{id}/answer` 可携带 `answer/audioUrl/audioOssId`,语音地址会随作业记录保存;完成后记录写入 `aihr_practice_session`,员工端可提交 1-5 分训练体验和可选意见,员工端展示训练历史和能力画像,主管端展示待复盘列表,点进单条可看评分、话术、导师改写,并可标记“已复盘”。
+- 员工端训练:底部“练”进入 `/h5/#/pages/user/practice/index`,展示今日任务、每日三题、专项训练、训练前预习卡和训练对话;每日三题支持文字或录音转写,作答接口 `/api/aihr/mobile/practice/assignments/{id}/answer` 可携带 `answer/audioUrl/audioOssId`,语音地址会随作业记录保存;完成后记录写入 `aihr_practice_session`,员工端可提交 1-5 分训练体验和可选意见,员工端展示训练历史和能力画像,主管端展示待复盘列表,点进单条可看评分、话术、导师改写,并可标记“已复盘”。能力画像按 BRD 五维返回任务完成度、话术规范性、情绪管理能力、响应时效、增值转化潜力;暂无工单/时效数据的维度返回 `null`,前端显示“待采集”,不把训练次数当正式绩效指标。
- 候选人端:`/h5/#/pages/candidate/index/index` 的“开始面试/面试练习”复用 AI 面试 API;“补充资料”走候选资料上传接口,成功后展示资料类型和审核状态。管理端 `/recruit/interview` 底部展示“候选资料审核”,支持通过/驳回。
- 演示 fallback 只作为未配置模型或外部接口异常时的降级,不作为真实测试通过证据。
diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md
index 5cfc0a80..a055a686 100644
--- a/docs/BRD_IMPLEMENTATION_AUDIT.md
+++ b/docs/BRD_IMPLEMENTATION_AUDIT.md
@@ -142,4 +142,5 @@
- 2026-07-14 G3 案例读取权限复核:发现案例列表和详情对任意已登录后台系统用户返回当前租户数据,存在普通后台账号横向读取案例资产的风险;现收紧为 `superadmin`/`hr_operator`,APP 用户保留登录后的项目范围读取。新增源码契约测试,AIHR 全量测试 `57/57`、脚本语法和空白检查通过,修复提交为 `b0d24970`,尚未发布生产。
- 2026-07-14 G3 驾驶舱权限复核:发现管理驾驶舱只要求登录,APP 用户或普通后台账号理论上可读取当前租户的组织、训练、知识库、案例和候选人汇总;现收紧为 `superadmin`/`hr_operator`,主管团队数据继续走移动端项目范围接口。新增源码契约测试,AIHR 全量测试 `58/58`、脚本语法和空白检查通过,修复提交为 `7522794d`,尚未发布生产。
- 2026-07-14 后续 BRD P1 复核:认证/晋升规则、岗位-SOP 资格 gating、方言样本、正式案例视频样片与组织项目范围仍依赖 HR/运维输入;当前不继续用硬编码、演示 seed 或新增页面伪造完成,下一批优先接正式规则与试点数据后再实现。
+- 2026-07-14 BRD 4.5.1 五维能力画像对齐:员工端与管理端画像现在统一返回“任务完成度、话术规范性、情绪管理能力、响应时效、增值转化潜力”五项;话术规范性由现有合规+沟通陪练分数作为训练代理,情绪和增值转化分别由陪练情绪/营销分代理。任务完成度、响应时效因尚未接入工单闭环、首解率、首响时长和处理周期,明确返回“待采集”,不再用训练次数或综合分伪造 BRD 绩效指标;正式权重和绩效口径仍需 HR 确认。
- 2026-07-14 只读发布预检:当前提交 `07557656` 工作区干净,本地管理端、`mobile-uni` H5 与后端 jar 均有产物;生产根站点与租户接口返回 200。本轮权限修复尚未执行生产同步、服务重启或业务数据写入,不能把本地提交视为线上已生效。
diff --git a/frontend/src/api/aihr/mobile-admin.ts b/frontend/src/api/aihr/mobile-admin.ts
index cccb30a3..fad5c7ce 100644
--- a/frontend/src/api/aihr/mobile-admin.ts
+++ b/frontend/src/api/aihr/mobile-admin.ts
@@ -45,7 +45,7 @@ export type CompetencyProfile = {
contribution: number;
assessmentScore: number;
aiLevel: string;
- dimensions: Array<{ label: string; value: number | string; note: string }>;
+ dimensions: Array<{ label: string; value: number | string | null; note: string }>;
growthPath: Array<{ level: string; progress: number; status: string; evidence: string }>;
};
@@ -91,7 +91,7 @@ export type PromotionEvidence = {
contribution: number;
completed: number;
pendingReview: number;
- dimensions: Array<{ label: string; value: number | string; note: string }>;
+ dimensions: Array<{ label: string; value: number | string | null; note: string }>;
latestRecords: ReviewRecord[];
assignments: PracticeAssignment[];
evidenceItems: string[];
diff --git a/frontend/src/views/competency/growth.vue b/frontend/src/views/competency/growth.vue
index b5cb5bf6..cb30e1b8 100644
--- a/frontend/src/views/competency/growth.vue
+++ b/frontend/src/views/competency/growth.vue
@@ -56,7 +56,13 @@
{{ item.label }}
{{ item.note }}
-
+
+ 待采集
@@ -422,6 +428,12 @@ onMounted(() => {
color: #667085;
}
+.dimension-item .dimension-unavailable {
+ margin-bottom: 8px;
+ color: #98a2b3;
+ font-weight: 600;
+}
+
.evidence-list {
margin: 0;
padding-left: 18px;
diff --git a/mobile-uni/src/pages/user/profile/index.vue b/mobile-uni/src/pages/user/profile/index.vue
index 1b01ed99..0190e82a 100644
--- a/mobile-uni/src/pages/user/profile/index.vue
+++ b/mobile-uni/src/pages/user/profile/index.vue
@@ -70,7 +70,9 @@
{{ item.label }}
{{ item.note }}
- {{ item.value }}
+
+ {{ item.value === null || item.value === undefined ? '待采集' : item.value }}
+
@@ -594,6 +596,11 @@ onShow(() => {
font-weight: 900;
}
+.dimension-score.pending {
+ color: var(--employee-muted);
+ font-size: 14px;
+}
+
.refresh-button {
min-height: 36px;
padding: 0 12px;
diff --git a/mobile-uni/src/types/api.ts b/mobile-uni/src/types/api.ts
index 4cae38da..4af58c98 100644
--- a/mobile-uni/src/types/api.ts
+++ b/mobile-uni/src/types/api.ts
@@ -468,7 +468,7 @@ export interface InterviewFinishResponse {
export interface CompetencyDimension {
label: string;
- value: number | string;
+ value: number | string | null;
note: string;
}
diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh
index 07bf4f73..450024b1 100755
--- a/scripts/demo-check.sh
+++ b/scripts/demo-check.sh
@@ -393,6 +393,8 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/control
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPromptTemplateController.java "/api/aihr/prompt-templates"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPromptTemplateController.java "public R> list"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "mobileCompletedCount"
+contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "任务完成度"
+contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "响应时效"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java "MistakeAggregateResponse"
contains mobile-uni/src/pages/supervisor/team/index.vue "团队错题本"
contains mobile-uni/src/types/api.ts "PracticeMistakeAggregate"