Files
hospital-rescue-app-v2/uni_modules/tmx-ui/components/x-countdown/x-countdown.uvue
T
2026-09-24 16:25:22 +08:00

298 lines
7.1 KiB
Plaintext

<script lang="ts" setup>
import { type PropType, ref, computed, watch, onMounted, onBeforeUnmount } from "vue"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { checkIsCssUnit } from "../../core/util/xCoreUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
type TIME_OBJ = {
ms : string,
ss : string,
mm : string,
hh : string,
dd : string,
format : string
}
/**
*
* @name 倒计时 xCountdown
* @description 倒计时,可以精确到秒,毫秒,记住
* @page /pages/index/countdown
* @category 展示组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xCountdown"})
defineSlots<{
default(props: {
status: "initial" | "running" | "paused" | "finished",
time: number,
label: string,
ms: string,
ss: string,
mm: string,
hh: string,
dd: string,
}): any
}>()
const emits = defineEmits([
/**
* 时间变化时触发
* @param {number} time - 当前剩余的时间
*/
"change",
/**
* 暂停时触发
*/
'pause',
/**
* 开始时触发
*/
'start',
/**
* 结束时触发
*/
'complete'
])
type xCountdownPropsType = {
/** */
time: number,
/**
* 指令,可以通过变动此值来达到暂停,开始,结束的功能,当然也可以通过ref方法控制。"pause" | "play" | "reset" | ""
*/
actions: "pause" | "play" | "reset" | "",
/**
* 显示格式
* DD天,HH时,MM分,SS秒,MS毫秒
*/
format: string,
autoStart: boolean,
/**
* 以秒还是毫秒为单位到计时。
* ss|ms
*/
unit: "ss" | "ms",
/** 文本大小 */
fontSize: string,
/** 文本颜色 */
color: string,
/**
* 是否使用验证码模式
* 统一倒计时实例
*/
captcha: boolean
}
const props = withDefaults(defineProps<xCountdownPropsType>(), {
time: 0,
actions: "",
format: "DD天HH时MM分SS秒",
autoStart: false,
unit: "ss",
fontSize: "16",
color: "#333333",
captcha: false
})
// 状态
const intervalId = ref<number>(0)
const status = ref<"initial" | "running" | "paused" | "finished">("initial")
const totalTime = ref<number>(0)
function displayTime(): TIME_OBJ {
let milliseconds = totalTime.value;
let seconds = Math.floor(milliseconds / 1000);
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
let days = Math.floor(hours / 24);
milliseconds %= 1000;
seconds %= 60;
minutes %= 60;
hours %= 24;
let day_str = days < 10 ? "0" + days.toString() : days.toString()
let hours_str = hours < 10 ? "0" + hours.toString() : hours.toString()
let minutes_str = minutes < 10 ? "0" + minutes.toString() : minutes.toString()
let seconds_str = seconds < 10 ? "0" + seconds.toString() : seconds.toString()
let milliseconds_str = milliseconds < 10 ? "0" + milliseconds.toString() : milliseconds.toString()
let formattedTime = props.format.replace("DD", day_str)
formattedTime = formattedTime.replace("HH", hours_str)
formattedTime = formattedTime.replace("MM", minutes_str)
formattedTime = formattedTime.replace("SS", seconds_str)
formattedTime = formattedTime.replace("MS", milliseconds_str)
return {
ms: milliseconds_str,
ss: seconds_str,
mm: minutes_str,
hh: hours_str,
dd: day_str,
format: formattedTime
} as TIME_OBJ;
}
// 计算属性
const _label = computed((): TIME_OBJ => {
return displayTime();
})
const _time = computed((): number => props.time)
const _color = computed((): string => getDefaultColor(props.color))
const _fontSize = computed((): string => checkIsCssUnit(props.fontSize, xConfig.unit))
// 工具函数与业务函数(被调用者在前)
function setCactcha(): void {
if (!props.captcha) return;
uni.setStorageSync("timeidLasttime", totalTime.value)
}
/**
* 开始
*/
function start(): void {
let intms = props.unit == "ss" ? 1000 : 16
if (status.value === "initial" || status.value === "paused") {
status.value = "running";
/** 开始时触发 */
emits("start")
setCactcha()
intervalId.value = setInterval(() => {
totalTime.value -= intms;
if (props.captcha) {
uni.setStorageSync("timeidLasttime", totalTime.value)
}
/**
* 时间变化时触发
* @param time {number} 当前剩余的时间
*/
emits("change", totalTime.value)
if (totalTime.value < 0) {
totalTime.value = 0
if (props.captcha) {
uni.setStorageSync("timeidLasttime", totalTime.value)
}
clearInterval(intervalId.value);
status.value = "finished"; // 倒计时结束
/** 结束时触发 */
emits("complete")
return;
}
}, intms);
}
}
/**
* 暂停
*/
function pause(): void {
if (status.value === "running") {
clearInterval(intervalId.value);
status.value = "paused"; // 暂停倒计时
setCactcha()
/** 暂停时触发 */
emits("pause")
}
}
/**
* 重置
*/
function reset(): void {
clearInterval(intervalId.value);
status.value = "initial"; // 重置倒计时为未开始状态
totalTime.value = _time.value
if (props.captcha) {
uni.setStorageSync("timeidLasttime", 0)
}
}
/**
* 获取当前运行状态
*/
function getStatus(): "initial" | "running" | "paused" | "finished" {
return status.value
}
// 生命周期
onMounted(() => {
totalTime.value = _time.value;
if (props.captcha) {
const oldTime = uni.getStorageSync("timeidLasttime")
if (oldTime != null && typeof oldTime == 'number') {
if (oldTime > 0) {
totalTime.value = oldTime;
start();
return;
}
}
}
if (props.autoStart) {
start();
}
})
onBeforeUnmount(() => {
clearInterval(intervalId.value);
if (status.value == 'running' || status.value == 'paused') {
setCactcha()
}
})
watch((): number => props.time, (): void => {
reset();
})
watch((): any => props.actions, (act:"pause" | "play" | "reset" | ""): void => {
if (act == 'reset') {
reset();
} else if (act == 'play') {
start();
} else if (act == 'pause') {
pause();
}
})
// 暴露可选方法
defineExpose({
/**
* 开始
*/
start,
/**
* 暂停
*/
pause,
/**
* 重置
*/
reset,
/**
* 获取当前运行状态,返回值是:"initial" | "running" | "paused" | "finished"
*/
getStatus
})
</script>
<template>
<view>
<!--
@slot 插槽
@prop {string} status - 状态值可能为:"initial" | "running" | "paused" | "finished"
@prop {number} time - 当前剩余的时间:单位为毫秒
@prop {string} label - 当前被属性format格式化后的文本
@prop {string} ms - 剩余的毫秒数
@prop {string} ss - 剩余的秒数
@prop {string} mm - 剩余的分钟
@prop {string} hh - 剩余的小时
@prop {string} dd - 剩余的天数
-->
<slot :status="status" :time="totalTime" :label="_label.format" :ms="_label.ms" :ss="_label.ss" :mm="_label.mm"
:hh="_label.hh" :dd="_label.dd">
<text :style="{color:_color,fontSize:_fontSize}">{{_label.format}}</text>
</slot>
</view>
</template>
<style scoped>
</style>