2025-07-21 16:47:04 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view
|
2025-09-04 20:18:18 +08:00
|
|
|
|
ref="loadingRef"
|
2025-07-21 16:47:04 +08:00
|
|
|
|
class="cl-loading"
|
|
|
|
|
|
:class="[
|
|
|
|
|
|
{
|
2025-08-08 11:28:28 +08:00
|
|
|
|
'cl-loading--dark': isDark && color == '',
|
2025-07-21 16:47:04 +08:00
|
|
|
|
'cl-loading--spin': loading,
|
|
|
|
|
|
'!border-r-transparent': true
|
|
|
|
|
|
},
|
|
|
|
|
|
pt.className
|
|
|
|
|
|
]"
|
|
|
|
|
|
:style="{
|
2025-09-04 20:18:18 +08:00
|
|
|
|
height: getPx(size!),
|
|
|
|
|
|
width: getPx(size!),
|
|
|
|
|
|
// #ifndef APP
|
|
|
|
|
|
borderWidth: '1px',
|
2025-08-08 11:28:28 +08:00
|
|
|
|
borderTopColor: color,
|
|
|
|
|
|
borderRightColor: 'transparent',
|
|
|
|
|
|
borderBottomColor: color,
|
|
|
|
|
|
borderLeftColor: color
|
2025-09-04 20:18:18 +08:00
|
|
|
|
// #endif
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}"
|
|
|
|
|
|
v-if="loading"
|
|
|
|
|
|
>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-09-04 20:18:18 +08:00
|
|
|
|
import { computed, nextTick, onMounted, ref, shallowRef, watch } from "vue";
|
|
|
|
|
|
import { ctx, isDark, parsePt } from "@/cool";
|
2025-07-21 16:47:04 +08:00
|
|
|
|
import type { ClIconProps } from "../cl-icon/props";
|
2025-08-13 15:21:16 +08:00
|
|
|
|
import { useSize } from "../../hooks";
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
|
|
|
|
|
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: ""
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-04 20:18:18 +08:00
|
|
|
|
const { getPxValue, getPx } = useSize();
|
2025-08-13 15:21:16 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 透传样式类型定义
|
|
|
|
|
|
type PassThrough = {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
icon?: ClIconProps;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 解析透传样式
|
|
|
|
|
|
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
|
|
|
|
|
|
2025-09-04 20:18:18 +08:00
|
|
|
|
// 组件引用
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
|
|
|
|
|
// 开始旋转动画
|
2025-09-04 20:18:18 +08:00
|
|
|
|
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
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
2025-09-04 20:18:18 +08:00
|
|
|
|
);
|
|
|
|
|
|
// #endif
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 组件挂载后监听loading状态
|
|
|
|
|
|
onMounted(() => {
|
2025-09-04 20:18:18 +08:00
|
|
|
|
// #ifdef APP
|
2025-07-21 16:47:04 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
computed(() => props.loading),
|
|
|
|
|
|
(val: boolean) => {
|
|
|
|
|
|
// 当loading为true时开始旋转
|
|
|
|
|
|
if (val) {
|
|
|
|
|
|
start();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
immediate: true
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
2025-09-04 20:18:18 +08:00
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
computed(() => [props.color, props.size, isDark.value]),
|
|
|
|
|
|
() => {
|
|
|
|
|
|
drawLoading();
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// #endif
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.cl-loading {
|
|
|
|
|
|
@apply flex flex-row items-center justify-center rounded-full;
|
2025-09-04 20:18:18 +08:00
|
|
|
|
|
|
|
|
|
|
// #ifndef APP
|
2025-07-21 16:47:04 +08:00
|
|
|
|
@apply border-surface-700 border-solid;
|
2025-09-04 20:18:18 +08:00
|
|
|
|
// #endif
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-08-08 11:28:28 +08:00
|
|
|
|
&--dark {
|
|
|
|
|
|
border-color: white !important;
|
|
|
|
|
|
border-right-color: transparent !important;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef H5 || MP
|
|
|
|
|
|
&--spin {
|
|
|
|
|
|
animation: spin 2.5s linear infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
from {
|
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|