diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index fbc6be2e..d7046019 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -90,10 +90,10 @@ const iconMap: Record = { // 分组图标映射 — PDCA五组 const groupIcons: Record = { '基础数据': Document, - '规划与目标': DataBoard, - '执行与控制': Edit, - '监控与评价': TrendCharts, - '复盘与改进': DataAnalysis, + '🔵 P 规划与目标': DataBoard, + '🟢 D 执行与控制': Edit, + '🟡 C 监控与评价': TrendCharts, + '🔴 A 复盘与改进': DataAnalysis, '系统与支持': Tools, } diff --git a/frontend/src/permission.ts b/frontend/src/permission.ts index 240141a6..03510700 100644 --- a/frontend/src/permission.ts +++ b/frontend/src/permission.ts @@ -34,34 +34,34 @@ export const MENU_ITEMS: MenuItem[] = [ { path: '/data', label: '数据管理', icon: 'Connection', roles: ['ceo', 'finance', 'it'], group: '基础数据' }, // ── 规划与目标 ── - { path: '/my-dashboard', label: '我的工作台', icon: 'Monitor', roles: ['ceo', 'finance', 'business', 'it'], group: '规划与目标' }, - { path: '/maps', label: '战略地图', icon: 'DataBoard', roles: ['ceo', 'finance'], group: '规划与目标' }, - { path: '/budget', label: '预算管理', icon: 'Coin', roles: ['ceo', 'finance', 'business', 'it'], group: '规划与目标' }, - { path: '/alignment', label: '战略执行看板', icon: 'Opportunity', roles: ['ceo', 'finance', 'business', 'it'], group: '规划与目标' }, + { path: '/my-dashboard', label: '我的工作台', icon: 'Monitor', roles: ['ceo', 'finance', 'business', 'it'], group: '🔵 P 规划与目标' }, + { path: '/maps', label: '战略地图', icon: 'DataBoard', roles: ['ceo', 'finance'], group: '🔵 P 规划与目标' }, + { path: '/budget', label: '预算管理', icon: 'Coin', roles: ['ceo', 'finance', 'business', 'it'], group: '🔵 P 规划与目标' }, + { path: '/alignment', label: '战略执行看板', icon: 'Opportunity', roles: ['ceo', 'finance', 'business', 'it'], group: '🔵 P 规划与目标' }, // ══════════════════════════════════════════════════════════════ // GROUP 2: 执行与控制(Do) // ══════════════════════════════════════════════════════════════ - { path: '/cost', label: '成本分析', icon: 'Money', roles: ['ceo', 'finance', 'it'], group: '执行与控制' }, - { path: '/deviations', label: '差异分析', icon: 'DataAnalysis', roles: ['ceo', 'finance', 'business', 'it'], group: '执行与控制' }, - { path: '/action-plans', label: '改善行动', icon: 'Edit', roles: ['ceo', 'finance', 'business', 'it'], group: '执行与控制' }, + { path: '/cost', label: '成本分析', icon: 'Money', roles: ['ceo', 'finance', 'it'], group: '🟢 D 执行与控制' }, + { path: '/deviations', label: '差异分析', icon: 'DataAnalysis', roles: ['ceo', 'finance', 'business', 'it'], group: '🟢 D 执行与控制' }, + { path: '/action-plans', label: '改善行动', icon: 'Edit', roles: ['ceo', 'finance', 'business', 'it'], group: '🟢 D 执行与控制' }, // ══════════════════════════════════════════════════════════════ // GROUP 3: 监控与评价(Check) // ══════════════════════════════════════════════════════════════ - { path: '/dashboard', label: '经营看板', icon: 'DataBoard', roles: ['ceo', 'finance', 'business', 'it'], group: '监控与评价' }, - { path: '/reports', label: '管理报表', icon: 'DataAnalysis', roles: ['ceo', 'finance', 'business'], group: '监控与评价' }, - { path: '/dupont-analysis', label: '杜邦分析', icon: 'TrendCharts', roles: ['ceo', 'finance', 'it'], group: '监控与评价' }, - { path: '/customer', label: '客户维度', icon: 'User', roles: ['ceo', 'finance', 'business', 'it'], group: '监控与评价' }, - { path: '/learning-dashboard', label: '学习成长看板', icon: 'Reading', roles: ['ceo', 'finance', 'it'], group: '监控与评价' }, - { path: '/alerts', label: '预警中心', icon: 'WarningFilled', roles: ['ceo', 'finance', 'business', 'it'], group: '监控与评价' }, - { path: '/data-quality', label: '数据质量', icon: 'WarningFilled', roles: ['ceo', 'finance', 'it'], group: '复盘与改进' }, + { path: '/dashboard', label: '经营看板', icon: 'DataBoard', roles: ['ceo', 'finance', 'business', 'it'], group: '🟡 C 监控与评价' }, + { path: '/reports', label: '管理报表', icon: 'DataAnalysis', roles: ['ceo', 'finance', 'business'], group: '🟡 C 监控与评价' }, + { path: '/dupont-analysis', label: '杜邦分析', icon: 'TrendCharts', roles: ['ceo', 'finance', 'it'], group: '🟡 C 监控与评价' }, + { path: '/customer', label: '客户维度', icon: 'User', roles: ['ceo', 'finance', 'business', 'it'], group: '🟡 C 监控与评价' }, + { path: '/learning-dashboard', label: '学习成长看板', icon: 'Reading', roles: ['ceo', 'finance', 'it'], group: '🟡 C 监控与评价' }, + { path: '/alerts', label: '预警中心', icon: 'WarningFilled', roles: ['ceo', 'finance', 'business', 'it'], group: '🟡 C 监控与评价' }, + { path: '/data-quality', label: '数据质量', icon: 'WarningFilled', roles: ['ceo', 'finance', 'it'], group: '🔴 A 复盘与改进' }, // ══════════════════════════════════════════════════════════════ // GROUP 4: 复盘与改进(Act) // ══════════════════════════════════════════════════════════════ - { path: '/maps-review', label: '战略回顾会', icon: 'TrendCharts', roles: ['ceo', 'finance'], group: '复盘与改进' }, - { path: '/predict', label: '预测模拟', icon: 'DataLine', roles: ['ceo', 'finance', 'it'], group: '复盘与改进' }, - { path: '/real-options', label: '实物期权计算器', icon: 'TrendCharts', roles: ['ceo', 'finance'], group: '复盘与改进' }, + { path: '/maps-review', label: '战略回顾会', icon: 'TrendCharts', roles: ['ceo', 'finance'], group: '🔴 A 复盘与改进' }, + { path: '/predict', label: '预测模拟', icon: 'DataLine', roles: ['ceo', 'finance', 'it'], group: '🔴 A 复盘与改进' }, + { path: '/real-options', label: '实物期权计算器', icon: 'TrendCharts', roles: ['ceo', 'finance'], group: '🔴 A 复盘与改进' }, // ══════════════════════════════════════════════════════════════ // GROUP 5: 系统与支持(Infra) diff --git a/frontend/src/views/MapReview.vue b/frontend/src/views/MapReview.vue index 1623582c..79284c57 100644 --- a/frontend/src/views/MapReview.vue +++ b/frontend/src/views/MapReview.vue @@ -19,6 +19,136 @@ + +
+ + + 🔵 战略回顾会(季度) + + + 🟢 运营复盘会(月度) + + + + 议程:①战略地图O检查 ②因果链验证 ③O调整 ④下季度优先级 + + + 议程:①OKR进度检查 ②KPI达标情况 ③偏差分析 ④改善行动确认 + +
+ + + + + + +
@@ -179,6 +309,12 @@ const showKpiDialog = ref(false) const selectedObjName = ref("") const selectedKpis = ref([]) +// ── 双会议模板状态 ── +const meetingType = ref("strategic") +const deviations = ref([]) +const krs = ref([]) +const loadingDeviations = ref(false) + // ── 计算属性 ── const pendingCount = () => actionPlans.value.filter((p:any) => p.status === 'pending').length const inProgressCount = () => actionPlans.value.filter((p:any) => p.status === 'in_progress').length @@ -196,6 +332,9 @@ async function loadReview() { dimensions.value = r.dimensions || [] focusItems.value = r.focus_items || [] actionPlans.value = r.action_plans || [] + // 联动加载偏差数据 + await loadDeviations() + await loadKrs() } catch (e: any) { ElMessage.error("加载战略回顾数据失败") } finally { @@ -203,6 +342,37 @@ async function loadReview() { } } +// ── 加载偏差数据(从差异分析模块联动) ── +async function loadDeviations() { + loadingDeviations.value = true + try { + const r: any = await api.get('/deviations', { params: { hierarchical: true, page_size: 20 } }) + const data = r.data || r.items || r || [] + deviations.value = Array.isArray(data) ? data : [] + } catch (_) { + deviations.value = [] + } finally { + loadingDeviations.value = false + } +} + +// ── 加载KR进度数据 ── +async function loadKrs() { + try { + const r: any = await api.get('/maps/' + selectedMap.value + '/krs') + krs.value = r.data || r || [] + } catch (_) { + krs.value = [] + } +} + +// ── 会议类型切换 ── +function onMeetingTypeChange() { + if (selectedMap.value) { + loadReview() + } +} + // ── 工具函数 ── function badgeIcon(level: string) { return level === 'green' ? '🟢' : level === 'yellow' ? '🟡' : level === 'red' ? '🔴' : '⚪' @@ -350,4 +520,45 @@ onMounted(async () => { .kpi-detail-item { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 13px; } .kpi-detail-item .label { color: #999; width: 60px; } .kpi-detail-item .val { font-weight: 500; } + +/* ── 双会议模板 ── */ +.meeting-type-bar { display: flex; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 8px; } +.op-agenda-cards, +.linkage-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 24px; } +.agenda-card, +.linkage-card { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); border: 1px solid #f0f0f0; } +.agenda-card-title, +.linkage-card-title { font-size: 15px; font-weight: 600; margin-bottom: 12px; color: #333; padding-bottom: 8px; border-bottom: 2px solid #f0f2f5; } +.agenda-card-body, +.linkage-card-body { font-size: 13px; } +.agenda-dim-row, +.linkage-dim-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid #f5f5f5; } +.agenda-dim-row:last-child, +.linkage-dim-row:last-child { border-bottom: none; } +.agenda-dim-icon, +.linkage-dim-icon { font-size: 16px; } +.agenda-dim-name, +.linkage-dim-name { flex: 1; } +.agenda-dim-count { color: #999; font-size: 12px; } +.linkage-dim-info { flex: 1; display: flex; justify-content: space-between; align-items: center; } +.linkage-dim-ratio { color: #409EFF; font-size: 12px; font-weight: 500; } +.kpi-summary-row { padding: 4px 0; font-size: 13px; display: flex; align-items: center; gap: 6px; } +.deviation-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 13px; } +.deviation-row:last-child { border-bottom: none; } +.deviation-badge { font-size: 12px; flex-shrink: 0; } +.deviation-kpi { flex: 1; color: #333; } +.deviation-rate { font-weight: 500; color: #e6a23c; } +.badge-red .deviation-rate { color: #f56c6c; } +.kr-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid #f5f5f5; } +.kr-row:last-child { border-bottom: none; } +.kr-name { flex: 1; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.action-plan-preview { margin-top: 8px; border-top: 1px solid #f0f0f0; padding-top: 8px; } +.ap-row { display: flex; align-items: center; gap: 6px; padding: 4px 0; font-size: 12px; } +.ap-status { flex-shrink: 0; } +.ap-title { flex: 1; color: #666; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.no-data { color: #999; padding: 12px; text-align: center; font-size: 13px; } +@media screen and (max-width: 768px) { + .op-agenda-cards, + .linkage-cards { grid-template-columns: 1fr; } +}