| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- <template>
- <view class="flow-bar card p-4">
- <view class="flex items-start justify-between">
- <view
- v-for="(step, index) in steps"
- :key="step.key"
- class="flow-step"
- >
- <view class="flow-node-wrap">
- <view
- v-if="index !== 0"
- class="flow-line flow-line-left"
- :class="step.active || step.current ? 'flow-line-active' : ''"
- />
- <view
- class="flow-node"
- :class="nodeClass(step)"
- >
- <text v-if="step.active && !step.current" class="uni-icons uniui-checkmarkempty flow-check"></text>
- <text v-else class="flow-node-text">{{ index + 1 }}</text>
- </view>
- <view
- v-if="index !== steps.length - 1"
- class="flow-line flow-line-right"
- :class="nextActive(index) ? 'flow-line-active' : ''"
- />
- </view>
- <text class="flow-label" :class="step.current ? 'flow-label-current' : step.active ? 'flow-label-active' : ''">
- {{ step.label }}
- </text>
- </view>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { computed } from 'vue'
- import type { TimelineRecord } from './Timeline.vue'
- interface FlowStep {
- key: string
- label: string
- threshold: number
- active: boolean
- current: boolean
- }
- const props = withDefaults(
- defineProps<{
- status: string
- timeline?: TimelineRecord[]
- }>(),
- {
- timeline: () => []
- }
- )
- const STATUS_ORDER: string[] = [
- 'pending',
- 'confirmed',
- 'scheduled',
- 'departed',
- 'arrived',
- 'constructing',
- 'inspecting',
- 'completed'
- ]
- const currentIndex = computed(() => {
- const idx = STATUS_ORDER.indexOf(props.status)
- return idx < 0 ? 0 : idx
- })
- const steps = computed<FlowStep[]>(() => {
- const idx = currentIndex.value
- const defs: Omit<FlowStep, 'active' | 'current'>[] = [
- { key: 'confirm', label: '确认', threshold: 1 },
- { key: 'depart', label: '出车', threshold: 3 },
- { key: 'arrive', label: '到达', threshold: 4 },
- { key: 'construct', label: '施工', threshold: 5 },
- { key: 'inspect', label: '验收', threshold: 6 },
- { key: 'complete', label: '完成', threshold: 7 }
- ]
- return defs.map(d => ({
- ...d,
- active: idx >= d.threshold,
- current: idx === d.threshold
- }))
- })
- function nodeClass(step: FlowStep): string {
- if (step.current) return 'flow-node-current'
- if (step.active) return 'flow-node-active'
- return ''
- }
- function nextActive(index: number): boolean {
- const next = steps.value[index + 1]
- return !!next && next.active
- }
- </script>
- <style scoped>
- .flow-bar {
- margin-bottom: 16px;
- }
- .flow-step {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- min-width: 0;
- }
- .flow-node-wrap {
- width: 100%;
- height: 28px;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .flow-line {
- position: absolute;
- top: 50%;
- height: 2px;
- background-color: #e5e7eb;
- transform: translateY(-50%);
- }
- .flow-line-left {
- left: 0;
- right: 50%;
- }
- .flow-line-right {
- left: 50%;
- right: 0;
- }
- .flow-line-active {
- background-color: #2563eb;
- }
- .flow-node {
- width: 24px;
- height: 24px;
- border-radius: 50%;
- background-color: #f3f4f6;
- border: 2px solid #e5e7eb;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1;
- }
- .flow-node-active {
- background-color: #2563eb;
- border-color: #2563eb;
- }
- .flow-node-current {
- background-color: #fff;
- border-color: #2563eb;
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
- }
- .flow-check {
- color: #fff;
- font-size: 12px;
- }
- .flow-node-text {
- font-size: 11px;
- color: #9ca3af;
- }
- .flow-node-current .flow-node-text {
- color: #2563eb;
- font-weight: 600;
- }
- .flow-label {
- margin-top: 6px;
- font-size: 11px;
- color: #9ca3af;
- text-align: center;
- }
- .flow-label-active {
- color: #2563eb;
- }
- .flow-label-current {
- color: #2563eb;
- font-weight: 600;
- }
- </style>
|