181 lines
4.6 KiB
Vue
181 lines
4.6 KiB
Vue
<script setup lang="ts">
|
||
import type { Milestone } from '@/types/business/project'
|
||
|
||
interface Props {
|
||
milestones: Milestone[]
|
||
}
|
||
|
||
const props = defineProps<Props>()
|
||
|
||
/** 三态 → 修饰类(设计稿:已完成=绿实心+白勾 / 进行中=白底粗蓝环 / 待开始=白底灰环) */
|
||
function stateClass(status: Milestone['status']): string {
|
||
if (status === '已完成') return 'is-done'
|
||
if (status === '进行中') return 'is-active'
|
||
return 'is-pending'
|
||
}
|
||
|
||
/**
|
||
* 连接线是否走「已完成」的绿色。
|
||
*
|
||
* <p>设计稿实测:只有<b>下一个节点已完成</b>时该段才是绿色(第二个节点已完成 → 第 1 段绿;
|
||
* 第三个节点进行中 → 第 2 段仍是灰色,不随「进行中」变蓝)。</p>
|
||
*/
|
||
function linkDone(index: number): boolean {
|
||
return props.milestones[index + 1]?.status === '已完成'
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<section class="project-milestones">
|
||
<h2 class="project-milestones__title">
|
||
项目进度(里程碑)
|
||
</h2>
|
||
|
||
<ol v-if="props.milestones.length > 0" class="project-milestones__track">
|
||
<li
|
||
v-for="(node, index) in props.milestones"
|
||
:key="`${node.name}-${index}`"
|
||
class="project-milestones__node"
|
||
:class="[stateClass(node.status), { 'is-link-done': linkDone(index) }]"
|
||
>
|
||
<span class="project-milestones__dot">
|
||
<svg
|
||
v-if="node.status === '已完成'"
|
||
class="project-milestones__check"
|
||
viewBox="0 0 16 16"
|
||
aria-hidden="true"
|
||
>
|
||
<path d="M3.5 8.4 6.6 11.5 12.5 4.8" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||
</svg>
|
||
</span>
|
||
<span class="project-milestones__name">{{ node.name }}</span>
|
||
<!-- 设计稿:已完成节点显示完成时间,其余显示状态文案 -->
|
||
<span class="project-milestones__sub">{{ node.time || node.status }}</span>
|
||
</li>
|
||
</ol>
|
||
|
||
<p v-else class="project-milestones__empty text-secondary">
|
||
暂无里程碑数据
|
||
</p>
|
||
</section>
|
||
</template>
|
||
|
||
<style lang="less" scoped>
|
||
// 节点列宽 = 设计稿相邻节点中心距(356 设计px × 0.77 ≈ 274px)
|
||
@milestone-node-width: 274px;
|
||
@milestone-dot-size: 18px;
|
||
|
||
.project-milestones {
|
||
.flex(column, flex-start, stretch);
|
||
// 设计 13:里程碑区块直接落在白底页面上,无卡片
|
||
padding: 0;
|
||
|
||
&__title {
|
||
.section-title();
|
||
// 设计 13:竖条 4×20 设计px、文字 15 设计px/700
|
||
padding-left: 9px;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
|
||
&::before {
|
||
width: 3px;
|
||
height: 15px;
|
||
}
|
||
}
|
||
|
||
// 设计稿:轨迹从容器左缘起、**固定节距**左对齐(不铺满整宽)
|
||
&__track {
|
||
.flex(row, flex-start, flex-start);
|
||
margin-top: 16px;
|
||
padding-bottom: @spacing-xs;
|
||
overflow-x: auto;
|
||
.scrollbar();
|
||
}
|
||
|
||
&__node {
|
||
position: relative;
|
||
.flex(column, flex-start, center);
|
||
flex: 0 0 @milestone-node-width;
|
||
min-width: 0;
|
||
|
||
// 圆点之间的连接线:从本节点圆点右缘连到下一节点圆点左缘
|
||
&:not(:last-child)::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: (@milestone-dot-size / 2 - 1px);
|
||
left: calc(50% + @milestone-dot-size / 2);
|
||
width: calc(100% - @milestone-dot-size);
|
||
height: 2px;
|
||
background: @milestone-line;
|
||
}
|
||
|
||
&.is-link-done:not(:last-child)::after {
|
||
background: @milestone-done;
|
||
}
|
||
}
|
||
|
||
&__dot {
|
||
.flex(row, center, center);
|
||
position: relative;
|
||
z-index: 1;
|
||
width: @milestone-dot-size;
|
||
height: @milestone-dot-size;
|
||
border: 3px solid @milestone-idle;
|
||
border-radius: @radius-round;
|
||
background: @white;
|
||
box-sizing: border-box;
|
||
|
||
// 设计 13:已完成圆点略小(20 设计px)、实心绿
|
||
.is-done & {
|
||
width: 16px;
|
||
height: 16px;
|
||
border-color: @milestone-done;
|
||
background: @milestone-done;
|
||
}
|
||
|
||
.is-active & {
|
||
border-width: 3px;
|
||
border-color: @project-accent;
|
||
}
|
||
}
|
||
|
||
&__check {
|
||
width: 10px;
|
||
height: 10px;
|
||
}
|
||
|
||
&__name {
|
||
margin-top: @spacing-sm;
|
||
max-width: 100%;
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
text-align: center;
|
||
color: @text-color;
|
||
.multi-ellipsis(2);
|
||
|
||
.is-done & {
|
||
color: @success-color;
|
||
}
|
||
|
||
.is-active & {
|
||
color: @project-accent;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
&__sub {
|
||
margin-top: @spacing-xs;
|
||
max-width: 100%;
|
||
font-size: 11px;
|
||
text-align: center;
|
||
color: @text-color-disabled;
|
||
.multi-ellipsis(2);
|
||
}
|
||
|
||
&__empty {
|
||
margin-top: @spacing-base;
|
||
font-size: @font-size-base;
|
||
}
|
||
}
|
||
</style>
|