版本发布
This commit is contained in:
312
uni_modules/cool-ui/components/cl-countdown/cl-countdown.uvue
Normal file
312
uni_modules/cool-ui/components/cl-countdown/cl-countdown.uvue
Normal file
@@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<view class="cl-countdown" :class="[pt.className]">
|
||||
<view
|
||||
class="cl-countdown__item"
|
||||
:class="[`${item.isSplitor ? pt.splitor?.className : pt.text?.className}`]"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
>
|
||||
<slot name="item" :item="item">
|
||||
<cl-text>{{ item.value }}</cl-text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick, onBeforeUnmount, onBeforeMount, computed, type PropType } from "vue";
|
||||
import type { PassThroughProps } from "../../types";
|
||||
import { dayUts, get, has, isEmpty, parsePt } from "@/cool";
|
||||
|
||||
type Item = {
|
||||
value: string;
|
||||
isSplitor: boolean;
|
||||
};
|
||||
|
||||
defineOptions({
|
||||
name: "cl-countdown"
|
||||
});
|
||||
|
||||
defineSlots<{
|
||||
item(props: { item: Item }): any;
|
||||
}>();
|
||||
|
||||
const props = defineProps({
|
||||
// 样式穿透配置
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 格式化模板,支持 {d}天{h}:{m}:{s} 格式
|
||||
format: {
|
||||
type: String,
|
||||
default: "{h}:{m}:{s}"
|
||||
},
|
||||
// 是否隐藏为0的单位
|
||||
hideZero: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 倒计时天数
|
||||
day: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 倒计时小时数
|
||||
hour: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 倒计时分钟数
|
||||
minute: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 倒计时秒数
|
||||
second: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 结束时间,可以是Date对象或日期字符串
|
||||
datetime: {
|
||||
type: [Date, String] as PropType<Date | string>,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 组件事件定义
|
||||
*/
|
||||
const emit = defineEmits(["stop", "done", "change"]);
|
||||
|
||||
/**
|
||||
* 样式穿透类型定义
|
||||
*/
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
text?: PassThroughProps;
|
||||
splitor?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 解析样式穿透配置
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 定时器ID,用于清除定时器
|
||||
let timer: number = 0;
|
||||
|
||||
// 当前剩余秒数
|
||||
const seconds = ref(0);
|
||||
|
||||
// 倒计时运行状态
|
||||
const isRunning = ref(false);
|
||||
|
||||
// 显示列表
|
||||
const list = ref<Item[]>([]);
|
||||
|
||||
/**
|
||||
* 倒计时选项类型定义
|
||||
*/
|
||||
type Options = {
|
||||
day?: number;
|
||||
hour?: number;
|
||||
minute?: number;
|
||||
second?: number;
|
||||
datetime?: Date | string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 将时间单位转换为总秒数
|
||||
* @param options 时间选项,支持天、时、分、秒或具体日期时间
|
||||
* @returns 总秒数
|
||||
*/
|
||||
function toSeconds({ day, hour, minute, second, datetime }: Options) {
|
||||
if (datetime != null) {
|
||||
// 如果提供了具体日期时间,计算与当前时间的差值
|
||||
const diff = dayUts(datetime).diff(dayUts());
|
||||
return Math.max(0, Math.floor(diff / 1000));
|
||||
} else {
|
||||
// 否则将各个时间单位转换为秒数
|
||||
return Math.max(
|
||||
0,
|
||||
(day ?? 0) * 86400 + (hour ?? 0) * 3600 + (minute ?? 0) * 60 + (second ?? 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行倒计时逻辑
|
||||
* 计算剩余时间并格式化显示
|
||||
*/
|
||||
function countDown() {
|
||||
// 计算天、时、分、秒,使用更简洁的计算方式
|
||||
const totalSeconds = Math.floor(seconds.value);
|
||||
const day = Math.floor(totalSeconds / 86400); // 86400 = 24 * 60 * 60
|
||||
const hour = Math.floor((totalSeconds % 86400) / 3600); // 3600 = 60 * 60
|
||||
const minute = Math.floor((totalSeconds % 3600) / 60);
|
||||
const second = totalSeconds % 60;
|
||||
|
||||
// 格式化时间对象,用于模板替换
|
||||
const t = {
|
||||
d: day.toString(),
|
||||
h: hour.toString().padStart(2, "0"),
|
||||
m: minute.toString().padStart(2, "0"),
|
||||
s: second.toString().padStart(2, "0")
|
||||
};
|
||||
|
||||
// 控制是否隐藏零值,初始为true表示隐藏
|
||||
let isHide = true;
|
||||
// 记录开始隐藏的位置索引,-1表示不隐藏
|
||||
let start = -1;
|
||||
|
||||
// 根据格式模板生成显示列表
|
||||
list.value = (props.format.split(/[{,}]/) as string[])
|
||||
.map((e, i) => {
|
||||
const value = has(t, e) ? (get(t, e) as string) : e;
|
||||
const isSplitor = /^\D+$/.test(value);
|
||||
|
||||
if (props.hideZero) {
|
||||
if (isHide && !isSplitor) {
|
||||
if (value == "00" || value == "0" || isEmpty(value)) {
|
||||
start = i;
|
||||
isHide = true;
|
||||
} else {
|
||||
isHide = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
isSplitor
|
||||
} as Item;
|
||||
})
|
||||
.filter((e, i) => {
|
||||
return !isEmpty(e.value) && (start == -1 ? true : start < i);
|
||||
})
|
||||
.filter((e, i) => {
|
||||
if (i == 0 && e.isSplitor) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// 触发change事件
|
||||
emit("change", list.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除定时器并重置状态
|
||||
*/
|
||||
function clear() {
|
||||
clearTimeout(timer);
|
||||
timer = 0;
|
||||
isRunning.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止倒计时
|
||||
*/
|
||||
function stop() {
|
||||
clear();
|
||||
emit("stop");
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒计时结束处理
|
||||
*/
|
||||
function done() {
|
||||
clear();
|
||||
emit("done");
|
||||
}
|
||||
|
||||
/**
|
||||
* 继续倒计时
|
||||
* 启动定时器循环执行倒计时逻辑
|
||||
*/
|
||||
function next() {
|
||||
// 如果时间已到或正在运行,直接返回
|
||||
if (seconds.value <= 0 || isRunning.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isRunning.value = true;
|
||||
|
||||
/**
|
||||
* 倒计时循环函数
|
||||
* 每秒执行一次,直到时间结束
|
||||
*/
|
||||
function loop() {
|
||||
countDown();
|
||||
|
||||
if (seconds.value <= 0) {
|
||||
done();
|
||||
return;
|
||||
} else {
|
||||
seconds.value--;
|
||||
// @ts-ignore
|
||||
timer = setTimeout(() => loop(), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始倒计时
|
||||
* @param options 可选的倒计时参数,不传则使用props中的值
|
||||
*/
|
||||
function start(options: Options | null = null) {
|
||||
nextTick(() => {
|
||||
// 计算初始秒数
|
||||
seconds.value = toSeconds({
|
||||
day: options?.day ?? props.day,
|
||||
hour: options?.hour ?? props.hour,
|
||||
minute: options?.minute ?? props.minute,
|
||||
second: options?.second ?? props.second,
|
||||
datetime: options?.datetime ?? props.datetime
|
||||
});
|
||||
|
||||
// 开始倒计时
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// 监听时间单位变化,重新开始倒计时
|
||||
watch(
|
||||
computed(() => [props.day, props.hour, props.minute, props.second] as number[]),
|
||||
() => {
|
||||
start();
|
||||
}
|
||||
);
|
||||
|
||||
// 监听结束时间变化,重新开始倒计时
|
||||
watch(
|
||||
computed(() => props.datetime),
|
||||
() => {
|
||||
start();
|
||||
}
|
||||
);
|
||||
|
||||
// 组件销毁前停止倒计时
|
||||
onBeforeUnmount(() => stop());
|
||||
|
||||
// 组件挂载前开始倒计时
|
||||
onBeforeMount(() => start());
|
||||
|
||||
defineExpose({
|
||||
start,
|
||||
stop,
|
||||
done,
|
||||
isRunning
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-countdown {
|
||||
@apply flex flex-row items-center;
|
||||
|
||||
&__item {
|
||||
@apply flex flex-row justify-center items-center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
19
uni_modules/cool-ui/components/cl-countdown/props.ts
Normal file
19
uni_modules/cool-ui/components/cl-countdown/props.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { PassThroughProps } from "../../types";
|
||||
|
||||
export type ClCountdownPassThrough = {
|
||||
className?: string;
|
||||
text?: PassThroughProps;
|
||||
splitor?: PassThroughProps;
|
||||
};
|
||||
|
||||
export type ClCountdownProps = {
|
||||
className?: string;
|
||||
pt?: ClCountdownPassThrough;
|
||||
format?: string;
|
||||
hideZero?: boolean;
|
||||
day?: number;
|
||||
hour?: number;
|
||||
minute?: number;
|
||||
second?: number;
|
||||
datetime?: Date | string;
|
||||
};
|
||||
Reference in New Issue
Block a user