Files
frost-navigation/src/views/ToolboxView/Other/TimerTool.vue

417 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="tool-detail-page">
<!-- 计时器显示 -->
<n-card size="small" title="计时器">
<div class="timer-display">
<span class="time-main">{{ formatTimeMain(data.timeDisplay) }}</span>
<span class="time-milliseconds">{{ formatTimeMilliseconds(data.timeDisplay) }}</span>
</div>
</n-card>
<!-- 控制按钮 -->
<n-card size="small" title="控制">
<n-flex justify="center" gap="12">
<n-button
type="success"
:disabled="data.isRunning"
@click="startTimer"
>开始</n-button>
<n-button
type="warning"
:disabled="!data.isRunning"
@click="pauseTimer"
>暂停</n-button>
<n-button
type="error"
@click="resetTimer"
>重置</n-button>
<n-button
type="primary"
@click="recordTime"
>记录</n-button>
</n-flex>
</n-card>
<!-- 设置 -->
<n-card size="small" title="设置">
<n-form
class="form-no-feedback"
label-align="left"
label-placement="top"
label-width="auto"
>
<!-- 计时模式 -->
<n-form-item label="计时模式">
<n-radio-group v-model:value="data.timerMode">
<n-radio-button label="正计时" :value="'countUp'" />
<n-radio-button label="倒计时" :value="'countDown'" />
</n-radio-group>
</n-form-item>
<!-- 倒计时时间设置 -->
<n-form-item label="倒计时设置" v-if="data.timerMode === 'countDown'">
<n-input-group>
<n-input-number
v-model:value="data.countDownTime.hours"
:min="0"
:max="99"
:precision="0"
:step="1"
></n-input-number>
<span class="time-separator"></span>
<n-input-number
v-model:value="data.countDownTime.minutes"
:min="0"
:max="59"
:precision="0"
:step="1"
></n-input-number>
<span class="time-separator"></span>
<n-input-number
v-model:value="data.countDownTime.seconds"
:min="0"
:max="59"
:precision="0"
:step="1"
></n-input-number>
<span class="time-separator"></span>
</n-input-group>
</n-form-item>
</n-form>
</n-card>
<!-- 时间记录 -->
<n-card size="small" title="时间记录">
<div class="records-content">
<div
v-if="data.records.length === 0"
class="no-records"
>暂无记录</div>
<div
v-for="(record, index) in data.records"
:key="record.id"
class="record-item"
>
<span class="record-index">{{ index + 1 }}</span>
<span class="record-time">{{ formatTime(record.time) }}</span>
<span class="record-timestamp">{{ formatDateTime(record.timestamp) }}</span>
</div>
</div>
<n-button
type="error"
size="small"
class="clear-records-btn"
@click="clearRecords"
>清空记录</n-button>
</n-card>
</div>
</template>
<script setup>
import {
NButton, NCard, NFlex, NForm, NFormItem, NInputGroup, NInputNumber, NRadioButton, NRadioGroup,
} from 'naive-ui';
import {
reactive, onBeforeUnmount, watch,
} from 'vue';
import {
getCommonDateTime,
} from '@frost-utils/javascript/common/index';
import {
$dialog, $notification,
} from '@/assets/js/naive-ui';
/** 数据 */
const data = reactive({
/** 计时器模式countUp正计时, countDown倒计时 */
timerMode: 'countUp',
/** 是否正在运行 */
isRunning: false,
/** 当前显示时间(毫秒) */
timeDisplay: 0,
/** 倒计时时间设置 */
countDownTime: {
hours: 0,
minutes: 5,
seconds: 0
},
/** 计时器ID */
timerID: null,
/** 开始时间戳 */
startTime: 0,
/** 已运行时间 */
elapsedTime: 0,
/** 时间记录 */
records: [],
/** 记录ID */
recordID: 0,
});
/** 格式化时间显示主时间部分HH:MM:SS */
function formatTimeMain(milliseconds) {
let totalSeconds = Math.floor(milliseconds / 1000);
let hours = Math.floor(totalSeconds / 3600);
let minutes = Math.floor((totalSeconds % 3600) / 60);
let seconds = totalSeconds % 60;
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
/** 格式化时间显示(毫秒部分:.SSS */
function formatTimeMilliseconds(milliseconds) {
let ms = milliseconds % 1000;
return `.${ms.toString().padStart(3, '0')}`;
}
/** 格式化完整时间显示(用于记录和提示) */
function formatTime(milliseconds) {
return `${formatTimeMain(milliseconds)}${formatTimeMilliseconds(milliseconds)}`;
}
/** 格式化日期时间 */
function formatDateTime(timestamp) {
return getCommonDateTime(timestamp);
}
/** 开始计时器 */
function startTimer() {
if (data.timerMode === 'countUp') {
// 正计时模式
data.startTime = Date.now() - data.elapsedTime;
data.timerID = setInterval(() => {
data.elapsedTime = Date.now() - data.startTime;
data.timeDisplay = data.elapsedTime;
}, 10); // 每10毫秒更新一次提高毫秒显示精度
} else {
// 倒计时模式
if (data.timeDisplay === 0) {
// 如果是首次开始或重置后开始,设置倒计时时间
const { hours, minutes, seconds } = data.countDownTime;
data.timeDisplay = (hours * 3600 + minutes * 60 + seconds) * 1000;
}
data.startTime = Date.now() + data.timeDisplay;
data.timerID = setInterval(() => {
data.timeDisplay = Math.max(0, data.startTime - Date.now());
data.elapsedTime = (data.startTime - data.timeDisplay) - data.startTime + data.timeDisplay;
// 倒计时结束
if (data.timeDisplay === 0) {
pauseTimer();
notify({
message: '倒计时结束!',
type: 'success',
title: '提示'
});
}
}, 10); // 每10毫秒更新一次提高毫秒显示精度
}
data.isRunning = true;
}
/** 暂停计时器 */
function pauseTimer() {
if (data.timerID) {
clearInterval(data.timerID);
data.timerID = null;
data.isRunning = false;
}
}
/** 重置计时器 */
function resetTimer() {
pauseTimer();
data.elapsedTime = 0;
data.timeDisplay = 0;
}
/** 记录时间 */
function recordTime() {
data.recordID += 1;
data.records.push({
id: data.recordID,
time: data.timeDisplay,
timestamp: Date.now()
});
notify({
message: `已记录时间: ${formatTime(data.timeDisplay)}`,
type: 'info',
title: '记录成功'
});
}
/** 清空记录 */
function clearRecords() {
$dialog.create({
content: '确定要清空所有时间记录吗?',
negativeText: '取消',
positiveText: '确定',
title: '确认',
type: 'default',
onPositiveClick: () => {
data.records = [];
data.recordID = 0;
},
});
}
/**
* @description 提示信息
* @param {object} options
* @param {number} options.duration
* @param {string} options.message
* @param {string} options.title
* @param {string} options.type
*/
function notify(options) {
let {
duration = 3000,
message = '',
title = '提示',
type = 'info',
} = options;
return $notification.create({
content: message,
duration: duration,
title: title,
type: type,
});
}
/** 监听计时模式切换 */
watch(
() => data.timerMode,
() => {
// 切换模式时重置计时器
resetTimer();
}
);
/** 监听倒计时时间设置变化 */
watch(
() => [data.countDownTime.hours, data.countDownTime.minutes, data.countDownTime.seconds],
() => {
// 只有在倒计时模式且计时器未运行时,才更新显示时间
if (data.timerMode === 'countDown' && !data.isRunning && data.timeDisplay === 0) {
const { hours, minutes, seconds } = data.countDownTime;
data.timeDisplay = (hours * 3600 + minutes * 60 + seconds) * 1000;
}
},
{ deep: true }
);
onBeforeUnmount(() => {
if (data.timerID) {
clearInterval(data.timerID);
}
});
</script>
<style lang="less" scoped>
.timer-display {
font-size: 48px;
font-weight: bold;
text-align: center;
padding: 20px 0;
color: #2196F3;
font-family: 'Courier New', monospace;
display: flex;
align-items: baseline;
justify-content: center;
gap: 4px;
.time-main {
font-size: 48px;
}
.time-milliseconds {
font-size: 32px;
color: #666;
margin-left: 2px;
}
}
.time-separator {
display: flex;
align-items: center;
justify-content: center;
padding: 0 8px;
font-size: 16px;
color: #666;
}
.records-content {
padding: 8px 16px;
border: 1px solid #F0F0F0;
border-radius: 4px;
background-color: #FFF;
line-height: 1.6;
max-height: 200px;
overflow-y: auto;
margin-bottom: 12px;
.record-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
border-bottom: 1px dashed #F0F0F0;
&:last-child {
border-bottom: none;
}
}
.record-index {
width: 30px;
color: #999;
}
.record-time {
flex: 1;
text-align: center;
font-weight: bold;
color: #2196F3;
font-family: 'Courier New', monospace;
}
.record-timestamp {
color: #999;
font-size: 12px;
}
.no-records {
text-align: center;
color: #999;
padding: 20px 0;
}
}
.clear-records-btn {
display: block;
margin-left: auto;
}
</style>