177 lines
3.5 KiB
Vue
177 lines
3.5 KiB
Vue
<template>
|
||
<view class="steps-vertical">
|
||
<view class="step-item" v-for="(item, index) in steps" :key="index">
|
||
<!-- 左侧:圆点 + 竖线 -->
|
||
<view class="step-left">
|
||
<!-- 圆点 -->
|
||
<view class="step-circle" :class="getClass(index)">
|
||
<!-- 插槽:自定义圆点 -->
|
||
<slot v-if="$slots.circle" name="circle" :index="index" :status="getStatus(index)">
|
||
</slot>
|
||
<view v-else-if="getStatus(index) === 'finish'">
|
||
<uni-icons type="checkmarkempty" size="15" :color="props.color"></uni-icons>
|
||
</view>
|
||
<text v-else-if="getStatus(index) === 'process'">{{ index + 1 }}</text>
|
||
<text v-else-if="getStatus(index) === 'wait'">{{ index + 1 }}</text>
|
||
</view>
|
||
|
||
<!-- 竖线(最后一条不显示) -->
|
||
<view
|
||
class="step-line"
|
||
:class="{ finish: getStatus(index) === 'finish' }"
|
||
v-if="index < steps.length - 1"
|
||
></view>
|
||
</view>
|
||
|
||
<!-- 右侧:内容区域(支持插槽!) -->
|
||
<view class="step-right">
|
||
<!-- 默认标题 -->
|
||
<view
|
||
class="step-title"
|
||
:class="{ active: getStatus(index) !== 'wait' }"
|
||
v-if="!$slots.item"
|
||
>
|
||
{{ item.title }}
|
||
</view>
|
||
|
||
<!-- 默认描述 -->
|
||
<view class="step-desc" v-if="item.desc && !$slots.item">
|
||
{{ item.desc }}
|
||
</view>
|
||
|
||
<!-- 🔥 核心:自定义内容插槽 -->
|
||
<slot
|
||
name="item"
|
||
v-if="$slots.item"
|
||
:index="index"
|
||
:status="getStatus(index)"
|
||
:data="item"
|
||
></slot>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
|
||
const props = defineProps({
|
||
// 步骤列表
|
||
steps: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
// 当前步骤(从 0 开始)
|
||
current: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
// 主题色
|
||
color: {
|
||
type: String,
|
||
default: '#007aff'
|
||
}
|
||
})
|
||
|
||
// 获取状态:finish / process / wait
|
||
const getStatus = index => {
|
||
if (index < props.current) return 'finish'
|
||
if (index === props.current) return 'process'
|
||
return 'wait'
|
||
}
|
||
|
||
// 样式类
|
||
const getClass = index => {
|
||
return 'step-circle-' + getStatus(index)
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.step-item {
|
||
display: flex;
|
||
position: relative;
|
||
}
|
||
|
||
/* 左侧 */
|
||
.step-left {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-right: 14rpx;
|
||
}
|
||
|
||
/* 圆点 */
|
||
.step-circle {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 50%;
|
||
font-size: 32rpx;
|
||
line-height: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
z-index: 10;
|
||
border: 3rpx solid transparent;
|
||
|
||
color: #999999;
|
||
border-color: #999999;
|
||
}
|
||
|
||
/* 已完成 */
|
||
.step-circle-finish {
|
||
background: transparent;
|
||
color: v-bind('props.color');
|
||
border-color: v-bind('props.color');
|
||
}
|
||
|
||
/* 进行中 */
|
||
.step-circle-process {
|
||
border-color: v-bind('props.color');
|
||
color: #fff;
|
||
background: v-bind('props.color');
|
||
}
|
||
|
||
/* 待完成 */
|
||
.step-circle-wait {
|
||
color: #999999;
|
||
border-color: #999999;
|
||
}
|
||
|
||
/* 竖线 */
|
||
.step-line {
|
||
width: 4rpx;
|
||
height: 100%;
|
||
background: #e5e5e5;
|
||
margin-top: 8rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.step-line.finish {
|
||
background: v-bind('props.color');
|
||
}
|
||
|
||
/* 右侧内容 */
|
||
.step-right {
|
||
flex: 1;
|
||
}
|
||
|
||
.step-title {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.step-title.active {
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.step-desc {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
line-height: 1.5;
|
||
}
|
||
</style>
|