cl-loading.uvue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. <template>
  2. <view
  3. ref="loadingRef"
  4. class="cl-loading"
  5. :class="[
  6. {
  7. 'cl-loading--dark': isDark && color == '',
  8. '!border-r-transparent': true
  9. },
  10. pt.className
  11. ]"
  12. :style="loadingStyle"
  13. v-if="loading"
  14. >
  15. </view>
  16. </template>
  17. <script setup lang="ts">
  18. import { computed, onMounted, shallowRef, watch } from "vue";
  19. import { createAnimation, ctx, isAppAndroid, isDark, parsePt } from "@/.cool";
  20. import type { ClIconProps } from "../cl-icon/props";
  21. import { useSize } from "../../hooks";
  22. defineOptions({
  23. name: "cl-loading"
  24. });
  25. // 定义组件属性
  26. const props = defineProps({
  27. // 透传样式
  28. pt: {
  29. type: Object,
  30. default: () => ({})
  31. },
  32. // 是否加载中
  33. loading: {
  34. type: Boolean,
  35. default: true
  36. },
  37. // 图标大小
  38. size: {
  39. type: Number,
  40. default: 14
  41. },
  42. // 图标颜色
  43. color: {
  44. type: String,
  45. default: ""
  46. }
  47. });
  48. const { toScale } = useSize();
  49. // 透传样式类型定义
  50. type PassThrough = {
  51. className?: string;
  52. icon?: ClIconProps;
  53. };
  54. // 解析透传样式
  55. const pt = computed(() => parsePt<PassThrough>(props.pt));
  56. // 组件引用
  57. const loadingRef = shallowRef<UniElement | null>(null);
  58. // 颜色值
  59. const color = computed<string>(() => {
  60. if (props.color == "") {
  61. return isDark.value ? "#ffffff" : (ctx.color["surface-700"] as string);
  62. }
  63. switch (props.color) {
  64. case "primary":
  65. return ctx.color["primary-500"] as string;
  66. case "success":
  67. return "#22c55e";
  68. case "warn":
  69. return "#eab308";
  70. case "error":
  71. return "#ef4444";
  72. case "info":
  73. return "#71717a";
  74. case "dark":
  75. return "#3f3f46";
  76. case "light":
  77. return "#ffffff";
  78. case "disabled":
  79. return "#d4d4d8";
  80. default:
  81. return props.color;
  82. }
  83. });
  84. // 大小
  85. const size = computed(() => toScale(props.size) + "px");
  86. // 加载样式
  87. const loadingStyle = computed(() => {
  88. const style = {
  89. height: size.value,
  90. width: size.value,
  91. borderWidth: "1px",
  92. borderTopColor: color.value,
  93. borderRightColor: "transparent",
  94. borderBottomColor: color.value,
  95. borderLeftColor: color.value
  96. };
  97. // 手动校准偏移量
  98. if (isAppAndroid()) {
  99. if (props.size <= 16) {
  100. style["position"] = "relative";
  101. style["top"] = "-0.5px";
  102. }
  103. }
  104. return style;
  105. });
  106. // 开始旋转动画
  107. async function start() {
  108. createAnimation(loadingRef.value, {
  109. duration: 2500,
  110. loop: -1,
  111. timingFunction: "linear"
  112. })
  113. .rotate("0deg", "360deg")
  114. .play();
  115. }
  116. // 组件挂载后监听loading状态
  117. onMounted(() => {
  118. watch(
  119. computed(() => props.loading),
  120. (val: boolean) => {
  121. // 当loading为true时开始旋转
  122. if (val) {
  123. start();
  124. }
  125. },
  126. {
  127. immediate: true
  128. }
  129. );
  130. });
  131. </script>
  132. <style lang="scss" scoped>
  133. .cl-loading {
  134. @apply flex flex-row items-center justify-center rounded-full;
  135. @apply border-surface-700 border-solid;
  136. &--dark {
  137. border-color: white !important;
  138. border-right-color: transparent !important;
  139. }
  140. }
  141. </style>