From baf27a5de932dacee6629fca771b277eb22663dc Mon Sep 17 00:00:00 2001 From: let5sne Date: Fri, 10 Jul 2026 00:04:39 +0800 Subject: [PATCH] feat(frontend): make growth assignments selectable --- docs/PC管理端功能差异TODO.md | 4 +-- frontend/src/views/competency/growth.vue | 42 ++++++++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/docs/PC管理端功能差异TODO.md b/docs/PC管理端功能差异TODO.md index 0967000c..75be7868 100644 --- a/docs/PC管理端功能差异TODO.md +++ b/docs/PC管理端功能差异TODO.md @@ -5,9 +5,9 @@ | 优先级 | 差异项 | 当前状态 | 本次实现 | 验收口径 | |---|---|---|---|---| -| P0 | 组织人员 / 岗位 / 项目权限运营页 | 组织同步 API 存在,侧栏未暴露运营视图 | 新增 `组织与权限` 页面,呈现组织人员快照、岗位映射、项目数据范围和同步预检入口 | PC 可查询人员快照,看到身份三轴、权限映射、同步结果边界 | +| P0 | 组织人员 / 岗位 / 项目权限运营页 | 已挂 `组织与权限` 页面,接组织快照查询、项目/岗位/层级/状态筛选、同步预检和质量提示 | 保留组织人员快照、岗位映射、项目数据范围和同步预检入口 | PC 可查询人员快照,看到身份三轴、权限映射、同步结果边界 | | P0 | 主管复盘 + 人工校准 + 派发训练闭环 | 已挂 `复盘校准` 页面,接真实复盘、评分校准、学分和派发接口 | 保留一页式处理:看记录、写建议、校准总分、给学分、派训练 | 主管能从一页完成“看记录 -> 写建议 -> 校准评分 -> 派训练” | -| P0 | 能力画像 / 等级证据包 / 激励规则 | 后端已有画像和证据包接口;PC 无独立页 | 新增 `成长激励` 页面,并用组织快照选择员工查看画像和派发专项 | HR 能看到初/中/高级证据和学分规则口径 | +| P0 | 能力画像 / 等级证据包 / 激励规则 | 已挂 `成长激励` 页面,接画像、晋升证据包、预警、待训任务和专项派发接口 | 在成长页选择员工、专项场景和派发原因后派训练;规则先展示最小运营口径 | HR 能看到初/中/高级证据和学分规则口径,并能按场景派发专项 | | P1 | 学模块课程 / 考试后台 | 文档要求阶段二建设;当前无页面 | 暂不实现,保留 TODO | 阶段二专项设计后再建课程、课件、考试、成绩表 | | P1 | 大喇叭文件分发 | 总纲阶段二;当前无页面 | 暂不实现,保留 TODO | 半年会后按身份三轴做摘要、已读未读、补推 | | P1 | 开放问题榜 | 阶段一只埋 `knowledge_gap`;当前无 UI | 暂不实现,保留 TODO | 阶段二消费 gap 池、上榜、最佳答案、沉淀入库 | diff --git a/frontend/src/views/competency/growth.vue b/frontend/src/views/competency/growth.vue index 678f8a2c..d9babba1 100644 --- a/frontend/src/views/competency/growth.vue +++ b/frontend/src/views/competency/growth.vue @@ -107,6 +107,12 @@

待训任务

派发专项 +
+ + + + +
@@ -136,7 +142,7 @@ import { Download, Refresh } from '@element-plus/icons-vue'; import { ElMessage } from 'element-plus'; import FileSaver from 'file-saver'; import { computed, onMounted, ref, watch } from 'vue'; -import { listPracticeScenarios } from '@/api/aihr/practice'; +import { listPracticeScenarios, type PracticeScenarioResponse } from '@/api/aihr/practice'; import { createPracticeAssignment, getCompetencyProfile, @@ -160,6 +166,9 @@ const evidence = ref(null); const alerts = ref([]); const assignments = ref([]); const orgSnapshot = ref(null); +const scenarios = ref([]); +const assignmentScenarioId = ref(''); +const assignmentReason = ref('HR在成长激励页派发专项训练'); const rules = [ { source: '训练完成', points: '+5', guard: '只认完成记录,不按点击或打开计分' }, @@ -206,22 +215,34 @@ const loadAll = async () => { } }; +const loadScenarios = async () => { + try { + scenarios.value = ((await listPracticeScenarios()).data || []).filter((item) => item.enabled); + assignmentScenarioId.value = assignmentScenarioId.value || scenarios.value[0]?.id || ''; + } catch (error) { + scenarios.value = []; + } +}; + const createManualAssignment = async () => { const party = extPartyId.value.trim() || evidence.value?.extPartyId || ''; if (!party) { ElMessage.warning('请先输入员工手机号或 extPartyId'); return; } + const scenario = scenarios.value.find((item) => item.id === assignmentScenarioId.value); + if (!scenario) { + ElMessage.warning('请先选择专项场景'); + return; + } try { - const scenarios = (await listPracticeScenarios()).data || []; - const scenario = scenarios.find((item) => item.enabled) || scenarios[0]; await createPracticeAssignment({ extPartyId: party, traineeName: selectedPerson.value?.personName || party, - scenarioId: scenario?.id || 'fee-parking', - scenarioName: scenario?.name, + scenarioId: scenario.id, + scenarioName: scenario.name, source: 'manual', - reason: 'HR在成长激励页派发专项训练' + reason: assignmentReason.value.trim() || 'HR在成长激励页派发专项训练' }); ElMessage.success('专项训练已派发'); await loadAll(); @@ -279,6 +300,7 @@ watch(extPartyId, () => { onMounted(() => { loadOrgSnapshot(); + loadScenarios(); loadAll(); }); @@ -374,6 +396,13 @@ onMounted(() => { } } +.assignment-form { + display: grid; + grid-template-columns: minmax(180px, 0.75fr) minmax(220px, 1fr); + gap: 10px; + margin-bottom: 12px; +} + .dimension-list, .stage-list, .alert-list { @@ -431,6 +460,7 @@ onMounted(() => { @media (max-width: 1080px) { .page-head, .main-grid, + .assignment-form, .metric-grid { grid-template-columns: 1fr; }