feat 搭建架构骨架

This commit is contained in:
Ironsp
2026-09-24 09:09:47 +08:00
parent dd9f6b77a4
commit 7a2efa3fea
180 changed files with 18967 additions and 9 deletions

View File

@@ -0,0 +1,186 @@
<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);
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>