| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- <template>
- <view
- ref="loadingRef"
- class="cl-loading"
- :class="[
- {
- 'cl-loading--dark': isDark && color == '',
- 'cl-loading--spin': loading,
- '!border-r-transparent': true
- },
- pt.className
- ]"
- :style="{
- height: getPx(size!),
- width: getPx(size!),
- // #ifndef APP
- borderWidth: '1px',
- borderTopColor: color,
- borderRightColor: 'transparent',
- borderBottomColor: color,
- borderLeftColor: color
- // #endif
- }"
- v-if="loading"
- >
- </view>
- </template>
- <script setup lang="ts">
- import { computed, nextTick, onMounted, ref, shallowRef, watch } from "vue";
- import { ctx, isDark, parsePt } from "@/cool";
- import type { ClIconProps } from "../cl-icon/props";
- import { useSize } from "../../hooks";
- defineOptions({
- name: "cl-loading"
- });
- // 定义组件属性
- const props = defineProps({
- // 透传样式
- pt: {
- type: Object,
- default: () => ({})
- },
- // 是否加载中
- loading: {
- type: Boolean,
- default: true
- },
- // 图标大小
- size: {
- type: [Number, String],
- default: 24
- },
- // 图标颜色
- color: {
- type: String,
- default: ""
- }
- });
- const { getPxValue, getPx } = useSize();
- // 透传样式类型定义
- type PassThrough = {
- className?: string;
- icon?: ClIconProps;
- };
- // 解析透传样式
- const pt = computed(() => parsePt<PassThrough>(props.pt));
- // 组件引用
- const loadingRef = shallowRef<UniElement | null>(null);
- const color = computed<string>(() => {
- if (props.color == "") {
- return isDark.value ? "#ffffff" : (ctx.color["surface-700"] as string);
- }
- switch (props.color) {
- case "primary":
- return ctx.color["primary-500"] as string;
- case "success":
- return "#22c55e";
- case "warn":
- return "#eab308";
- case "error":
- return "#ef4444";
- case "info":
- return "#71717a";
- case "dark":
- return "#3f3f46";
- case "light":
- return "#ffffff";
- case "disabled":
- return "#d4d4d8";
- default:
- return props.color;
- }
- });
- async function drawLoading() {
- // #ifdef APP
- await nextTick();
- if (loadingRef.value == null) {
- return;
- }
- const drawContext = loadingRef.value!.getDrawableContext();
- // 重置画布,准备绘制新的loading图形
- drawContext!.reset();
- drawContext!.beginPath();
- // 获取loading图标的尺寸和半径
- const size = getPxValue(props.size!);
- const radius = size / 2;
- const centerX = radius;
- const centerY = radius;
- // 设置线宽
- const lineWidth = 1;
- // 缺口角度为60度(Math.PI / 3),用于形成loading的缺口效果
- const gapAngle = Math.PI / 3; // 缺口60度
- // 起始角度为顶部(-90度)
- const startAngle = -Math.PI / 2; // 从顶部开始
- // 结束角度为起始角度加上300度(360-60),形成环形缺口
- const endAngle = startAngle + (2 * Math.PI - gapAngle); // 画300度
- // 绘制圆弧,形成loading环
- drawContext!.arc(centerX, centerY, radius - lineWidth, startAngle, endAngle, false);
- // 设置描边颜色和线宽
- drawContext!.strokeStyle = color.value;
- drawContext!.lineWidth = lineWidth;
- // 执行描边操作
- drawContext!.stroke();
- // 更新画布显示
- drawContext!.update();
- // #endif
- }
- // 开始旋转动画
- async function start() {
- // #ifdef APP
- await drawLoading();
- if (loadingRef.value == null) {
- return;
- }
- loadingRef.value!.animate(
- [
- {
- transform: "rotate(0deg)"
- },
- {
- transform: "rotate(360deg)"
- }
- ],
- {
- duration: 2500,
- easing: "linear",
- iterations: 999999
- }
- );
- // #endif
- }
- // 组件挂载后监听loading状态
- onMounted(() => {
- // #ifdef APP
- watch(
- computed(() => props.loading),
- (val: boolean) => {
- // 当loading为true时开始旋转
- if (val) {
- start();
- }
- },
- {
- immediate: true
- }
- );
- watch(
- computed(() => [props.color, props.size, isDark.value]),
- () => {
- drawLoading();
- }
- );
- // #endif
- });
- </script>
- <style lang="scss" scoped>
- .cl-loading {
- @apply flex flex-row items-center justify-center rounded-full;
- // #ifndef APP
- @apply border-surface-700 border-solid;
- // #endif
- &--dark {
- border-color: white !important;
- border-right-color: transparent !important;
- }
- // #ifdef H5 || MP
- &--spin {
- animation: spin 2.5s linear infinite;
- }
- @keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
- }
- // #endif
- }
- </style>
|