index.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <template>
  2. <div
  3. class="m-progress"
  4. :class="[
  5. 'm-progress--' + type,
  6. status ? 'is-' + status : '',
  7. ]">
  8. <div class="m-progress-bar" v-if="type === 'line'">
  9. <div class="m-progress-bar__outer" :style="strokeHeightStyle">
  10. <div class="m-progress-bar__inner" :style="barStyle">
  11. <div class="m-progress-bar__innerText" v-if="showText && textInside">{{content}}</div>
  12. </div>
  13. </div>
  14. </div>
  15. <div class="m-progress-circle" :style="{height: width + 'px', width: width + 'px'}" v-else>
  16. <svg viewBox="0 0 100 100">
  17. <circle
  18. cx="50"
  19. cy="50"
  20. r="47"
  21. stroke="#dcdcdc"
  22. :stroke-width="relativeStrokeWidth"
  23. fill="none"
  24. />
  25. <!-- 内圈圆,用于展示进度 -->
  26. <circle
  27. cx="50"
  28. cy="50"
  29. r="47"
  30. :stroke="circleStroke"
  31. :stroke-width="percentage ? relativeStrokeWidth : 0"
  32. fill="none"
  33. :stroke-dasharray="strokeDasharray"
  34. :stroke-dashoffset="strokeDashoffset"
  35. :stroke-linecap="strokeLinecap"
  36. transform="rotate(90,50,50)"
  37. />
  38. </svg>
  39. </div>
  40. <div class="m-progress__text" v-if="showText && !textInside">
  41. <slot v-if="!status">{{content}}</slot>
  42. <i class="iconfont" :class="iconClass" v-else></i>
  43. </div>
  44. </div>
  45. </template>
  46. <script>
  47. export default {
  48. name: "mProgress"
  49. };
  50. </script>
  51. <script setup>
  52. import { computed } from 'vue-demi';
  53. const props = defineProps({
  54. percentage: {
  55. type: Number,
  56. default: 0,
  57. required: true,
  58. validator: val => val >= 0 && val <= 100
  59. },
  60. type: {
  61. type: String,
  62. default: 'line',
  63. validator: val => ['line', 'circle', 'dashboard'].indexOf(val) > -1
  64. },
  65. showText: {
  66. type: Boolean,
  67. default: true
  68. },
  69. textInside: {
  70. type: Boolean,
  71. default: false
  72. },
  73. status: {
  74. type: String,
  75. validator: val => ['success', 'warning', 'error'].indexOf(val) > -1
  76. },
  77. format: Function,
  78. strokeWidth: {
  79. type: Number,
  80. default: 6
  81. },
  82. color: {
  83. type: [String, Array, Function],
  84. default: ''
  85. },
  86. width: {
  87. type: Number,
  88. default: 126
  89. },
  90. strokeLinecap: {
  91. type: String,
  92. default: 'round'
  93. }
  94. })
  95. const barStyle = computed(() => {
  96. return {
  97. width: props.percentage + '%',
  98. backgroundColor: getCurrentColor(props.percentage)
  99. }
  100. })
  101. const strokeHeightStyle = computed(() => {
  102. return {
  103. height: props.strokeWidth + 'px'
  104. }
  105. })
  106. const relativeStrokeWidth = computed(() => {
  107. return (props.strokeWidth / props.width * 100).toFixed(1);
  108. })
  109. // 半径
  110. const radius = computed(() => {
  111. if (props.type === 'circle' || props.type === 'dashboard') {
  112. return parseInt(50 - parseFloat(relativeStrokeWidth.value) / 2, 10);
  113. } else {
  114. return 0;
  115. }
  116. })
  117. // 周长
  118. const perimeter = computed(() => {
  119. return 2 * Math.PI * radius.value;
  120. })
  121. const rate = computed(() => {
  122. return props.type === 'dashboard' ? 0.75 : 1;
  123. })
  124. const strokeDasharray = computed(() => {
  125. return `${perimeter.value * rate.value * (props.percentage / 100) }px, ${perimeter.value}px`
  126. })
  127. const strokeDashoffset = computed(() => {
  128. const offset = -1 * perimeter.value * (1 - rate.value) / 2;
  129. return `${offset}px`;
  130. })
  131. const iconClass = computed(() => {
  132. if(props.status === 'warning') {
  133. return 'icon-warning-filling';
  134. }
  135. if(props.status === 'success') {
  136. return 'icon-success-filling'
  137. }
  138. if(props.status === 'error') {
  139. return 'icon-delete-filling'
  140. }
  141. })
  142. const content = computed(() => {
  143. if (typeof props.format === 'function') {
  144. return props.format(props.percentage) || '';
  145. } else {
  146. return `${props.percentage}%`;
  147. }
  148. })
  149. const circleStroke = computed(() => {
  150. let ret;
  151. if (props.color) {
  152. ret = getCurrentColor(props.percentage);
  153. } else {
  154. switch (props.status) {
  155. case 'success':
  156. ret = '#13ce66';
  157. break;
  158. case 'warning':
  159. ret = '#e6a23c';
  160. break;
  161. case 'error':
  162. ret = '#ff4949';
  163. break;
  164. default:
  165. ret = '#20a0ff';
  166. }
  167. }
  168. return ret;
  169. })
  170. const getCurrentColor = (percentage) => {
  171. if (typeof props.color === 'function') {
  172. return props.color(percentage);
  173. } else if (typeof props.color === 'string') {
  174. return props.color;
  175. } else {
  176. return getLevelColor(percentage);
  177. }
  178. }
  179. const getLevelColor = (percentage) => {
  180. const colorArray = getColorArray().sort((a, b) => a.percentage - b.percentage);
  181. for (let i = 0; i < colorArray.length; i++) {
  182. if (colorArray[i].percentage > percentage) {
  183. return colorArray[i].color;
  184. }
  185. }
  186. return colorArray[colorArray.length - 1].color;
  187. }
  188. const getColorArray = () => {
  189. const colorArr = props.color;
  190. const span = 100 / colorArr.length;
  191. return colorArr.map((seriesColor, index) => {
  192. if (typeof seriesColor === 'string') {
  193. return {
  194. color: seriesColor,
  195. percentage: (index + 1) * span
  196. }
  197. }
  198. return seriesColor
  199. });
  200. }
  201. </script>
  202. <style lang="scss" scoped>
  203. @import '../../styles/components/progress.scss';
  204. </style>