This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
.nut-progress {
position: relative;
display: flex;
align-items: center;
width: 100%;
.nut-progress-outer {
flex: 1;
height: 10px;
background-color: $progress-outer-background-color;
border-radius: $progress-outer-border-radius;
.nut-progress-inner {
width: 30%;
height: 100%;
background: $progress-inner-background-color;
border-radius: $progress-outer-border-radius;
transition: all 0.4s;
}
.nut-progress-text {
display: flex;
flex-direction: column;
justify-content: center;
color: #fff;
}
.nut-progress-slot {
display: flex;
align-items: center;
justify-content: center;
}
.nut-active {
&::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: "";
border-radius: $progress-outer-border-radius;
animation: progressActive 2s ease-in-out infinite;
}
}
@keyframes progressActive {
0% {
width: 0;
background: rgb(255 255 255 / 10%);
}
20% {
width: 0;
background: rgb(255 255 255 / 50%);
}
100% {
width: 100%;
background: rgb(255 255 255 / 0%);
}
}
&.nut-progress-small {
height: $progress-small-height;
.nut-progress-text {
top: 50%;
padding: $progress-small-text-padding;
font-size: $progress-small-text-font-size;
line-height: $progress-small-text-line-height;
}
}
&.nut-progress-base {
height: $progress-base-height;
.nut-progress-text {
top: 50%;
padding: $progress-base-text-padding;
font-size: $progress-base-text-font-size;
line-height: $progress-base-text-line-height;
}
}
&.nut-progress-large {
height: $progress-large-height;
.nut-progress-text {
top: 50%;
padding: $progress-large-text-padding;
font-size: $progress-large-text-font-size;
line-height: $progress-large-text-line-height;
}
}
}
.nut-progress-outer-part {
width: 90%;
}
.nut-progress-text {
display: flex;
align-items: center;
min-width: 35px;
padding: 0 5px;
font-size: 13px;
line-height: 1;
}
.nut-progress-insidetext {
position: absolute;
top: 50%;
min-width: 0;
padding: $progress-insidetext-padding;
background: $progress-insidetext-background;
border-radius: $progress-insidetext-border-radius;
transition: all 0.4s;
}
.nut-icon-success,
.nut-icon-fail {
display: inline-block;
width: 10px;
height: 10px;
}
}

View File

@@ -0,0 +1 @@
export * from './progress'

View File

@@ -0,0 +1,53 @@
import type { ExtractPropTypes } from 'vue'
import { commonProps, makeNumericProp, makeStringProp, truthProp } from '../_utils'
export const progressProps = {
...commonProps,
/**
* @description 百分比
*/
percentage: {
type: [Number, String],
default: 0,
required: true,
},
/**
* 是否需要展示百分号
*
* @description 是否需要展示百分号
*/
isShowPercentage: truthProp,
/**
* @description 进度条背景色
*/
strokeColor: makeStringProp(''),
/**
* @description 进度条宽度
*/
strokeWidth: makeNumericProp(''),
/**
* @description 进度条及文字尺寸,可选值 `small` `base` `large`
*/
size: makeStringProp<'small' | 'base' | 'large'>('base'),
/**
* @description 是否显示进度条文字内容
*/
showText: truthProp,
/**
* @description 进度条文字显示位置(false:外显true:内显)
*/
textInside: Boolean,
/**
* @description 进度条文字颜色设置
*/
textColor: makeStringProp(''),
/**
* @description 进度条文字背景颜色设置
*/
textBackground: makeStringProp(''),
/**
* @description 进度条当前状态,可选值`active(展示动画效果)` `icon(展示icon标签)`
*/
status: makeStringProp<'text' | 'active' | 'icon'>('text'),
}
export type ProgressProps = ExtractPropTypes<typeof progressProps>

View File

@@ -0,0 +1,102 @@
<script setup lang="ts">
import { computed, defineComponent, useSlots } from 'vue'
import { PREFIX } from '../_constants'
import { getMainClass } from '../_utils'
import NutIcon from '../icon/icon.vue'
import { progressProps } from './progress'
const props = defineProps(progressProps)
const slotDefault = !!useSlots().default
const classes = computed(() => {
return getMainClass(props, componentName)
})
const height = computed(() => {
if (props.strokeWidth)
return `${props.strokeWidth}px`
return undefined
})
const percentage = computed(() => {
return Number(props.percentage) >= 100 ? 100 : Number(props.percentage)
})
const bgStyle = computed(() => {
return {
width: `${percentage.value}%`,
background: props.strokeColor || '',
}
})
const textStyle = computed(() => {
return {
color: props.textColor || '',
}
})
</script>
<script lang="ts">
const componentName = `${PREFIX}-progress`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<div :class="classes" :style="customStyle">
<div
class="nut-progress-outer"
:class="[showText && !textInside ? 'nut-progress-outer-part' : '', size ? `nut-progress-${size}` : '']"
:style="{ height }"
>
<div class="nut-progress-inner" :class="[status === 'active' ? 'nut-active' : '']" :style="bgStyle" />
<div
v-if="showText && textInside && !slotDefault"
class="nut-progress-text nut-progress-insidetext"
:style="{
lineHeight: height,
left: `${percentage}%`,
transform: `translate(-${+percentage}%,-50%)`,
background: textBackground || strokeColor,
}"
>
<span :style="textStyle">{{ percentage }}{{ isShowPercentage ? '%' : '' }} </span>
</div>
<div
v-if="showText && textInside && slotDefault"
class="nut-progress-slot"
:style="{
position: `absolute`,
top: `50%`,
left: `${percentage}%`,
transform: `translate(-${+percentage}%,-50%)`,
}"
>
<slot />
</div>
</div>
<div v-if="showText && !textInside" class="nut-progress-text" :style="{ lineHeight: height }">
<template v-if="status === 'text' || status === 'active'">
<span :style="textStyle">{{ percentage }}{{ isShowPercentage ? '%' : '' }} </span>
</template>
<template v-else-if="status === 'icon'">
<slot name="iconName">
<NutIcon
name="checked"
width="15px"
height="15px"
custom-color="#439422"
/>
</slot>
</template>
</div>
</div>
</template>
<style lang="scss">
@import './index';
</style>