Files
cma-management/frontend/src/components/strategy-map/MapEditBar.vue
T
wecom-fullstack 63a2bac424
ci/woodpecker/push/main Pipeline was successful
feat(maps): 画布聚焦减负(聚焦层/详情浮层/汇总条/目标树收起/跨层摘要)— C 方案
- MapCanvas: 层聚焦与回全景联动;右栏停用改自绘可拖详情浮层;目标树默认收起+localStorage 持久化;底部跨层关系摘要条;聚焦态只绘聚焦层同层连线
- StrategyLayer: 层标题可点击聚焦并标「聚焦中」;非聚焦层压缩为 ≤48px 单行摘要(层名—达成率/目标数/mini 色条)
- MapSummaryBar: 汇总条压缩 ≤44px、5 项同排;窄屏无横向溢出
- MapEditBar: 新增「全景 / L1–L4」聚焦切换 chip(内联 SVG,无 emoji)
- 只读渲染层改动:不写 canvas_data / map_connections,无新增写链路调用
2026-10-24 20:03:32 +08:00

197 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="map-editbar">
<div class="eb-left">
<el-input
:model-value="search"
class="eb-search"
size="small"
clearable
placeholder="搜索目标 / 负责人 / KPI"
@update:model-value="(v: string) => $emit('update:search', v)"
>
<template #prefix>
<svg class="eb-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<circle cx="11" cy="11" r="7" /><path d="M20 20l-3.5-3.5" />
</svg>
</template>
</el-input>
<div class="eb-chips" role="group" aria-label="状态筛选">
<button
v-for="opt in filterOptions"
:key="opt.value"
type="button"
class="eb-chip"
:class="{ on: filter === opt.value }"
@click="$emit('update:filter', opt.value)"
>{{ opt.label }}</button>
</div>
<div class="eb-chips eb-layers" role="group" aria-label="层筛选">
<button
v-for="cfg in layerConfigs"
:key="cfg.key"
type="button"
class="eb-chip layer-chip"
:class="{ off: !visibleLayers[cfg.key] }"
:title="visibleLayers[cfg.key] ? '隐藏该层' : '显示该层'"
@click="$emit('toggle-layer', cfg.key)"
>
<span class="layer-dot" :style="{ background: cfg.color }"></span>
{{ cfg.short }}
</button>
</div>
</div>
<div class="eb-right">
<el-dropdown trigger="click" @command="onAdd">
<el-button size="small" type="primary" plain>
<svg class="eb-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M12 5v14M5 12h14" />
</svg>
加目标
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="cfg in layerConfigs" :key="cfg.key" :command="cfg.key">
<span class="dd-dot" :style="{ background: cfg.color }"></span>{{ cfg.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button size="small" @click="$emit('connect')">
<svg class="eb-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M10 13a5 5 0 0 0 7 0l2-2a5 5 0 0 0-7-7l-1 1" />
<path d="M14 11a5 5 0 0 0-7 0l-2 2a5 5 0 0 0 7 7l1-1" />
</svg>
连因果
</el-button>
<el-button size="small" :disabled="!canUndo" @click="$emit('undo')" title="撤销上一步画布编辑">
<svg class="eb-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M9 14L4 9l5-5" /><path d="M4 9h10a6 6 0 0 1 0 12h-3" />
</svg>
撤销
</el-button>
<el-button size="small" @click="$emit('completeness')" title="数据完备度与清单(口径:地图65·23KPI判据)">
<svg class="eb-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M4 19V5" /><path d="M4 19h16" /><rect x="7" y="11" width="3" height="6" rx=".5" />
<rect x="12" y="7" width="3" height="10" rx=".5" /><rect x="17" y="13" width="3" height="4" rx=".5" />
</svg>
数据完备度
</el-button>
<el-button size="small" @click="$emit('fit')" title="缩放到适合窗口">
<svg class="eb-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" />
</svg>
回全景
</el-button>
<el-button size="small" text @click="$emit('toggle-tree')" :title="treeOpen ? '收起左侧目标树(画布全宽)' : '展开左侧目标树'">
{{ treeOpen ? '收起目标树' : '展开目标树' }}
</el-button>
<!-- C 方案:聚焦开关(全景 = 默认四层等宽) -->
<div class="eb-chips eb-focus" role="group" aria-label="聚焦层">
<button
type="button"
class="eb-chip"
:class="{ on: !focusLayer }"
title="四层全景(默认)"
@click="$emit('focus-layer', null)"
>全景</button>
<button
v-for="cfg in layerConfigs"
:key="'focus-' + cfg.key"
type="button"
class="eb-chip"
:class="{ on: focusLayer === cfg.key }"
:title="focusLayer === cfg.key ? '再次点击退出聚焦' : '聚焦' + cfg.label"
@click="$emit('focus-layer', focusLayer === cfg.key ? null : cfg.key)"
>
<span class="layer-dot" :style="{ background: cfg.color }"></span>
{{ cfg.short }}
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
search: string
filter: 'all' | 'risk' | 'nodata'
visibleLayers: Record<string, boolean>
layerConfigs: { key: string; label: string; short: string; color: string }[]
canUndo: boolean
treeOpen: boolean
/** C 方案聚焦层:null = 四层全景 */
focusLayer?: string | null
}>()
const emit = defineEmits<{
(e: 'update:search', v: string): void
(e: 'update:filter', v: 'all' | 'risk' | 'nodata'): void
(e: 'toggle-layer', key: string): void
(e: 'add-objective', key: string): void
(e: 'connect'): void
(e: 'undo'): void
(e: 'fit'): void
(e: 'toggle-tree'): void
(e: 'completeness'): void
(e: 'focus-layer', key: string | null): void
}>()
const filterOptions: { value: 'all' | 'risk' | 'nodata'; label: string }[] = [
{ value: 'all', label: '全部' },
{ value: 'risk', label: '只看风险' },
{ value: 'nodata', label: '只看无数据' },
]
function onAdd(key: string) {
emit('add-objective', key)
}
</script>
<style scoped>
.map-editbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
flex-shrink: 0;
padding: 8px 10px;
margin-bottom: 10px;
background: var(--bh-bg-card, #fff);
border: 1px solid var(--bh-border, #E5E7EB);
border-radius: 8px;
}
.eb-left, .eb-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.eb-search { width: 220px; }
.eb-ico { width: 14px; height: 14px; vertical-align: -2px; }
.eb-chips { display: inline-flex; align-items: center; gap: 4px; padding: 2px; background: #F6F7F9; border: 1px solid #E5E7EB; border-radius: 6px; }
.eb-chip {
border: 0;
background: transparent;
color: #475569;
font-size: 12px;
line-height: 20px;
padding: 2px 10px;
border-radius: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
transition: background .15s, color .15s;
}
.eb-chip:hover { color: #635BFF; }
.eb-chip.on { background: #635BFF; color: #fff; }
.eb-chip.off { color: #94A3B8; text-decoration: line-through; }
.layer-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.dd-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
</style>