187 lines
3.9 KiB
Vue
187 lines
3.9 KiB
Vue
<script setup lang="ts">
|
||
import type { Milestone } from '@/types/business/project'
|
||
|
||
interface Props {
|
||
progress: number
|
||
milestones: Milestone[]
|
||
startTime?: string
|
||
endTime?: string
|
||
}
|
||
|
||
defineProps<Props>()
|
||
</script>
|
||
|
||
<template>
|
||
<section class="project-milestones">
|
||
<div class="project-milestones__head">
|
||
<h2 class="project-milestones__title">
|
||
总体进度
|
||
</h2>
|
||
<span class="project-milestones__percent">{{ progress }}%</span>
|
||
</div>
|
||
<div class="project-milestones__track">
|
||
<div
|
||
class="project-milestones__bar"
|
||
:style="{ width: `${progress}%` }"
|
||
/>
|
||
</div>
|
||
|
||
<ol class="project-milestones__timeline">
|
||
<li
|
||
v-for="node in milestones"
|
||
:key="node.name"
|
||
class="project-milestones__node"
|
||
:class="`is-${node.status === '已完成' ? 'done' : node.status === '进行中' ? 'active' : 'pending'}`"
|
||
>
|
||
<span class="project-milestones__dot" />
|
||
<div class="project-milestones__body">
|
||
<div class="project-milestones__row">
|
||
<span class="project-milestones__name">{{ node.name }}</span>
|
||
<span class="project-milestones__status">{{ node.status }}</span>
|
||
</div>
|
||
<span v-if="node.time" class="project-milestones__time text-secondary">{{ node.time }}</span>
|
||
</div>
|
||
</li>
|
||
<li v-if="milestones.length === 0" class="project-milestones__empty text-secondary">
|
||
暂无里程碑数据
|
||
</li>
|
||
</ol>
|
||
|
||
<div class="project-milestones__range">
|
||
<span v-if="startTime" class="text-secondary">开始时间:{{ startTime }}</span>
|
||
<span v-if="endTime" class="text-secondary">结束时间:{{ endTime }}</span>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<style lang="less" scoped>
|
||
.project-milestones {
|
||
padding: @spacing-lg @spacing-xl;
|
||
border-radius: @radius-lg;
|
||
background: @white;
|
||
box-shadow: @shadow-base;
|
||
|
||
&__head {
|
||
.flex(row, space-between, center);
|
||
}
|
||
|
||
&__title {
|
||
font-size: @font-size-lg;
|
||
font-weight: 600;
|
||
color: @heading-color;
|
||
}
|
||
|
||
&__percent {
|
||
font-size: @font-size-xl;
|
||
font-weight: 600;
|
||
color: @primary-color;
|
||
}
|
||
|
||
&__track {
|
||
height: 8px;
|
||
margin-top: @spacing-base;
|
||
border-radius: @radius-round;
|
||
background: @bg-color;
|
||
overflow: hidden;
|
||
}
|
||
|
||
&__bar {
|
||
height: 100%;
|
||
border-radius: @radius-round;
|
||
background: @primary-color;
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
&__timeline {
|
||
margin-top: @spacing-lg;
|
||
padding-left: @spacing-xs;
|
||
}
|
||
|
||
&__node {
|
||
position: relative;
|
||
padding: 0 0 @spacing-base @spacing-lg;
|
||
border-left: 2px solid @border-color;
|
||
|
||
&:last-of-type {
|
||
border-left-color: transparent;
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
&.is-done {
|
||
border-left-color: @success-color;
|
||
}
|
||
|
||
&.is-active {
|
||
border-left-color: @primary-color;
|
||
}
|
||
}
|
||
|
||
&__dot {
|
||
position: absolute;
|
||
top: 2px;
|
||
left: -7px;
|
||
width: 12px;
|
||
height: 12px;
|
||
border: 2px solid @border-color-dark;
|
||
border-radius: @radius-round;
|
||
background: @white;
|
||
|
||
.is-done & {
|
||
border-color: @success-color;
|
||
background: @success-color;
|
||
}
|
||
|
||
.is-active & {
|
||
border-color: @primary-color;
|
||
background: @primary-color;
|
||
}
|
||
}
|
||
|
||
&__body {
|
||
.flex(column, flex-start, stretch);
|
||
gap: 2px;
|
||
}
|
||
|
||
&__row {
|
||
.flex(row, center);
|
||
gap: @spacing-sm;
|
||
}
|
||
|
||
&__name {
|
||
font-size: @font-size-base;
|
||
color: @heading-color;
|
||
font-weight: 600;
|
||
}
|
||
|
||
&__status {
|
||
font-size: @font-size-sm;
|
||
color: @text-color-secondary;
|
||
|
||
.is-done & {
|
||
color: @success-color;
|
||
}
|
||
|
||
.is-active & {
|
||
color: @primary-color;
|
||
}
|
||
}
|
||
|
||
&__time {
|
||
font-size: @font-size-sm;
|
||
}
|
||
|
||
&__empty {
|
||
font-size: @font-size-base;
|
||
}
|
||
|
||
&__range {
|
||
.flex(row, flex-start, center);
|
||
gap: @spacing-lg;
|
||
margin-top: @spacing-base;
|
||
padding-top: @spacing-base;
|
||
border-top: 1px solid @border-color;
|
||
font-size: @font-size-sm;
|
||
}
|
||
}
|
||
</style>
|