207 lines
7.5 KiB
Vue
207 lines
7.5 KiB
Vue
<template>
|
|
<div :class="[
|
|
'bg-slate-50 h-full flex flex-col overflow-hidden',
|
|
isPresentationMode ? 'p-0' : 'p-6'
|
|
]">
|
|
<!-- Project Header -->
|
|
<div v-if="!isPresentationMode" class="mb-6 flex items-center justify-between">
|
|
<div>
|
|
<!-- Back to Project List Navigation -->
|
|
<div
|
|
class="flex items-center text-sm text-slate-500 mb-1 cursor-pointer hover:text-blue-600 transition-colors"
|
|
@click="setCurrentView('projects')"
|
|
>
|
|
<ArrowLeft :size="14" class="mr-1" />
|
|
<span>返回项目列表</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-4">
|
|
<div class="flex -space-x-2">
|
|
<div class="w-8 h-8 rounded-full bg-blue-100 border-2 border-white flex items-center justify-center text-xs font-bold text-blue-800" title="Project Manager">PM</div>
|
|
<div class="w-8 h-8 rounded-full bg-purple-100 border-2 border-white flex items-center justify-center text-xs font-bold text-purple-800" title="Data Analyst">DA</div>
|
|
<div class="w-8 h-8 rounded-full bg-gray-100 border-2 border-white flex items-center justify-center text-xs text-gray-500">+2</div>
|
|
</div>
|
|
<div class="h-6 w-px bg-slate-300"></div>
|
|
<button
|
|
@click="setIsPresentationMode(true)"
|
|
class="flex items-center px-4 py-2 bg-slate-900 text-white rounded-md text-sm hover:bg-slate-700 shadow-lg transition-all"
|
|
>
|
|
<Sparkles :size="16" class="mr-2" /> 演示模式
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stepper -->
|
|
<div :class="[
|
|
'bg-white border border-slate-200 rounded-lg mb-6',
|
|
isPresentationMode ? 'hidden' : 'block'
|
|
]">
|
|
<!-- Progress Header -->
|
|
<div class="px-4 pt-4 pb-2 flex items-center justify-between border-b border-slate-100">
|
|
<span class="text-xs font-medium text-slate-500 uppercase tracking-wider">项目进度</span>
|
|
<span class="text-sm font-bold text-blue-600">{{ Math.round(overallProgress) }}%</span>
|
|
</div>
|
|
<div class="px-4 py-2">
|
|
<div class="w-full bg-slate-100 rounded-full h-1.5 mb-2">
|
|
<div
|
|
class="bg-blue-600 h-1.5 rounded-full transition-all duration-500 ease-out"
|
|
:style="{ width: `${overallProgress}%` }"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center p-4">
|
|
<div
|
|
v-for="(step, idx) in steps"
|
|
:key="step.id"
|
|
class="flex items-center"
|
|
:class="idx === steps.length - 1 ? 'flex-none' : 'flex-1'"
|
|
>
|
|
<div
|
|
@click="handleStepClick(step.id as Step)"
|
|
class="flex items-center cursor-pointer group w-full"
|
|
>
|
|
<div class="relative">
|
|
<div :class="[
|
|
'w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-colors',
|
|
isStepActive(step.id)
|
|
? 'border-blue-600 bg-blue-600 text-white'
|
|
: isStepCompleted(idx)
|
|
? 'border-green-500 bg-green-500 text-white'
|
|
: 'border-slate-200 text-slate-400 bg-slate-50'
|
|
]">
|
|
<CheckCircle2 v-if="isStepCompleted(idx)" :size="16" />
|
|
<span v-else>{{ idx + 1 }}</span>
|
|
</div>
|
|
</div>
|
|
<span :class="[
|
|
'ml-3 text-sm font-medium',
|
|
isStepActive(step.id) ? 'text-slate-900' : 'text-slate-500'
|
|
]">
|
|
{{ step.label }}
|
|
</span>
|
|
<div
|
|
v-if="idx !== steps.length - 1"
|
|
:class="[
|
|
'flex-1 h-0.5 mx-4 relative',
|
|
isStepCompleted(idx) ? 'bg-green-500' : 'bg-slate-100'
|
|
]"
|
|
>
|
|
<div v-if="isStepCompleted(idx)" class="absolute inset-0 bg-green-500"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 演示模式退出按钮 -->
|
|
<button
|
|
v-if="isPresentationMode"
|
|
@click="setIsPresentationMode(false)"
|
|
class="fixed top-4 right-4 z-50 bg-white/90 backdrop-blur text-slate-800 px-4 py-2 rounded-full shadow-lg font-medium text-sm flex items-center hover:bg-white"
|
|
>
|
|
<EyeOff :size="16" class="mr-2"/> 退出演示
|
|
</button>
|
|
|
|
<!-- Main Workspace Area -->
|
|
<div class="flex-1 bg-white border border-slate-200 rounded-lg shadow-sm overflow-hidden flex flex-col">
|
|
<InventoryStep
|
|
v-if="currentStep === 'inventory'"
|
|
:inventory-mode="inventoryMode"
|
|
:set-inventory-mode="setInventoryMode"
|
|
:set-current-step="setCurrentStep"
|
|
/>
|
|
|
|
<ContextStep
|
|
v-if="currentStep === 'context'"
|
|
:set-current-step="setCurrentStep"
|
|
/>
|
|
|
|
<ValueStep
|
|
v-if="currentStep === 'value'"
|
|
:set-current-step="setCurrentStep"
|
|
:selected-scenarios="selectedScenarios"
|
|
:all-scenarios="initialScenarioData"
|
|
:toggle-scenario-selection="toggleScenarioSelection"
|
|
/>
|
|
|
|
<DeliveryStep
|
|
v-if="currentStep === 'delivery'"
|
|
:selected-scenarios="selectedScenarios"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue';
|
|
import { ArrowLeft, ChevronRight, Sparkles, EyeOff, CheckCircle2 } from 'lucide-vue-next';
|
|
import InventoryStep from './engagement/InventoryStep.vue';
|
|
import ContextStep from './engagement/ContextStep.vue';
|
|
import ValueStep from './engagement/ValueStep.vue';
|
|
import DeliveryStep from './engagement/DeliveryStep.vue';
|
|
import { scenarioData as initialScenarioData } from '@/data/mockData';
|
|
import type { ViewMode, Step, InventoryMode, Scenario } from '@/types';
|
|
|
|
interface Props {
|
|
currentStep: Step;
|
|
setCurrentStep: (step: Step) => void;
|
|
setCurrentView: (view: ViewMode) => void;
|
|
isPresentationMode: boolean;
|
|
setIsPresentationMode: (mode: boolean) => void;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const inventoryMode = ref<InventoryMode>('selection');
|
|
const selectedScenarios = ref<Scenario[]>(
|
|
initialScenarioData.filter(s => s.selected).map(s => ({ ...s }))
|
|
);
|
|
|
|
// Set inventory mode function
|
|
const setInventoryMode = (mode: InventoryMode) => {
|
|
inventoryMode.value = mode;
|
|
};
|
|
|
|
// Toggle scenario selection
|
|
const toggleScenarioSelection = (scenarioId: number) => {
|
|
const isSelected = selectedScenarios.value.some(s => s.id === scenarioId);
|
|
if (isSelected) {
|
|
selectedScenarios.value = selectedScenarios.value.filter(s => s.id !== scenarioId);
|
|
} else {
|
|
const scenario = initialScenarioData.find(s => s.id === scenarioId);
|
|
if (scenario) {
|
|
selectedScenarios.value.push({ ...scenario, selected: true });
|
|
}
|
|
}
|
|
};
|
|
|
|
// Stepper Configuration
|
|
const steps = [
|
|
{ id: 'inventory', label: '上传数据资源表' },
|
|
{ id: 'context', label: '背景调研' },
|
|
{ id: 'value', label: '识别场景' },
|
|
{ id: 'delivery', label: '盘点报告' },
|
|
];
|
|
|
|
const currentStepIndex = computed(() =>
|
|
steps.findIndex(s => s.id === props.currentStep)
|
|
);
|
|
|
|
const overallProgress = computed(() =>
|
|
((currentStepIndex.value + 1) / steps.length) * 100
|
|
);
|
|
|
|
const isStepActive = (stepId: string) => stepId === props.currentStep;
|
|
const isStepCompleted = (idx: number) => idx < currentStepIndex.value;
|
|
|
|
const handleStepClick = (stepId: Step) => {
|
|
props.setCurrentStep(stepId);
|
|
// Reset inventory mode if returning to that step
|
|
if (stepId === 'inventory' && inventoryMode.value === 'results') {
|
|
// keep results
|
|
} else if (stepId === 'inventory') {
|
|
inventoryMode.value = 'selection';
|
|
}
|
|
};
|
|
</script>
|