From 75d5a9bf64cbc1b0c4ab8293d2e286a37fafe15c Mon Sep 17 00:00:00 2001 From: Cyril Date: Fri, 17 Apr 2026 21:10:50 +0200 Subject: [PATCH] refactor(wizard): bifurcation MainView selon route.query.mode + i18n Private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MainView.vue lit route.query.mode (public|private) et bifurque après graph build - État Private remonté dans MainView (privateForm, privateSimStatus, timers...) - Cleanup timers sur onBeforeRouteLeave + onBeforeRouteUpdate + onUnmounted - currentStep reset sur changement de mode via watcher isPrivateMode - Step1GraphBuild reçoit mode prop ('public'|'private'), gate createSimulation+router.push en Public, emit('next-step') en Private - Ajout clés i18n public.stepNames, public.modeBadge, private.stepNames, private.modeBadge (EN + ZH) - Hardcode "PRIVATE IMPACT" et arrays privateStepNames/privateBreadcrumb migrés vers tm() Prompts N°24 + N°27 — Roadmap refactoring wizard Co-Authored-By: Claude Opus 4.7 --- frontend/src/components/Step1GraphBuild.vue | 17 +- frontend/src/views/MainView.vue | 802 ++++++++++++++++++-- locales/en.json | 8 + locales/zh.json | 8 + 4 files changed, 751 insertions(+), 84 deletions(-) diff --git a/frontend/src/components/Step1GraphBuild.vue b/frontend/src/components/Step1GraphBuild.vue index 687d1c7b..0debce9a 100644 --- a/frontend/src/components/Step1GraphBuild.vue +++ b/frontend/src/components/Step1GraphBuild.vue @@ -201,10 +201,11 @@ const props = defineProps({ ontologyProgress: Object, buildProgress: Object, graphData: Object, - systemLogs: { type: Array, default: () => [] } + systemLogs: { type: Array, default: () => [] }, + mode: { type: String, default: 'public' } }) -defineEmits(['next-step']) +const emit = defineEmits(['next-step']) const selectedOntologyItem = ref(null) const logContent = ref(null) @@ -216,9 +217,15 @@ const handleEnterEnvSetup = async () => { console.error('缺少项目或图谱信息') return } - + + // Mode privé : pas de simulation publique, on passe directement à l'étape 2 (Requirement) + if (props.mode === 'private') { + emit('next-step') + return + } + creatingSimulation.value = true - + try { const res = await createSimulation({ project_id: props.projectData.project_id, @@ -226,7 +233,7 @@ const handleEnterEnvSetup = async () => { enable_twitter: true, enable_reddit: true }) - + if (res.success && res.data?.simulation_id) { // 跳转到 simulation 页面 router.push({ diff --git a/frontend/src/views/MainView.vue b/frontend/src/views/MainView.vue index 65749c20..45479371 100644 --- a/frontend/src/views/MainView.vue +++ b/frontend/src/views/MainView.vue @@ -1,15 +1,22 @@ @@ -439,6 +863,12 @@ onUnmounted(() => { position: relative; } +.header-left { + display: flex; + align-items: center; + gap: 14px; +} + .header-center { position: absolute; left: 50%; @@ -453,6 +883,19 @@ onUnmounted(() => { cursor: pointer; } +.mode-badge { + display: flex; + align-items: center; + gap: 6px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.14em; + background: #000; + color: #fff; + padding: 4px 10px; + border-radius: 2px; +} + .view-switcher { display: flex; background: #F5F5F5; @@ -539,6 +982,10 @@ onUnmounted(() => { overflow: hidden; } +.content-area.split-view { + display: flex; +} + .panel-wrapper { height: 100%; overflow: hidden; @@ -549,4 +996,201 @@ onUnmounted(() => { .panel-wrapper.left { border-right: 1px solid #EAEAEA; } + +/* ── Private area ─────────────────────────────────────────────────────── */ +.private-area { + display: block; + overflow-y: auto; + padding: 24px 32px; +} + +/* Steps bar (private) */ +.steps-bar { + display: flex; + align-items: center; + padding: 12px 32px; + border-bottom: 1px solid #EFEFEF; + background: #FAFAFA; + flex-shrink: 0; + gap: 0; +} + +.step-node { + display: flex; + align-items: center; + gap: 8px; + position: relative; +} + +.step-circle { + width: 24px; + height: 24px; + border-radius: 50%; + border: 1.5px solid #D0D0D0; + background: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + color: #999; + flex-shrink: 0; + transition: all 0.2s; +} + +.step-node.is-active .step-circle { border-color: #000; background: #000; color: #fff; } +.step-node.is-done .step-circle { border-color: #4CAF50; background: #4CAF50; color: #fff; } + +.step-node-name { + font-size: 11px; + font-weight: 600; + color: #AAA; + white-space: nowrap; +} + +.step-node.is-active .step-node-name { color: #000; } +.step-node.is-done { cursor: pointer; } +.step-node.is-done .step-node-name { color: #555; } + +.step-connector { + width: 32px; + height: 1.5px; + background: #E0E0E0; + margin: 0 8px; + flex-shrink: 0; +} + +.step-connector.is-done { background: #4CAF50; } + +/* Error banner */ +.error-banner { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 32px; + background: #FFF3F3; + border-bottom: 1px solid #FFCDD2; + font-size: 12px; + color: #C62828; +} + +.error-close { + margin-left: auto; + background: none; + border: none; + font-size: 16px; + cursor: pointer; + color: #C62828; + padding: 0 4px; +} + +/* Private centered panel (step 2 after prepare) */ +.centered-panel { + max-width: 680px; + margin: 0 auto; +} + +.loading-block { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 60px 0; + text-align: center; +} + +.loading-ring { + width: 40px; + height: 40px; + border: 3px solid #E5E7EB; + border-top-color: #000; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { to { transform: rotate(360deg); } } + +.loading-label { font-size: 14px; font-weight: 600; color: #000; } +.loading-hint { font-size: 12px; color: #888; max-width: 400px; line-height: 1.5; } + +.prepare-results { display: flex; flex-direction: column; gap: 20px; padding: 20px 0; } + +.result-badge { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.1em; + padding: 6px 12px; + border-radius: 2px; +} + +.result-badge--ok { background: #E8F5E9; color: #2E7D32; } + +.result-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.stat-card { + border: 1.5px solid #E8E8E8; + border-radius: 4px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.stat-value { font-size: 28px; font-weight: 700; color: #000; } +.stat-label { font-size: 10px; font-weight: 600; letter-spacing: 0.1em; color: #888; text-transform: uppercase; } + +.relation-tags { display: flex; flex-wrap: wrap; gap: 6px; } +.relation-tag { font-size: 11px; padding: 3px 8px; background: #F0F0F0; border-radius: 2px; color: #444; font-weight: 500; text-transform: capitalize; } + +.sim-id-block { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: #F7F7F7; border-radius: 3px; } +.sim-id-label { font-size: 10px; font-weight: 700; letter-spacing: 0.1em; color: #999; } +.sim-id-value { font-size: 12px; color: #333; } + +.result-actions { display: flex; gap: 10px; } + +.btn-primary { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 22px; + background: #000; + color: #fff; + border: none; + border-radius: 3px; + font-size: 13px; + font-weight: 600; + font-family: inherit; + cursor: pointer; + transition: background 0.15s; +} + +.btn-primary:hover { background: #222; } +.btn-primary:disabled { background: #CCC; cursor: not-allowed; } + +.btn-secondary { + display: flex; + align-items: center; + gap: 6px; + padding: 9px 18px; + background: #fff; + color: #000; + border: 1.5px solid #000; + border-radius: 3px; + font-size: 13px; + font-weight: 600; + font-family: inherit; + cursor: pointer; + transition: background 0.15s; +} + +.btn-secondary:hover { background: #F5F5F5; } + +.mono { font-family: 'JetBrains Mono', monospace; } diff --git a/locales/en.json b/locales/en.json index 544c68b1..0bb3d323 100644 --- a/locales/en.json +++ b/locales/en.json @@ -77,6 +77,14 @@ "layoutWorkbench": "Workbench", "stepNames": ["Graph Build", "Env Setup", "Run Simulation", "Report Generation", "Deep Interaction"] }, + "public": { + "stepNames": ["Graph Build", "Env Setup", "Run Simulation", "Report Generation", "Deep Interaction"], + "modeBadge": "PUBLIC OPINION" + }, + "private": { + "stepNames": ["Requirement", "Prepare", "Run", "Report", "Interact"], + "modeBadge": "PRIVATE IMPACT" + }, "step1": { "ontologyGeneration": "Ontology Generation", "ontologyCompleted": "Completed", diff --git a/locales/zh.json b/locales/zh.json index cd747e2f..a36b60f5 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -77,6 +77,14 @@ "layoutWorkbench": "工作台", "stepNames": ["图谱构建", "环境搭建", "开始模拟", "报告生成", "深度互动"] }, + "public": { + "stepNames": ["图谱构建", "环境搭建", "开始模拟", "报告生成", "深度互动"], + "modeBadge": "公共舆论" + }, + "private": { + "stepNames": ["需求", "准备", "运行", "报告", "互动"], + "modeBadge": "私域影响" + }, "step1": { "ontologyGeneration": "本体生成", "ontologyCompleted": "已完成",