wd-progress.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <template>
  2. <view :class="`wd-progress ${customClass}`" :style="customStyle">
  3. <view class="wd-progress__outer">
  4. <view :class="`wd-progress__inner ${innerClass}`" :style="rootStyle"></view>
  5. </view>
  6. <view v-if="!hideText" class="wd-progress__label">{{ percentage }}%</view>
  7. <wd-icon
  8. v-else-if="status"
  9. :custom-class="`wd-progress__label wd-progress__icon ${innerClass}`"
  10. :name="iconName"
  11. :color="typeof color === 'string' ? color : ''"
  12. ></wd-icon>
  13. </view>
  14. </template>
  15. <script lang="ts">
  16. export default {
  17. name: 'wd-progress',
  18. options: {
  19. virtualHost: true,
  20. addGlobalClass: true,
  21. styleIsolation: 'shared'
  22. }
  23. }
  24. </script>
  25. <script lang="ts" setup>
  26. import wdIcon from '../wd-icon/wd-icon.vue'
  27. import { computed, ref, watch } from 'vue'
  28. import { isArray, isDef, isObj, objToStyle, pause } from '../common/util'
  29. import { progressProps, type ProgressColor } from './types'
  30. const props = defineProps(progressProps)
  31. const showColor = ref<string>('')
  32. const showPercent = ref<number>(0)
  33. const changeCount = ref<number>(0)
  34. let timer: ReturnType<typeof setTimeout> | null = null
  35. const rootStyle = computed(() => {
  36. return objToStyle({
  37. background: showColor.value,
  38. width: `${showPercent.value}%`,
  39. 'transition-duration': `${changeCount.value * props.duration * 0.001}s`
  40. })
  41. })
  42. const innerClass = computed(() => (props.status ? `is-${props.status}` : ''))
  43. const iconName = computed(() => {
  44. let icon: string = ''
  45. switch (props.status) {
  46. case 'danger':
  47. icon = 'close-outline'
  48. break
  49. case 'success':
  50. icon = 'check-outline'
  51. break
  52. case 'warning':
  53. icon = 'warn-bold'
  54. break
  55. default:
  56. break
  57. }
  58. return icon
  59. })
  60. watch(
  61. () => [props.percentage, props.color, props.duration],
  62. () => {
  63. validatePercentage(props.percentage)
  64. updateProgress()
  65. },
  66. { immediate: true }
  67. )
  68. function validatePercentage(value: number) {
  69. if (Number.isNaN(value) || value < 0 || value > 100) {
  70. console.error('The value of percentage must be between 0 and 100')
  71. }
  72. }
  73. /**
  74. * 进度条前进
  75. * @param partList 颜色数组
  76. * @param percentage 进度值
  77. */
  78. function updateProgressForward(partList: ProgressColor[], percentage: number) {
  79. return partList.some((part, index) => {
  80. if (showPercent.value < part.percentage && part.percentage <= percentage) {
  81. update(part.percentage, part.color)
  82. return true
  83. } else if (index === partList.length - 1) {
  84. update(percentage, part.color)
  85. }
  86. })
  87. }
  88. /**
  89. * 进度条后退
  90. * @param partList 颜色数组
  91. * @param percentage 进度值
  92. */
  93. function updateProgressBackward(partList: ProgressColor[], percentage: number) {
  94. return partList.some((part) => {
  95. if (percentage <= part.percentage) {
  96. update(percentage, part.color)
  97. return true
  98. }
  99. })
  100. }
  101. /**
  102. * 更新进度条
  103. */
  104. async function updateProgress() {
  105. const { percentage, color } = props
  106. if (!isDef(color) || (isArray(color) && color.length === 0)) {
  107. changeCount.value = Math.abs(percentage - showPercent.value)
  108. await pause()
  109. showPercent.value = percentage
  110. return
  111. }
  112. if (showPercent.value === percentage) return
  113. const colorArray = isArray(color) ? color : [color]
  114. validateColorArray(colorArray)
  115. const partList = createPartList(colorArray)
  116. showPercent.value > percentage ? updateProgressBackward(partList, percentage) : updateProgressForward(partList, percentage)
  117. }
  118. /**
  119. * 判断是否是颜色数组
  120. * @param array 颜色数组
  121. */
  122. function isProgressColorArray(array: string[] | ProgressColor[]): array is ProgressColor[] {
  123. return array.every(
  124. (color) => isObj(color) && Object.prototype.hasOwnProperty.call(color, 'color') && Object.prototype.hasOwnProperty.call(color, 'percentage')
  125. )
  126. }
  127. /**
  128. * 判断是否是字符串数组
  129. * @param array 颜色数组
  130. */
  131. function isStringArray(array: string[] | ProgressColor[]): array is string[] {
  132. return array.every((item) => typeof item === 'string')
  133. }
  134. /**
  135. * 颜色数组校验
  136. * @param colorArray 颜色数组
  137. */
  138. function validateColorArray(colorArray: string[] | ProgressColor[]) {
  139. const isStrArray = isStringArray(colorArray)
  140. const isObjArray = isProgressColorArray(colorArray)
  141. if (!isStrArray && !isObjArray) {
  142. throw Error('Color must be String or Object with color and percentage')
  143. }
  144. if (isObjArray && colorArray.some(({ percentage }) => Number.isNaN(percentage))) {
  145. throw Error('All the percentage must can be formatted to Number')
  146. }
  147. }
  148. /**
  149. * 创建颜色数组
  150. * @param colorArray 颜色数组
  151. * @return 颜色数组
  152. */
  153. function createPartList(colorArray: string[] | ProgressColor[]) {
  154. const partNum = 100 / colorArray.length
  155. return isProgressColorArray(colorArray)
  156. ? colorArray.sort((a, b) => a.percentage - b.percentage)
  157. : colorArray.map((item, index) => ({
  158. color: item,
  159. percentage: (index + 1) * partNum
  160. }))
  161. }
  162. function update(targetPercent: number, color: string) {
  163. if (timer) return
  164. const { duration } = props
  165. changeCount.value = Math.abs(targetPercent - showPercent.value)
  166. setTimeout(() => {
  167. showPercent.value = targetPercent
  168. showColor.value = color
  169. timer = setTimeout(() => {
  170. timer && clearTimeout(timer)
  171. timer = null
  172. updateProgress()
  173. }, changeCount.value * duration)
  174. }, 50)
  175. }
  176. </script>
  177. <style lang="scss" scoped>
  178. @import './index.scss';
  179. </style>