Compare commits

..

No commits in common. "3be055608e1978f1f527c1a09a7cb487c2ab94fe" and "9726cff90c488bfa6028f46f019a6a3d651b027e" have entirely different histories.

8 changed files with 47 additions and 393 deletions

2
.gitignore vendored
View File

@ -22,5 +22,3 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
dist.tar.gz

View File

@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
source ~/.nvm/nvm.sh source ~/.nvm/nvm.sh
nvm use nvm use
npm run build
tar zcvf dist.tar.gz dist

View File

@ -13,7 +13,6 @@
width: 100%; width: 100%;
padding-top: 40px; padding-top: 40px;
box-sizing: border-box; box-sizing: border-box;
position: relative;
} }
/* Style selector */ /* Style selector */
@ -134,23 +133,6 @@
overflow: hidden; overflow: hidden;
} }
.digital-clock-footer {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: flex-end;
margin-top: 4px;
}
.digital-clock-timezone {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
white-space: nowrap;
}
.style-button { .style-button {
padding: 10px 20px; padding: 10px 20px;
font-size: 14px; font-size: 14px;
@ -184,132 +166,3 @@
font-size: 13px; font-size: 13px;
} }
} }
/* Settings button */
.settings-button {
position: absolute;
top: 20px;
left: 20px;
width: 40px;
height: 40px;
padding: 8px;
font-size: 20px;
background-color: rgba(255, 255, 255, 0.05);
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.settings-button:hover {
background-color: rgba(255, 255, 255, 0.15);
}
/* Settings overlay */
.settings-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
/* Settings dialog */
.settings-dialog {
background-color: var(--bg);
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 400px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.settings-header h3 {
margin: 0;
font-size: 20px;
color: var(--text-h);
}
.settings-close {
width: 32px;
height: 32px;
padding: 0;
background: none;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 18px;
color: var(--text);
display: flex;
align-items: center;
justify-content: center;
}
.settings-close:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.settings-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.settings-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.settings-label {
font-size: 16px;
color: var(--text);
}
.settings-toggle {
width: 44px;
height: 24px;
padding: 2px;
background-color: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 12px;
cursor: pointer;
position: relative;
transition: background-color 0.2s ease;
}
.settings-toggle.active {
background-color: var(--accent);
}
.toggle-slider {
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
transition: transform 0.2s ease;
}
.settings-toggle.active .toggle-slider {
transform: translateX(20px);
}

View File

@ -23,19 +23,10 @@ function formatDate(date: Date): DateInfo {
} }
} }
function getTimezoneInfo(): string { function DigitalClock({ time, size, period, is24Hour, onToggleFormat, dateInfo }: { time: string; size: number; period?: string; is24Hour: boolean; onToggleFormat: () => void; dateInfo: DateInfo }) {
// 获取本地时区名称,如 "Asia/Shanghai"
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
// 获取时区偏移,如 "GMT+8"
const offset = new Date().toLocaleTimeString('en-US', { timeZoneName: 'short' }).split(' ').pop() || ''
return `${timeZone} ${offset}`
}
function DigitalClock({ time, size, period, is24Hour, onToggleFormat, dateInfo, timeZone, showDate, showTimezone }: { time: string; size: number; period?: string; is24Hour: boolean; onToggleFormat: () => void; dateInfo: DateInfo; timeZone: string; showDate: boolean; showTimezone: boolean }) {
const fontSize = Math.floor(size * 0.95) const fontSize = Math.floor(size * 0.95)
const periodSize = Math.floor(size * 0.20) const periodSize = Math.floor(size * 0.20)
const dateSize = Math.floor(size * 0.20) const dateSize = Math.floor(size * 0.20)
const timeZoneSize = Math.floor(size * 0.20)
return ( return (
<div className="digital-clock-outer"> <div className="digital-clock-outer">
@ -59,14 +50,12 @@ function DigitalClock({ time, size, period, is24Hour, onToggleFormat, dateInfo,
</span> </span>
)} )}
</div> </div>
{showDate && (
<span <span
className="digital-clock-date" className="digital-clock-date"
style={{ fontSize: `${dateSize}px` }} style={{ fontSize: `${dateSize}px` }}
> >
{dateInfo.dateString} {dateInfo.weekday} {dateInfo.dateString} {dateInfo.weekday}
</span> </span>
)}
</div> </div>
<div <div
className="digital-clock" className="digital-clock"
@ -74,16 +63,6 @@ function DigitalClock({ time, size, period, is24Hour, onToggleFormat, dateInfo,
> >
{time} {time}
</div> </div>
{showTimezone && (
<div className="digital-clock-footer">
<span
className="digital-clock-timezone"
style={{ fontSize: `${timeZoneSize}px` }}
>
{timeZone}
</span>
</div>
)}
</div> </div>
</div> </div>
) )
@ -91,68 +70,9 @@ function DigitalClock({ time, size, period, is24Hour, onToggleFormat, dateInfo,
function App() { function App() {
const [time, setTime] = useState(new Date()) const [time, setTime] = useState(new Date())
const [clockStyle, setClockStyle] = useState<ClockStyle>(() => { const [clockStyle, setClockStyle] = useState<ClockStyle>('digital')
// 从 localStorage 读取,默认数码管 ('digital')
const saved = localStorage.getItem('clock-style')
const validStyles: ClockStyle[] = ['digital', 'flip', 'roll']
return saved && validStyles.includes(saved as ClockStyle) ? (saved as ClockStyle) : 'digital'
})
const [clockSize, setClockSize] = useState(120) const [clockSize, setClockSize] = useState(120)
const [is24Hour, setIs24Hour] = useState(() => { const [is24Hour, setIs24Hour] = useState(true)
// 从 localStorage 读取,默认 24 小时制 (true)
const saved = localStorage.getItem('clock-is24Hour')
return saved === null ? true : saved === 'true'
})
const [rollDirection, setRollDirection] = useState<'down' | 'up'>(() => {
// 从 localStorage 读取,默认向下滚动 ('down')
const saved = localStorage.getItem('clock-rollDirection')
return saved === 'up' ? 'up' : 'down'
})
const [showSettings, setShowSettings] = useState(false)
const [showDate, setShowDate] = useState(() => {
// 从 localStorage 读取,默认显示日期 (true)
const saved = localStorage.getItem('clock-showDate')
return saved === null ? true : saved === 'true'
})
const [showTimezone, setShowTimezone] = useState(() => {
// 从 localStorage 读取,默认显示时区 (true)
const saved = localStorage.getItem('clock-showTimezone')
return saved === null ? true : saved === 'true'
})
// 切换样式时保存到 localStorage
const handleSetClockStyle = (style: ClockStyle) => {
setClockStyle(style)
localStorage.setItem('clock-style', style)
}
// 切换格式时保存到 localStorage
const toggleFormat = () => {
const newValue = !is24Hour
setIs24Hour(newValue)
localStorage.setItem('clock-is24Hour', String(newValue))
}
// 切换滚动方向时保存到 localStorage
const toggleRollDirection = () => {
const newValue = rollDirection === 'down' ? 'up' : 'down'
setRollDirection(newValue)
localStorage.setItem('clock-rollDirection', newValue)
}
// 切换显示日期时保存到 localStorage
const toggleShowDate = () => {
const newValue = !showDate
setShowDate(newValue)
localStorage.setItem('clock-showDate', String(newValue))
}
// 切换显示时区时保存到 localStorage
const toggleShowTimezone = () => {
const newValue = !showTimezone
setShowTimezone(newValue)
localStorage.setItem('clock-showTimezone', String(newValue))
}
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
@ -198,14 +118,13 @@ function App() {
const { timeString, period } = formatTime() const { timeString, period } = formatTime()
const dateInfo = formatDate(time) const dateInfo = formatDate(time)
const timeZone = getTimezoneInfo()
const renderClock = () => { const renderClock = () => {
switch (clockStyle) { switch (clockStyle) {
case 'flip': case 'flip':
return <FlipClock time={timeString} size={clockSize} dateInfo={dateInfo} period={period} is24Hour={is24Hour} onToggleFormat={toggleFormat} timeZone={timeZone} showDate={showDate} showTimezone={showTimezone} /> return <FlipClock time={timeString} size={clockSize} dateInfo={dateInfo} period={period} />
case 'roll': case 'roll':
return <RollClock time={timeString} size={clockSize} dateInfo={dateInfo} period={period} is24Hour={is24Hour} onToggleFormat={toggleFormat} direction={rollDirection} onToggleDirection={toggleRollDirection} timeZone={timeZone} showDate={showDate} showTimezone={showTimezone} /> return <RollClock time={timeString} size={clockSize} dateInfo={dateInfo} period={period} />
case 'digital': case 'digital':
default: default:
return ( return (
@ -214,11 +133,8 @@ function App() {
size={clockSize} size={clockSize}
period={period} period={period}
is24Hour={is24Hour} is24Hour={is24Hour}
onToggleFormat={toggleFormat} onToggleFormat={() => setIs24Hour(!is24Hour)}
dateInfo={dateInfo} dateInfo={dateInfo}
timeZone={timeZone}
showDate={showDate}
showTimezone={showTimezone}
/> />
) )
} }
@ -226,75 +142,22 @@ function App() {
return ( return (
<div className="app"> <div className="app">
<button
className="settings-button"
onClick={() => setShowSettings(true)}
aria-label="设置"
>
</button>
{showSettings && (
<div className="settings-overlay" onClick={() => setShowSettings(false)}>
<div className="settings-dialog" onClick={(e) => e.stopPropagation()}>
<div className="settings-header">
<h3></h3>
<button
className="settings-close"
onClick={() => setShowSettings(false)}
>
</button>
</div>
<div className="settings-content">
<div className="settings-row">
<span className="settings-label">12</span>
<button
className={`settings-toggle ${!is24Hour ? 'active' : ''}`}
onClick={toggleFormat}
>
<span className="toggle-slider" />
</button>
</div>
<div className="settings-row">
<span className="settings-label"></span>
<button
className={`settings-toggle ${showDate ? 'active' : ''}`}
onClick={toggleShowDate}
>
<span className="toggle-slider" />
</button>
</div>
<div className="settings-row">
<span className="settings-label"></span>
<button
className={`settings-toggle ${showTimezone ? 'active' : ''}`}
onClick={toggleShowTimezone}
>
<span className="toggle-slider" />
</button>
</div>
</div>
</div>
</div>
)}
<div className="style-selector"> <div className="style-selector">
<button <button
className={`style-button ${clockStyle === 'digital' ? 'active' : ''}`} className={`style-button ${clockStyle === 'digital' ? 'active' : ''}`}
onClick={() => handleSetClockStyle('digital')} onClick={() => setClockStyle('digital')}
> >
</button> </button>
<button <button
className={`style-button ${clockStyle === 'flip' ? 'active' : ''}`} className={`style-button ${clockStyle === 'flip' ? 'active' : ''}`}
onClick={() => handleSetClockStyle('flip')} onClick={() => setClockStyle('flip')}
> >
</button> </button>
<button <button
className={`style-button ${clockStyle === 'roll' ? 'active' : ''}`} className={`style-button ${clockStyle === 'roll' ? 'active' : ''}`}
onClick={() => handleSetClockStyle('roll')} onClick={() => setClockStyle('roll')}
> >
</button> </button>

View File

@ -83,23 +83,6 @@
white-space: nowrap; white-space: nowrap;
} }
.flip-clock-footer {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: flex-end;
margin-top: 4px;
}
.flip-clock-timezone {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
white-space: nowrap;
}
.flip-clock-digit-group { .flip-clock-digit-group {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -1,3 +1,4 @@
import { useState } from 'react';
import { FlipDigit } from './FlipDigit'; import { FlipDigit } from './FlipDigit';
import './FlipClock.css'; import './FlipClock.css';
@ -11,28 +12,24 @@ interface FlipClockProps {
size: number; size: number;
dateInfo: DateInfo; dateInfo: DateInfo;
period?: string; period?: string;
is24Hour: boolean;
onToggleFormat: () => void;
timeZone: string;
showDate: boolean;
showTimezone: boolean;
} }
export function FlipClock({ time, size, dateInfo, period, is24Hour, onToggleFormat, timeZone, showDate, showTimezone }: FlipClockProps) { export function FlipClock({ time, size, dateInfo, period }: FlipClockProps) {
const [is24Hour, setIs24Hour] = useState(true);
// Parse time - it's already in 12h format when period is provided // Parse time - it's already in 12h format when period is provided
const [hoursStr, minutes, seconds] = time.split(':'); const [hoursStr, minutes, seconds] = time.split(':');
const displayHours = hoursStr; const displayHours = hoursStr;
const periodSize = size * 0.20; const periodSize = size * 0.20;
const dateSize = size * 0.20; const dateSize = size * 0.20;
const timeZoneSize = size * 0.20;
return ( return (
<div className="flip-clock-outer"> <div className="flip-clock-outer">
<div className="flip-clock-side-panel"> <div className="flip-clock-side-panel">
<button <button
className="flip-format-toggle" className="flip-format-toggle"
onClick={onToggleFormat} onClick={() => setIs24Hour(!is24Hour)}
> >
{is24Hour ? '24H' : '12H'} {is24Hour ? '24H' : '12H'}
</button> </button>
@ -49,14 +46,12 @@ export function FlipClock({ time, size, dateInfo, period, is24Hour, onToggleForm
</span> </span>
)} )}
</div> </div>
{showDate && (
<span <span
className="flip-clock-date" className="flip-clock-date"
style={{ fontSize: `${dateSize}px` }} style={{ fontSize: `${dateSize}px` }}
> >
{dateInfo.dateString} {dateInfo.weekday} {dateInfo.dateString} {dateInfo.weekday}
</span> </span>
)}
</div> </div>
<div className="flip-clock-container"> <div className="flip-clock-container">
{/* Hours */} {/* Hours */}
@ -97,16 +92,6 @@ export function FlipClock({ time, size, dateInfo, period, is24Hour, onToggleForm
<FlipDigit value={parseInt(seconds[1], 10)} size={size} /> <FlipDigit value={parseInt(seconds[1], 10)} size={size} />
</div> </div>
</div> </div>
{showTimezone && (
<div className="flip-clock-footer">
<span
className="flip-clock-timezone"
style={{ fontSize: `${timeZoneSize}px` }}
>
{timeZone}
</span>
</div>
)}
</div> </div>
</div> </div>
); );

View File

@ -89,23 +89,6 @@
white-space: nowrap; white-space: nowrap;
} }
.roll-clock-footer {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: flex-end;
margin-top: 4px;
}
.roll-clock-timezone {
font-family: Arial, sans-serif;
font-weight: 600;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
line-height: 1;
white-space: nowrap;
}
.roll-clock-digit-group { .roll-clock-digit-group {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -1,3 +1,4 @@
import { useState } from 'react';
import { RollDigit } from './RollDigit'; import { RollDigit } from './RollDigit';
import './RollClock.css'; import './RollClock.css';
@ -11,16 +12,13 @@ interface RollClockProps {
size: number; size: number;
dateInfo: DateInfo; dateInfo: DateInfo;
period?: string; period?: string;
is24Hour: boolean;
onToggleFormat: () => void;
direction: 'down' | 'up';
onToggleDirection: () => void;
timeZone: string;
showDate: boolean;
showTimezone: boolean;
} }
export function RollClock({ time, size, dateInfo, period, is24Hour, onToggleFormat, direction, onToggleDirection, timeZone, showDate, showTimezone }: RollClockProps) { export function RollClock({ time, size, dateInfo, period }: RollClockProps) {
// Roll direction: 'down' = roll down, 'up' = roll up
const [direction, setDirection] = useState<'down' | 'up'>('down');
const [is24Hour, setIs24Hour] = useState(true);
// Parse time - it's already in 12h format when period is provided // Parse time - it's already in 12h format when period is provided
const [hoursStr, minutes, seconds] = time.split(':'); const [hoursStr, minutes, seconds] = time.split(':');
const displayHours = hoursStr; const displayHours = hoursStr;
@ -28,14 +26,18 @@ export function RollClock({ time, size, dateInfo, period, is24Hour, onToggleForm
const buttonSize = size * 0.25; const buttonSize = size * 0.25;
const periodSize = size * 0.20; const periodSize = size * 0.20;
const dateSize = size * 0.20; const dateSize = size * 0.20;
const timeZoneSize = size * 0.20;
// Toggle direction
const toggleDirection = () => {
setDirection((prev) => (prev === 'down' ? 'up' : 'down'));
};
return ( return (
<div className="roll-clock-outer"> <div className="roll-clock-outer">
<div className="roll-clock-side-panel"> <div className="roll-clock-side-panel">
<button <button
className="roll-format-toggle" className="roll-format-toggle"
onClick={onToggleFormat} onClick={() => setIs24Hour(!is24Hour)}
> >
{is24Hour ? '24H' : '12H'} {is24Hour ? '24H' : '12H'}
</button> </button>
@ -44,7 +46,7 @@ export function RollClock({ time, size, dateInfo, period, is24Hour, onToggleForm
<div className="roll-clock-main"> <div className="roll-clock-main">
<div className="roll-clock-header"> <div className="roll-clock-header">
<div className="roll-clock-header-left"> <div className="roll-clock-header-left">
{period && ( {!is24Hour && period && (
<span <span
className="roll-clock-period" className="roll-clock-period"
style={{ fontSize: `${periodSize}px` }} style={{ fontSize: `${periodSize}px` }}
@ -53,14 +55,12 @@ export function RollClock({ time, size, dateInfo, period, is24Hour, onToggleForm
</span> </span>
)} )}
</div> </div>
{showDate && (
<span <span
className="roll-clock-date" className="roll-clock-date"
style={{ fontSize: `${dateSize}px` }} style={{ fontSize: `${dateSize}px` }}
> >
{dateInfo.dateString} {dateInfo.weekday} {dateInfo.dateString} {dateInfo.weekday}
</span> </span>
)}
</div> </div>
<div className="roll-clock-container"> <div className="roll-clock-container">
{/* Hours */} {/* Hours */}
@ -101,16 +101,6 @@ export function RollClock({ time, size, dateInfo, period, is24Hour, onToggleForm
<RollDigit value={parseInt(seconds[1], 10)} size={size} direction={direction} /> <RollDigit value={parseInt(seconds[1], 10)} size={size} direction={direction} />
</div> </div>
</div> </div>
{showTimezone && (
<div className="roll-clock-footer">
<span
className="roll-clock-timezone"
style={{ fontSize: `${timeZoneSize}px` }}
>
{timeZone}
</span>
</div>
)}
</div> </div>
{/* Direction toggle button */} {/* Direction toggle button */}
@ -122,7 +112,7 @@ export function RollClock({ time, size, dateInfo, period, is24Hour, onToggleForm
borderRadius: `${buttonSize / 2}px`, borderRadius: `${buttonSize / 2}px`,
marginLeft: '8px', marginLeft: '8px',
}} }}
onClick={onToggleDirection} onClick={toggleDirection}
> >
<span <span
className="roll-clock-button-text" className="roll-clock-button-text"