fix: 🐛 修复 Progress 无法设置进度为 0 的问题 (#748)

 Closes: #747
This commit is contained in:
不如摸鱼去 2024-11-30 14:38:15 +08:00 committed by GitHub
parent 7bc359205d
commit c136f54cda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 204 additions and 166 deletions

View File

@ -2,11 +2,8 @@
# Progress 进度条
用于展示操作的当前进度。
## 基本用法
设置百分比 `percentage`
@ -20,7 +17,7 @@
设置 `hide-text` 隐藏进度文字。
```html
<wd-progress :percentage="60" hide-text>
<wd-progress :percentage="60" hide-text></wd-progress>
```
## 设置状态
@ -37,7 +34,7 @@
设置 `color` 修改进度条颜色。
```html
<wd-progress :percentage="80" color="#00c740">
<wd-progress :percentage="80" color="#00c740"></wd-progress>
```
`color` 也可以设置为数组或者函数。数组如果只传入颜色,则自动计算每个颜色的进度边界。函数需要返回一个颜色值。
@ -53,7 +50,9 @@
```
```typescript
const colorObject = ref<any>([
import type { ProgressColor } from '@/uni_modules/wot-design-uni/components/wd-progress/types'
const colorObject = ref<ProgressColor>([
{
color: 'yellow',
percentage: 30
@ -77,16 +76,22 @@ const percentage = ref<number>(100)
## Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
|-----|-----|------|-------|-------|--------|
| percentage | 进度数值最大值100 | number | - | 0 | - |
| hide-text | 隐藏进度文字 | boolean | - | false | - |
| color | 进度条颜色 | string / array | - | linear-gradient(315deg, rgba(81,124,240,1) 0%,rgba(118,158,245,1) 100%) | - |
| status | 进度条状态 | string | success / danger | - | - |
| duration | 进度增加1%所需毫秒数 | number | - | 30 | - |
| ---------- | --------------------- | --------------------------------------- | ---------------- | ------ | -------- |
| percentage | 进度数值,最大值 100 | `number` | - | 0 | - |
| hide-text | 隐藏进度文字 | `boolean` | - | false | - |
| color | 进度条颜色 | `string \| ProgressColor[] \| string[]` | - | - | - |
| status | 进度条状态 | `string` | success \| danger \| warning | - | - |
| duration | 进度增加 1%所需毫秒数 | `number` | - | 30 | - |
### ProgressColor
| 字段 | 说明 | 说明 | 最低版本 |
| ---------- | ------ | ------ | -------- |
| color | string | 颜色 | - |
| percentage | number | 百分比 | - |
## 外部样式类
| 类名 | 说明 | 最低版本 |
|-----|------|--------|
| ------------ | ---------- | -------- |
| custom-class | 根节点样式 | - |

View File

@ -1,7 +1,7 @@
<template>
<page-wraper>
<demo-block title="基本用法">
<wd-progress :percentage="30" />
<wd-progress :percentage="percentageZero" />
</demo-block>
<demo-block title="不显示进度文字">
@ -11,6 +11,7 @@
<demo-block title="进度条状态">
<wd-progress :percentage="100" hide-text status="success" />
<wd-progress :percentage="80" hide-text status="danger" />
<wd-progress :percentage="90" hide-text status="warning" />
</demo-block>
<demo-block title="修改颜色">
@ -21,12 +22,35 @@
<wd-progress :percentage="100" :color="['#00c740', '#ffb300', '#e2231a', '#0083ff']" />
<wd-progress :percentage="percentage" :color="colorObject" />
</demo-block>
<demo-block title="动态设置">
<wd-progress :percentage="percentageDynamic" />
<wd-button custom-style="margin-right: 10px;" @click="add" type="success" size="small">+10</wd-button>
<wd-button @click="reduce" type="error" size="small">-10</wd-button>
</demo-block>
</page-wraper>
</template>
<script lang="ts" setup>
import type { ProgressColor } from '@/uni_modules/wot-design-uni/components/wd-progress/types'
import { ref } from 'vue'
const colorObject = ref<any>([
const percentageZero = ref<number>(30)
const percentage = ref<number>(100)
//
const percentageDynamic = ref<number>(50)
// 10
const add = () => {
percentageDynamic.value = Math.min(percentageDynamic.value + 10, 100)
}
// 10
const reduce = () => {
percentageDynamic.value = Math.max(percentageDynamic.value - 10, 0)
}
const colorObject = ref<ProgressColor[]>([
{
color: 'yellow',
percentage: 30
@ -44,6 +68,5 @@ const colorObject = ref<any>([
percentage: 90
}
])
const percentage = ref<number>(100)
</script>
<style lang="scss" scoped></style>

View File

@ -486,15 +486,9 @@ $-progress-padding: var(--wot-progress-padding, 9px 0 8px) !default; // 进度
$-progress-bg: var(--wot-progress-bg, rgba(229, 229, 229, 1)) !default; // 进度条底色
$-progress-danger-color: var(--wot-progress-danger-color, $-color-danger) !default; // 进度条danger颜色
$-progress-success-color: var(--wot-progress-success-color, $-color-success) !default; // 进度条success进度条颜色
$-progress-color: var(--wot-progress-color, resultColor(315deg, $-color-theme, 'dark' 'light', #517cf0 #769ef5, 0% 100%)) !default; // 进度条渐变色
$-progress-linear-success-color: var(
--wot-progress-linear-success-color,
resultColor(315deg, $-color-theme, 'dark' 'light', #20b080 #2bd69d, 0% 100%)
) !default; // success进度条渐变色
$-progress-linear-danger-color: var(
--wot-progress-linear-danger-color,
resultColor(315deg, $-color-theme, 'dark' 'light', #e04350 #ff5964, 0% 100%)
) !default; // danger进度条渐变色
$-progress-warning-color: var(--wot-progress-warning-color, $-color-warning) !default; // 进度条warning进度条颜色
$-progress-color: var(--wot-progress-color, $-color-theme) !default; // 进度条颜色
$-progress-height: var(--wot-progress-height, 3px) !default; // 进度条高度
$-progress-label-color: var(--wot-progress-label-color, #333) !default; // 文字颜色
$-progress-label-fs: var(--wot-progress-label-fs, 14px) !default; // 文字字号

View File

@ -37,10 +37,13 @@
font-size: $-progress-icon-fs;
@include when(danger) {
background: $-progress-linear-danger-color;
background: $-progress-danger-color;
}
@include when(success) {
background: $-progress-linear-success-color;
background: $-progress-success-color;
}
@include when(warning) {
background: $-progress-warning-color;
}
}
@include edeep(label) {
@ -58,5 +61,8 @@
@include when(success) {
color: $-progress-success-color;
}
@include when(warning) {
color: $-progress-warning-color;
}
}
}

View File

@ -1,16 +1,21 @@
/*
* @Author: weisheng
* @Date: 2024-03-15 20:40:34
* @LastEditTime: 2024-03-18 15:32:11
* @LastEditTime: 2024-11-30 00:01:23
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-progress\types.ts
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-progress/types.ts
*
*/
import type { PropType } from 'vue'
import { baseProps, makeBooleanProp, makeNumberProp } from '../common/props'
export type ProgressStatus = 'success' | 'danger' // 状态类型
export type ProgressStatus = 'success' | 'danger' | 'warning' // 状态类型
export type ProgressColor = {
color: string // 颜色
percentage: number // 百分比
}
export const progressProps = {
...baseProps,
@ -26,8 +31,7 @@ export const progressProps = {
*
*/
color: {
type: [String, Array, Object] as PropType<string | string[] | Record<string, any>[]>,
default: 'linear-gradient(315deg, rgba(81,124,240,1) 0%,rgba(118,158,245,1) 100%)'
type: [String, Array] as PropType<string | string[] | ProgressColor[]>
},
/**
* 1%

View File

@ -1,15 +1,13 @@
<template>
<view :class="`wd-progress ${customClass}`" :style="customStyle">
<!--进度条-->
<view class="wd-progress__outer">
<view :class="`wd-progress__inner ${progressClass}`" :style="rootStyle"></view>
<view :class="`wd-progress__inner ${innerClass}`" :style="rootStyle"></view>
</view>
<!--文案图标-->
<view v-if="!hideText" class="wd-progress__label">{{ percentage }}%</view>
<wd-icon
v-else-if="status"
:custom-class="`wd-progress__label wd-progress__icon ${progressClass}`"
:name="status == 'danger' ? 'close-outline' : 'check-outline'"
:custom-class="`wd-progress__label wd-progress__icon ${innerClass}`"
:name="iconName"
:color="typeof color === 'string' ? color : ''"
></wd-icon>
</view>
@ -29,155 +27,66 @@ export default {
<script lang="ts" setup>
import wdIcon from '../wd-icon/wd-icon.vue'
import { computed, ref, watch } from 'vue'
import { checkNumRange, isArray, objToStyle } from '../common/util'
import { progressProps } from './types'
import { isArray, isDef, isObj, objToStyle, pause } from '../common/util'
import { progressProps, type ProgressColor } from './types'
const props = defineProps(progressProps)
//
const showColor = ref<string>('')
//
const showPercent = ref<number>(0)
// newPercent - oldPercent
const changeCount = ref<number>(0)
const progressClass = ref<string>('')
let timer: NodeJS.Timeout | null = null //
let timer: NodeJS.Timeout | null = null
const rootStyle = computed(() => {
const style: Record<string, string | number> = {
return objToStyle({
background: showColor.value,
width: showPercent.value + '%',
'transition-duration': changeCount.value * props.duration * 0.001 + 's'
width: `${showPercent.value}%`,
'transition-duration': `${changeCount.value * props.duration * 0.001}s`
})
})
const innerClass = computed(() => (props.status ? `is-${props.status}` : ''))
const iconName = computed(() => {
let icon: string = ''
switch (props.status) {
case 'danger':
icon = 'close-outline'
break
case 'success':
icon = 'check-outline'
break
case 'warning':
icon = 'warn-bold'
break
default:
break
}
return objToStyle(style)
return icon
})
watch(
() => props.percentage,
(newValue) => {
//
if (Number.isNaN(newValue) || newValue < 0 || newValue > 100) {
() => [props.percentage, props.color, props.duration],
() => {
validatePercentage(props.percentage)
updateProgress()
},
{ immediate: true }
)
function validatePercentage(value: number) {
if (Number.isNaN(value) || value < 0 || value > 100) {
console.error('The value of percentage must be between 0 and 100')
}
controlProgress()
}
)
watch(
() => props.color,
() => {
controlProgress()
},
{
deep: true,
immediate: true
}
)
watch(
() => props.status,
() => {
computeProgressClass()
},
{
deep: true,
immediate: true
}
)
watch(
() => props.duration,
(newValue) => {
checkNumRange(newValue)
},
{
deep: true,
immediate: true
}
)
function computeProgressClass() {
const { status } = props
let progressClasses: string[] = []
status && progressClasses.push(`is-${status}`)
progressClass.value = progressClasses.join(' ')
}
/**
* @description
* @param {Number} targetPercent 目标值
* @param {String} color 目标颜色
* 进度条前进
* @param partList 颜色数组
* @param percentage 进度值
*/
function update(targetPercent: number, color: string) {
//
if (timer) return
const { duration } = props
// transition-duration
changeCount.value = Math.abs(targetPercent - showPercent.value)
setTimeout(() => {
showPercent.value = targetPercent
showColor.value = color
timer = setTimeout(() => {
clearTimeout(timer as any)
timer = null
controlProgress()
}, changeCount.value * duration)
}, 50)
}
/**
* @description 控制进度条的进度和每段的颜色
*/
function controlProgress() {
const {
//
percentage,
// color
color
} = props
//
if (showPercent.value === percentage || !percentage) return
/**
* 数组边界安全判断
*/
let colorArray: string[] | Record<string, any>[] = (isArray(color) ? color : [color]) as string[] | Record<string, any>[]
if (colorArray.length === 0) throw Error('The colorArray is empty')
const isStrArray = (colorArray as any).every((item: any) => typeof item === 'string')
// eslint-disable-next-line no-prototype-builtins
const isObjArray = (colorArray as any).every((color: any) => color.hasOwnProperty('color') && color.hasOwnProperty('percentage'))
if (!isStrArray && !isObjArray) {
throw Error('Color must be String or Object with color and percentage')
}
if (isObjArray && (colorArray as any).some(({ percentage }: any) => Number.isNaN(parseInt(percentage)))) {
throw Error('All the percentage must can be formatted to Number')
}
/**
* 根据colorArray平均分布每段color值或使用用户自定义的值
*/
const partNum = parseInt(`${100 / colorArray.length}`)
const partList = isObjArray
? colorArray.sort((a: any, b: any) => a.percentage - b.percentage)
: colorArray.map((item, index) => {
return {
color: item,
percentage: (index + 1) * partNum
}
})
/**
* 找到当前目标
*/
showPercent.value > percentage
? // targettarget
partList.some((part: any) => {
if (percentage <= part.percentage) {
update(percentage, part.color)
return true
}
})
: // 使
partList.some((part: any, index: number) => {
function updateProgressForward(partList: ProgressColor[], percentage: number) {
return partList.some((part, index) => {
if (showPercent.value < part.percentage && part.percentage <= percentage) {
// nowtarget
update(part.percentage, part.color)
return true
} else if (index === partList.length - 1) {
@ -185,7 +94,104 @@ function controlProgress() {
}
})
}
/**
* 进度条后退
* @param partList 颜色数组
* @param percentage 进度值
*/
function updateProgressBackward(partList: ProgressColor[], percentage: number) {
return partList.some((part) => {
if (percentage <= part.percentage) {
update(percentage, part.color)
return true
}
})
}
/**
* 更新进度条
*/
async function updateProgress() {
const { percentage, color } = props
if (!isDef(color) || (isArray(color) && color.length === 0)) {
changeCount.value = Math.abs(percentage - showPercent.value)
await pause()
showPercent.value = percentage
return
}
if (showPercent.value === percentage) return
const colorArray = isArray(color) ? color : [color]
validateColorArray(colorArray)
const partList = createPartList(colorArray)
showPercent.value > percentage ? updateProgressBackward(partList, percentage) : updateProgressForward(partList, percentage)
}
/**
* 判断是否是颜色数组
* @param array 颜色数组
*/
function isProgressColorArray(array: string[] | ProgressColor[]): array is ProgressColor[] {
return array.every(
(color) => isObj(color) && Object.prototype.hasOwnProperty.call(color, 'color') && Object.prototype.hasOwnProperty.call(color, 'percentage')
)
}
/**
* 判断是否是字符串数组
* @param array 颜色数组
*/
function isStringArray(array: string[] | ProgressColor[]): array is string[] {
return array.every((item) => typeof item === 'string')
}
/**
* 颜色数组校验
* @param colorArray 颜色数组
*/
function validateColorArray(colorArray: string[] | ProgressColor[]) {
const isStrArray = isStringArray(colorArray)
const isObjArray = isProgressColorArray(colorArray)
if (!isStrArray && !isObjArray) {
throw Error('Color must be String or Object with color and percentage')
}
if (isObjArray && colorArray.some(({ percentage }) => Number.isNaN(percentage))) {
throw Error('All the percentage must can be formatted to Number')
}
}
/**
* 创建颜色数组
* @param colorArray 颜色数组
* @return 颜色数组
*/
function createPartList(colorArray: string[] | ProgressColor[]) {
const partNum = 100 / colorArray.length
return isProgressColorArray(colorArray)
? colorArray.sort((a, b) => a.percentage - b.percentage)
: colorArray.map((item, index) => ({
color: item,
percentage: (index + 1) * partNum
}))
}
function update(targetPercent: number, color: string) {
if (timer) return
const { duration } = props
changeCount.value = Math.abs(targetPercent - showPercent.value)
setTimeout(() => {
showPercent.value = targetPercent
showColor.value = color
timer = setTimeout(() => {
timer && clearTimeout(timer)
timer = null
updateProgress()
}, changeCount.value * duration)
}, 50)
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>