Refactor time utility functions for improved type safety and error handling

- Removed deprecated formatDate function.
- Enhanced formatWithZone to support TypeScript types for parameters.
- Updated getTimeZoneOffset to use TypeScript types.
- Improved formatWithDate for better error messages and type safety.
This commit is contained in:
雨霖铃
2026-01-28 22:39:44 +08:00
parent 710bffd2f8
commit 92026ee7fa
6 changed files with 813 additions and 843 deletions
+12 -12
View File
@@ -6,6 +6,7 @@ const CopyButton = ({
className = 'action-btn',
successMessage = '复制成功!',
errorMessage = '复制失败,请手动复制。',
copyingMessage = '复制中...',
}) => {
const [status, setStatus] = useState('idle'); // 'idle' | 'copying' | 'success' | 'error'
@@ -53,25 +54,24 @@ const CopyButton = ({
// 根据状态计算当前显示的文本
const currentText =
status === 'success' ? successMessage : status === 'error' ? errorMessage : buttonText;
status === 'success'
? successMessage
: status === 'error'
? errorMessage
: status === 'copying'
? copyingMessage
: buttonText;
// 动态样式:只在非默认状态下覆盖颜色,平时让 className 控制
const getStyle = () => {
const baseStyle = {
transition: 'all 0.3s ease',
cursor: 'pointer', // 确保有手型光标
// 这里去掉了 padding/border/radius 的硬编码,建议在 CSS 类中定义
// 除非你想强制覆盖
};
if (status === 'success') {
return { ...baseStyle, backgroundColor: '#4CAF50', color: 'white' };
return { backgroundColor: '#4CAF50', color: 'white' };
}
if (status === 'error') {
return { ...baseStyle, backgroundColor: '#f44336', color: 'white' };
return { backgroundColor: '#f44336', color: 'white' };
}
return baseStyle;
return {};
};
return (
@@ -79,7 +79,7 @@ const CopyButton = ({
onClick={handleClick}
className={`${className} ${status} copy-button`}
style={getStyle()}
disabled={status === 'copying'}
disabled={status === 'success' || status === 'copying'}
>
{currentText}
</button>
+19 -17
View File
@@ -1,5 +1,5 @@
import {formatWithDate, formatWithZone, TimezoneOptions} from "../utils/timeUtils";
import {useState, useCallback} from "react";
import { formatWithDate, formatWithZone } from '../utils/timeUtils';
import { useState, useCallback } from 'react';
/**
* 日期时间转时间戳组件
@@ -44,13 +44,13 @@ const TIME_ZONE_LIST = [
// 时间戳单位选项
const TIMESTAMP_UNITS = [
{value: 'milliseconds', label: '毫秒(ms)'},
{value: 'seconds', label: '秒(s)'},
{ value: 'milliseconds', label: '毫秒(ms)' },
{ value: 'seconds', label: '秒(s)' },
];
export function DatetimeToTimestamp() {
/** @type {[string, function]} 输入的日期时间字符串 */
const [dateValue, setDateValue] = useState(() => formatWithZone(Date.now()));
const [dateValue, setDateValue] = useState(() => formatWithZone(Date.now(), 'Asia/Shanghai'));
/** @type {[string, function]} 选择的时区 */
const [selectedZone, setSelectedZone] = useState('Asia/Shanghai');
@@ -79,9 +79,7 @@ export function DatetimeToTimestamp() {
return;
}
const finalResult = unit === 'milliseconds'
? timestamp
: Math.floor(timestamp / 1000);
const finalResult = unit === 'milliseconds' ? timestamp : Math.floor(timestamp / 1000);
setResult(finalResult.toString());
} catch (err) {
@@ -111,7 +109,8 @@ export function DatetimeToTimestamp() {
* 处理时间戳单位变化
* @type {function(React.ChangeEvent<HTMLSelectElement>): void}
*/
const handleUnitChange = useCallback((e) => {
const handleUnitChange = useCallback(
(e) => {
const newUnit = e.target.value;
setUnit(newUnit);
@@ -119,13 +118,14 @@ export function DatetimeToTimestamp() {
if (result) {
const currentResult = parseInt(result, 10);
if (!isNaN(currentResult)) {
const newResult = newUnit === 'milliseconds'
? currentResult * 1000
: Math.floor(currentResult / 1000);
const newResult =
newUnit === 'milliseconds' ? currentResult * 1000 : Math.floor(currentResult / 1000);
setResult(newResult.toString());
}
}
}, [result]);
},
[result],
);
return (
<div className="datetime-converter">
@@ -148,7 +148,11 @@ export function DatetimeToTimestamp() {
onChange={handleZoneChange}
aria-label="选择时区"
>
<TimezoneOptions zones={TIME_ZONE_LIST}/>
{TIME_ZONE_LIST.map((zone) => (
<option key={zone} value={zone}>
{zone}
</option>
))}
</select>
</div>
@@ -183,16 +187,14 @@ export function DatetimeToTimestamp() {
onChange={handleUnitChange}
aria-label="选择时间戳单位"
>
{TIMESTAMP_UNITS.map(({value, label}) => (
{TIMESTAMP_UNITS.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</div>
</div>
</div>
);
}
+6 -6
View File
@@ -1,5 +1,5 @@
import {useEffect, useState, useRef, useCallback} from "react";
import CopyButton from "./CopyButton";
import { useEffect, useState, useRef, useCallback } from 'react';
import CopyButton from './CopyButton';
/**
* 时间戳显示和执行组件
@@ -79,7 +79,7 @@ export function TimestampExecution() {
* @type {function(): void}
*/
const toggleUnit = useCallback(() => {
setShowMilliseconds(prev => !prev);
setShowMilliseconds((prev) => !prev);
}, []);
/**
@@ -87,7 +87,7 @@ export function TimestampExecution() {
* @type {function(): void}
*/
const toggleTimestamp = useCallback(() => {
setIsRunningTimestamp(prev => !prev);
setIsRunningTimestamp((prev) => !prev);
}, []);
/**
@@ -118,7 +118,7 @@ export function TimestampExecution() {
<div className="timestamp-controls">
<button
type="button"
className="timestamp-btn action-btn"
className="action-btn"
onClick={toggleUnit}
aria-label={unitButtonLabel}
title={unitButtonLabel}
@@ -134,7 +134,7 @@ export function TimestampExecution() {
<button
type="button"
className={`timestamp-btn ${isRunningTimestamp ? 'stop-btn' : 'action-btn'}`}
className={`${isRunningTimestamp ? 'stop-btn' : 'action-btn'}`}
onClick={toggleTimestamp}
aria-label={toggleButtonLabel}
title={toggleButtonLabel}
+11 -7
View File
@@ -1,5 +1,5 @@
import {useState, useCallback} from "react";
import {formatWithZone, TimezoneOptions} from "../utils/timeUtils";
import { useState, useCallback } from 'react';
import { formatWithZone } from '../utils/timeUtils';
/**
* 时间戳转日期时间组件
@@ -43,8 +43,8 @@ const TIME_ZONE_LIST = [
// 时间戳单位选项
const TIMESTAMP_UNITS = [
{value: 'milliseconds', label: '毫秒(ms)'},
{value: 'seconds', label: '秒(s)'},
{ value: 'milliseconds', label: '毫秒(ms)' },
{ value: 'seconds', label: '秒(s)' },
];
export function TimestampToDatetime() {
@@ -138,7 +138,7 @@ export function TimestampToDatetime() {
onChange={handleUnitChange}
aria-label="选择时间戳单位"
>
{TIMESTAMP_UNITS.map(({value, label}) => (
{TIMESTAMP_UNITS.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
@@ -177,10 +177,14 @@ export function TimestampToDatetime() {
onChange={handleZoneChange}
aria-label="选择时区"
>
<TimezoneOptions zones={TIME_ZONE_LIST}/>
{TIME_ZONE_LIST.map((zone) => (
<option key={zone} value={zone}>
{zone}
</option>
))}
</select>
</div>
</div>
</div>
)
);
}
+26 -50
View File
@@ -1,5 +1,6 @@
/* 隐藏页面滚动条 */
html, body {
html,
body {
margin: 0;
padding: 0;
width: 100%;
@@ -437,6 +438,30 @@ body,
-ms-overflow-style: none;
}
button {
background: #4caf50;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
transition:
transform 0.08s ease,
box-shadow 0.2s ease,
background-color 0.25s ease;
}
button:hover {
background: #3d8b40;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:active {
background: #4caf50;
transform: scale(0.96) translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* 超小屏幕适配 - 修复边框遮挡问题 */
@media (max-width: 399px) {
.nav {
@@ -578,23 +603,6 @@ body,
cursor: pointer;
border: 1px solid #d9d9d9;
box-shadow: 0 2px #00000004;
transition: .8s;
opacity: 4;
}
.action-btn:hover {
background: #3d8b40;
}
.action-btn:active {
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.2);
opacity: 0;
}
.action-btn:active::after {
box-shadow: none;
opacity: 0.4;
transition: 0s;
}
.stop-btn {
@@ -680,22 +688,6 @@ select {
flex-wrap: wrap;
}
.timestamp-btn {
padding: 10px 20px;
border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
border: none;
cursor: pointer;
font-size: 0.95rem;
min-width: 120px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
/* 确保 CopyButton 也使用相同的样式 */
.timestamp-controls .action-btn,
.timestamp-controls .stop-btn {
@@ -705,19 +697,6 @@ select {
border-radius: 8px;
font-weight: 500;
font-size: 0.95rem;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.timestamp-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.timestamp-btn:active {
transform: translateY(0);
}
.timestamp-status {
@@ -780,7 +759,6 @@ select {
gap: 8px;
}
.timestamp-btn,
.timestamp-controls .action-btn,
.timestamp-controls .stop-btn {
width: auto;
@@ -805,7 +783,6 @@ select {
gap: 4px;
}
.timestamp-btn,
.timestamp-controls .action-btn,
.timestamp-controls .stop-btn {
padding: 6px 4px;
@@ -834,7 +811,6 @@ select {
gap: 3px;
}
.timestamp-btn,
.timestamp-controls .action-btn,
.timestamp-controls .stop-btn {
padding: 5px 3px;
+14 -26
View File
@@ -1,20 +1,8 @@
export function formatDate(value, timezone = 'Asia/Shanghai') {
return (new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZone: timezone,
})).format(value);
}
export const TimezoneOptions = ({zones}) => {
return (zones.map((zone) => (<option key={zone} value={zone}>{zone}</option>)));
}
export const formatWithZone = (timestamp, zone = 'Asia/Shanghai', unit = 'milliseconds') => {
export const formatWithZone = (
timestamp: number | string,
zone = 'Asia/Shanghai',
unit = 'milliseconds',
) => {
try {
const ms = unit === 'milliseconds' ? Number(timestamp) : Number(timestamp) * 1000;
return new Intl.DateTimeFormat('zh-CN', {
@@ -29,18 +17,18 @@ export const formatWithZone = (timestamp, zone = 'Asia/Shanghai', unit = 'millis
} catch (e) {
return '格式错误';
}
}
};
export const getTimeZoneOffset = (timeZone) => {
export const getTimeZoneOffset = (timeZone: string) => {
const now = new Date();
const utc = new Date(now.toLocaleString('en-US', {timeZone: 'UTC'}));
const target = new Date(now.toLocaleString('en-US', {timeZone: timeZone}));
const utc = new Date(now.toLocaleString('en-US', { timeZone: 'UTC' }));
const target = new Date(now.toLocaleString('en-US', { timeZone: timeZone }));
return target.getTime() - utc.getTime();
}
};
export const formatWithDate = (
date,
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
date: string,
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone,
) => {
try {
// 创建日期对象
@@ -64,6 +52,6 @@ export const formatWithDate = (
return dateObj.getTime();
}
} catch (error) {
return '日期转换错误: ' + error.message;
return '日期转换错误: ' + error;
}
}
};