From 4d20c27de43adfd99bd96a165d57e8aba53486f6 Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Mon, 19 Oct 2026 10:33:40 +0800 Subject: [PATCH] =?UTF-8?q?wip(strategy-map):=20V1+V3=20=E5=90=88=E7=89=88?= =?UTF-8?q?=20+=20v2/v3=20=E5=8F=A3=E5=BE=84=EF=BC=88=E5=AE=8C=E5=A4=87?= =?UTF-8?q?=E5=BA=A6/C1=20hold/57=20=E5=BD=92=E6=A1=A3=E7=BB=84=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/map_completeness.py | 176 +++++ backend/app/api/maps.py | 25 +- backend/app/data/__init__.py | 1 + backend/app/data/caliber_map_completeness.py | 72 ++ backend/app/data/revenue_caliber.py | 63 ++ backend/app/main.py | 3 +- frontend/components.d.ts | 5 + frontend/src/api/index.ts | 3 + .../strategy-map/MapCompleteness.vue | 210 ++++++ .../components/strategy-map/MapEditBar.vue | 170 +++++ .../src/components/strategy-map/MapNode.vue | 38 +- .../components/strategy-map/MapNodeDetail.vue | 338 ++++++++++ .../components/strategy-map/MapSummaryBar.vue | 129 ++++ .../components/strategy-map/MapTargetTree.vue | 297 +++++++++ .../components/strategy-map/StrategyLayer.vue | 57 +- frontend/src/views/MapCanvas.vue | 618 ++++++++++++++++-- 16 files changed, 2116 insertions(+), 89 deletions(-) create mode 100644 backend/app/api/map_completeness.py create mode 100644 backend/app/data/__init__.py create mode 100644 backend/app/data/caliber_map_completeness.py create mode 100644 backend/app/data/revenue_caliber.py create mode 100644 frontend/src/components/strategy-map/MapCompleteness.vue create mode 100644 frontend/src/components/strategy-map/MapEditBar.vue create mode 100644 frontend/src/components/strategy-map/MapNodeDetail.vue create mode 100644 frontend/src/components/strategy-map/MapSummaryBar.vue create mode 100644 frontend/src/components/strategy-map/MapTargetTree.vue diff --git a/backend/app/api/map_completeness.py b/backend/app/api/map_completeness.py new file mode 100644 index 00000000..75f1c37a --- /dev/null +++ b/backend/app/api/map_completeness.py @@ -0,0 +1,176 @@ +"""战略地图 · 数据完备度 API(口径唯一真源:CMA-CALIBER-MAP-COMPLETENESS-20261018 v1.0) + +主口径(对外唯一主数):仅地图 65(已发布)15/19 = 78.9%,判据 = 被 23-KPI 集挂接。 +附注口径(只作附注):全量 4 图 30/31 = 96.8%,判据 = 有KPI挂接(含归档图)。 +KPI 级:active 行 117(e1=73 / e2=44),按行计,与目标级分列、不相除。 + +所有汇总数一律运行时对库内数据现算;判据数据(23-KPI 挂接关系)见 +app/data/caliber_map_completeness.py。禁用组合(19/19、83.9%)在本模块无生成路径。 +""" +from typing import Optional + +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session + +from app.auth_middleware import require_auth +from app.data import caliber_map_completeness as cal +from app.database import get_db +from app.deps import get_entity_id +from app.models import KPIDefinition, MapObjective, StrategicMap + +router = APIRouter( + prefix="/api/cma/maps/completeness", + tags=["战略地图数据完备度"], + dependencies=[Depends(require_auth)], +) + + +def _pct(n: int, d: int) -> Optional[float]: + """占比只保留 1 位小数;分母为 0 返回 None(展示为「无数据」)。""" + if not d: + return None + return round(n * 100.0 / d, 1) + + +def _entity_scopes(db: Session, entity_id: int): + """返回(主口径地图, 同属主的地图列表)。主口径地图仅认 map_id=65 且属于该实体。""" + maps = db.query(StrategicMap).filter(StrategicMap.entity_id == entity_id).all() + primary = next((m for m in maps if m.id == cal.PRIMARY_MAP_ID), None) + return primary, maps + + +def _objective_rows(db: Session, maps): + """拉取给定地图的战略目标,并逐行标记 23-KPI 挂接 / 是否有 KPI。""" + map_ids = [m.id for m in maps] + if not map_ids: + return [], {} + title_by_map = {m.id: m.title for m in maps} + status_by_map = {m.id: (m.status or "") for m in maps} + objs = ( + db.query(MapObjective) + .filter(MapObjective.map_id.in_(map_ids)) + .order_by(MapObjective.map_id, MapObjective.sort_order, MapObjective.id) + .all() + ) + rows = [] + for o in objs: + kpis = o.kpis if isinstance(o.kpis, list) else [] + rows.append({ + "objective_id": o.id, + "map_id": o.map_id, + "map_title": title_by_map.get(o.map_id), + "map_status": status_by_map.get(o.map_id), + "layer": o.dimension_key, + "layer_label": cal.LAYER_LABELS.get(o.dimension_key, o.dimension_key), + "name": o.name, + "kpis": kpis, + "has_kpi": len(kpis) > 0, + "covered_23kpi": o.id in cal.LINKED_OBJECTIVES, + }) + return rows, title_by_map + + +@router.get("/summary") +def completeness_summary( + entity_id: int = Depends(get_entity_id), + db: Session = Depends(get_db), +): + """数据完备度总览:目标级(主数 + 附注)与 KPI 级(117 行)两层分列。""" + primary_map, maps = _entity_scopes(db, entity_id) + rows, _ = _objective_rows(db, maps) + + # ---- 目标级 · 主口径:仅地图 65,判据 = 被 23-KPI 集挂接 ---- + if primary_map is not None: + main_rows = [r for r in rows if r["map_id"] == cal.PRIMARY_MAP_ID] + main_total = len(main_rows) + main_covered = sum(1 for r in main_rows if r["covered_23kpi"]) + primary = { + "available": True, + "caliber": cal.PRIMARY_CALIBER, + "label": cal.PRIMARY_LABEL, + "map_id": primary_map.id, + "map_title": primary_map.title, + "map_status": primary_map.status, + "covered": main_covered, + "total": main_total, + "pct": _pct(main_covered, main_total), + "display": f"{main_covered}/{main_total}", + } + else: + primary = { + "available": False, + "caliber": cal.PRIMARY_CALIBER, + "label": cal.PRIMARY_LABEL, + "map_id": cal.PRIMARY_MAP_ID, + "covered": None, + "total": None, + "pct": None, + "display": "—", + } + + # ---- 目标级 · 附注口径:全量 4 图,判据 = 有KPI挂接(23-KPI 挂接 或 map_objectives.kpis 非空)---- + # 附注与主数同属地图 65 的账套(全量 4 图);无地图 65 的账套不适用,展示「—」而非 0。 + footnote_total = len(rows) + footnote_covered = sum(1 for r in rows if r["covered_23kpi"] or r["has_kpi"]) + footnote_available = primary_map is not None and bool(rows) + footnote = { + "available": footnote_available, + "caliber": cal.FOOTNOTE_CALIBER, + "label": cal.FOOTNOTE_LABEL, + "covered": footnote_covered if footnote_available else None, + "total": footnote_total if footnote_available else None, + "pct": _pct(footnote_covered, footnote_total) if footnote_available else None, + "display": f"{footnote_covered}/{footnote_total}" if footnote_available else "—", + } + + # ---- KPI 级:active 按行(e1=73 / e2=44,合计 117;去重编码 102 另册)---- + kpi_rows = ( + db.query(KPIDefinition) + .filter(KPIDefinition.status == "active") + .all() + ) + by_entity: dict = {} + codes = set() + for k in kpi_rows: + by_entity[k.entity_id] = by_entity.get(k.entity_id, 0) + 1 + codes.add(k.kpi_code) + kpi_level = { + "caliber": cal.KPI_CALIBER, + "label": cal.KPI_LABEL, + "total": len(kpi_rows), + "by_entity": [ + {"entity_id": eid, "count": cnt} for eid, cnt in sorted(by_entity.items()) + ], + "distinct_codes": len(codes), + "note": "按 kpi_code 去重为独立口径,不与行数相除或换算", + } + + return { + "caliber_id": cal.CALIBER_ID, + "caliber_version": cal.CALIBER_VERSION, + "entity_id": entity_id, + "applicable": primary_map is not None, + "scope_note": "主口径仅覆盖地图 65(已发布);附注口径含归档图,判据不同,不得互换算", + "objective_level": {"primary": primary, "footnote": footnote}, + "kpi_level": kpi_level, + } + + +@router.get("/objectives") +def completeness_objectives( + map_id: Optional[int] = None, + entity_id: int = Depends(get_entity_id), + db: Session = Depends(get_db), +): + """数据完备度清单(目标级逐行):供空态清单与补齐排期使用。""" + _, maps = _entity_scopes(db, entity_id) + if map_id is not None: + maps = [m for m in maps if m.id == map_id] + rows, _ = _objective_rows(db, maps) + return { + "caliber_id": cal.CALIBER_ID, + "caliber_version": cal.CALIBER_VERSION, + "entity_id": entity_id, + "caliber": cal.FOOTNOTE_CALIBER, + "rows": rows, + } diff --git a/backend/app/api/maps.py b/backend/app/api/maps.py index 6cb93cff..b2dfb688 100644 --- a/backend/app/api/maps.py +++ b/backend/app/api/maps.py @@ -16,6 +16,15 @@ import logging logger = logging.getLogger(__name__) +# ── C1 数据前置(改造单 v2 §3,硬约束)────────────────────────────── +# C_CUST_CONCENTRATION 的 69 个实测点含 2026-10 未来期与重复值;清理完成前: +# ① 相关目标节点显示「数据待核」、**不着色**(level=pending); +# ② 该 KPI 的实测值/达成率不得进入摘要条、节点详情、趋势图(96.3% 零出口)。 +# 清理单据落地后删除本表即可恢复;不要在别处另写白名单。 +DATA_HOLD_KPIS = { + "C_CUST_CONCENTRATION": "数据待核:C1 实测点含未来期与重复值,清理未完成", +} + router = APIRouter(prefix="/api/cma/maps", tags=["战略地图"], dependencies=[Depends(require_role("ceo", "finance"))], ) @@ -74,6 +83,18 @@ def list_maps(db: Session = Depends(get_db), entity_id: int = Depends(get_entity maps = db.query(StrategicMap).filter(StrategicMap.entity_id == entity_id).order_by(StrategicMap.updated_at.desc()).all() return {"data": [m_to_dict(m, db) for m in maps]} +@router.get("/strategy/revenue-caliber") +def strategy_revenue_caliber(entity_id: int = Depends(get_entity_id)): + """战略地图摘要条 · 收入口径状态(真数据铁律:未闭合不出单一汇总数)。 + + 2026-10-18 改造单 v2 §4:A1 收入口径未闭合前,前端只显示「口径待定」+ + 分业务位(酒类代销 / IT自营,amount=None ⇒ 显示「—/无数据」); + 后端不得擅自选总额/净额,也不得把两块合并成一个「营业收入」。 + """ + from app.data import revenue_caliber + return revenue_caliber.snapshot(entity_id) + + @router.post("") def create_map(data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)): # 空白地图默认四层空结构(避免dimensions=NULL导致加载报错) @@ -1026,7 +1047,9 @@ def get_map_review(map_id: int, level: Optional[str] = None, db: Session = Depen if level and kpi_def.kpi_level != level: continue lv = latest_values.get(kpi_def.id, {}) - actual = lv.get("actual_value") + # C1 数据前置:held KPI 的实测值一律不外泄(96.3% 零出口) + held = code in DATA_HOLD_KPIS + actual = None if held else lv.get("actual_value") # 多粒度目标优先: 月目标(多数KPI月度) → 兼容legacy target_value (OpenCode审查#5) target = kpi_def.target_monthly or kpi_def.target_value # 判断红黄绿灯(绿≥90% / 黄60-90% / 红<60%) diff --git a/backend/app/data/__init__.py b/backend/app/data/__init__.py new file mode 100644 index 00000000..e371b01d --- /dev/null +++ b/backend/app/data/__init__.py @@ -0,0 +1 @@ +"""口径判据数据包(战略地图数据完备度等,只读常量)。""" diff --git a/backend/app/data/caliber_map_completeness.py b/backend/app/data/caliber_map_completeness.py new file mode 100644 index 00000000..e1556c29 --- /dev/null +++ b/backend/app/data/caliber_map_completeness.py @@ -0,0 +1,72 @@ +"""战略地图数据完备度 · 口径定义(唯一真源引用,勿改判据/分母) + +口径 ID : CMA-CALIBER-MAP-COMPLETENESS-20261018 v1.0 +定义件 : /srv/hermes/mailbox/project/pending-tasks/_reports/ + caliber-strategy-map-completeness-20261018.md +裁定 : ASK-20261018-15 / 任总 2026-10-18T23:07:11「放行14和15」 + +本模块只放**判据数据**(23-KPI 挂接关系 + 来源指纹),不存任何汇总数: +覆盖率一律由 API 运行时对库内 `map_objectives` / `kpi_definitions` 现算。 +口径变更 = 重新立 ASK 并改本文件 + 定义件,禁止就地改分母。 +""" + +CALIBER_ID = "CMA-CALIBER-MAP-COMPLETENESS-20261018" +CALIBER_VERSION = "1.0" + +# 主口径:仅已发布地图 65(三年战略规划 2027-2029) +PRIMARY_MAP_ID = 65 +PRIMARY_CALIBER = "map65_23kpi_cover" +PRIMARY_LABEL = "地图65·已发布·判据=被23KPI集挂接" +FOOTNOTE_CALIBER = "map_all_kpi_attached" +FOOTNOTE_LABEL = "全量4图·含归档图·判据=有KPI挂接" +KPI_CALIBER = "active_kpi_rows" +KPI_LABEL = "KPI 行(active·按行)" + +# 来源指纹(定义件 §5,任何人可复现) +SOURCE_FILES = { + "map-objectives-dump.csv": + "3896963ee7aff0492073f9b61491a10b10f3b131484e1f1006655d2c2f9bda78", + "a2-linkage-23kpi.csv": + "4494313269938663a197fabecaee644a4f4c66c84a02b725527c07e917f63971", + "active-kpi-118.csv": + "e37dcd699a06a18d5de9aa5b543b7a48b6d7d12c3e4c440f21186ea870cda1c1", +} + +# 被 23-KPI 集挂接的战略目标(objective_id -> (map_id, name)) +# 来源 a2-linkage-23kpi.csv「挂接类型非空」逐行,26 条(全量 4 图)。 +LINKED_OBJECTIVES = { + 1020: (53, "营收增长"), + 1022: (53, "客户拓展"), + 1024: (53, "运营效率"), + 1025: (53, "数字化能力"), + 1021: (53, "成本与现金流"), + 1023: (53, "客户亲密"), + 20888: (57, "营收目标"), + 20889: (57, "建立渠道分级体系"), + 21013: (61, "营收目标"), + 21014: (61, "保障现金流安全"), + 21016: (61, "产品领先"), + 22556: (65, "营业收入"), + 22562: (65, "高贡献线收入占比"), + 22566: (65, "组合决策机制(负贡献四线退出转投)"), + 22571: (65, "贡献率思维全员培训"), + 22557: (65, "营业利润"), + 22563: (65, "重点客户回款率"), + 22567: (65, "月度经营闭环"), + 22572: (65, "渠道经理「选品+算账」能力认证"), + 22558: (65, "边际贡献率 CM"), + 22568: (65, "库存去化"), + 22573: (65, "CMA 月度经营驾驶舱"), + 22559: (65, "现金周转期 CCC"), + 22565: (65, "客户集中度"), + 22574: (65, "贡献率/CCC/净现金入考核与激励"), + 22570: (65, "数据地基(清脏+改瓶+智享通直连)"), +} + +# 分层标签(UI 用,与前端 LAYER_CONFIG 一致) +LAYER_LABELS = { + "finance": "财务", + "customer": "客户", + "process": "内部流程", + "learning": "学习成长", +} diff --git a/backend/app/data/revenue_caliber.py b/backend/app/data/revenue_caliber.py new file mode 100644 index 00000000..4dd8e2b9 --- /dev/null +++ b/backend/app/data/revenue_caliber.py @@ -0,0 +1,63 @@ +"""战略地图 · 收入口径(revenue caliber)状态 —— 单一事实源 + +口径来源:改造单 `project-20261018-cma-strategy-map-ux-revamp-fullstack` 增量 v2 §4。 +截至本文件落地(2026-10-18T23:00),A1 收入口径**尚未闭合**: + - 业务称谓「代理」≠ 会计判定(总额法 total / 净额法 net / 混合 mixed 未裁); + - 财务净额件与项目侧「三问」总额判定并存,正由 A1 补充单裁决。 + +因此本模块只声明**状态**与**业务线维度**,不存任何金额: + - 接口照常返回 `revenue_caliber` 与分业务位(amount=None + status=pending); + - 前端不得写死任一方法,不得把两块合并成一个「营业收入」; + - 口径闭合后只改本文件(status/caliber/decided_by/closed_at),并回填金额取数逻辑, + 不需要改前端。 + +铁律:未闭合前,页面与接口都**不得**输出单一汇总收入数。 +""" + +# 口径状态:unclosed / closed +STATUS = "unclosed" +CALIBER = None # closed 时取 "total" / "net" / "mixed" +CALIBER_LABEL = "口径待定" +DECIDED_BY = None # closed 时填裁决单/ASK 编号 +CLOSED_AT = None # ISO 时间 +REASON = ( + "A1 收入口径尚未闭合(截至 2026-10-18T23:00):业务称谓「代理」≠ 会计判定," + "总额法/净额法/混合法并存,待 A1 补充单裁决" +) + +# 分业务(不得合并;amount 由 closed 后的取数逻辑回填) +BUSINESS_LINES = [ + {"key": "liquor_agency", "label": "酒类代销"}, + {"key": "it_self", "label": "IT自营"}, +] + + +def snapshot(entity_id: int) -> dict: + """返回对外口径快照(无金额;未闭合时 amount 一律 None)。""" + return { + "entity_id": entity_id, + "status": STATUS, + "caliber": CALIBER, + "caliber_label": CALIBER_LABEL, + "caliber_options": [ + {"key": "total", "label": "总额法"}, + {"key": "net", "label": "净额法"}, + {"key": "mixed", "label": "混合法"}, + ], + "decided_by": DECIDED_BY, + "closed_at": CLOSED_AT, + "reason": REASON, + "businesses": [ + { + "key": b["key"], + "label": b["label"], + "amount": None, + "unit": "万元", + "status": "pending", + "missing_reason": "收入口径未闭合" if STATUS != "closed" else None, + } + for b in BUSINESS_LINES + ], + "allow_single_total": STATUS == "closed", + "note": "未闭合前不显示单一汇总数;分业务分列,不合并为「营业收入」", + } diff --git a/backend/app/main.py b/backend/app/main.py index 53b24c0d..bae6684d 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -7,7 +7,7 @@ from fastapi.responses import JSONResponse from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from dotenv import load_dotenv from app.database import init_db, get_db -from app.api import auth, kpis, kpi_governance, templates, maps, dashboard, data, alerts, ai_analysis, ai_suggestions, alert_rules, users, thresholds, notifications, permissions, action_plans, alignment, org, objectives, versions, budget, cost, predict, reports, security, knowledge, bot_bridge, bot_bridge_v2, bot_suggestions, lead, tenant, customer_dashboard, deviation_push, budget_generate, knowledge_articles, kpi_causality, data_quality, bi_reports, entities, bsc_layers, okr, okr_templates, subjects, driver_budget, bot_kpis, ontology, bot_iron_law, analysis_results, expenses, cash, tax_compliance, verify, growth_quality, products, data_classification, value_sources, zero_based, derivation_rules, cash_classify, cashflow_structure, cash_vouchers_json, ext_input_ledgers, treasury, doc_center +from app.api import auth, kpis, kpi_governance, templates, maps, dashboard, data, alerts, ai_analysis, ai_suggestions, alert_rules, users, thresholds, notifications, permissions, action_plans, alignment, org, objectives, versions, budget, cost, predict, reports, security, knowledge, bot_bridge, bot_bridge_v2, bot_suggestions, lead, tenant, customer_dashboard, deviation_push, budget_generate, knowledge_articles, kpi_causality, data_quality, bi_reports, entities, bsc_layers, okr, okr_templates, subjects, driver_budget, bot_kpis, ontology, bot_iron_law, analysis_results, expenses, cash, tax_compliance, verify, growth_quality, products, data_classification, value_sources, zero_based, derivation_rules, cash_classify, cashflow_structure, cash_vouchers_json, ext_input_ledgers, treasury, doc_center, map_completeness from app.utils.cache import clear_all as clear_cache, delete as delete_cache from scripts.erp_sync import run_sync as run_erp_sync from app.auth_middleware import require_auth, require_auth_401 @@ -56,6 +56,7 @@ app.include_router(action_plans.router) app.include_router(alignment.router) app.include_router(org.router) app.include_router(objectives.router) +app.include_router(map_completeness.router) # 战略地图数据完备度(口径 CMA-CALIBER-MAP-COMPLETENESS-20261018 v1.0) app.include_router(versions.router) app.include_router(budget.router) app.include_router(value_sources.router) diff --git a/frontend/components.d.ts b/frontend/components.d.ts index dead878e..6207002d 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -80,7 +80,12 @@ declare module 'vue' { KPIListView: typeof import('./src/components/KPIListView.vue')['default'] MapCanvasDialogs: typeof import('./src/components/map-canvas/MapCanvasDialogs.vue')['default'] MapCanvasToolbar: typeof import('./src/components/map-canvas/MapCanvasToolbar.vue')['default'] + MapCompleteness: typeof import('./src/components/strategy-map/MapCompleteness.vue')['default'] + MapEditBar: typeof import('./src/components/strategy-map/MapEditBar.vue')['default'] MapNode: typeof import('./src/components/strategy-map/MapNode.vue')['default'] + MapNodeDetail: typeof import('./src/components/strategy-map/MapNodeDetail.vue')['default'] + MapSummaryBar: typeof import('./src/components/strategy-map/MapSummaryBar.vue')['default'] + MapTargetTree: typeof import('./src/components/strategy-map/MapTargetTree.vue')['default'] MyDialog: typeof import('./src/components/MyDialog.vue')['default'] NodeEditDialog: typeof import('./src/components/strategy-map/NodeEditDialog.vue')['default'] OkrDecomposition: typeof import('./src/components/okr/OkrDecomposition.vue')['default'] diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index feaa64e1..2fac2664 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -75,6 +75,9 @@ export const mapApi = { versionRollback: (mapId: number, vid: number) => api.post(`/maps/${mapId}/versions/${vid}/rollback`), versionUpdateComment: (mapId: number, vid: number, comment: string) => api.put(`/maps/${mapId}/versions/${vid}`, { comment }), versionSnapshot: (mapId: number, comment?: string) => api.post(`/maps/${mapId}/versions/snapshot`, comment ? { comment } : {}), + // 数据完备度(口径 CMA-CALIBER-MAP-COMPLETENESS-20261018 v1.0 · 2026-10-18) + completenessSummary: () => api.get('/maps/completeness/summary'), + completenessObjectives: (params?: any) => api.get('/maps/completeness/objectives', { params }), } // 复盘记录持久化(大PDCA Act闭环 2026-08-27) diff --git a/frontend/src/components/strategy-map/MapCompleteness.vue b/frontend/src/components/strategy-map/MapCompleteness.vue new file mode 100644 index 00000000..5cdfd05c --- /dev/null +++ b/frontend/src/components/strategy-map/MapCompleteness.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/frontend/src/components/strategy-map/MapEditBar.vue b/frontend/src/components/strategy-map/MapEditBar.vue new file mode 100644 index 00000000..7f3c84d8 --- /dev/null +++ b/frontend/src/components/strategy-map/MapEditBar.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/frontend/src/components/strategy-map/MapNode.vue b/frontend/src/components/strategy-map/MapNode.vue index 1ae4b3ad..f8b64ef5 100644 --- a/frontend/src/components/strategy-map/MapNode.vue +++ b/frontend/src/components/strategy-map/MapNode.vue @@ -1,6 +1,7 @@ + + + + + + + + +
确定回滚到此版本?当前内容将被覆盖。
+
回滚会以所选版本覆盖当前画布内容,执行后页面自动刷新。
+ +
- +