Files
cz-web/src/views/project/cpns/ProjectMilestones.vue
2026-09-30 15:55:36 +08:00

181 lines
4.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>