complete frontend - refact from vue

This commit is contained in:
2025-06-28 16:57:26 +08:00
parent 1e65c2d872
commit 1d41ad6ef1
9 changed files with 1772 additions and 96 deletions
+158
View File
@@ -0,0 +1,158 @@
'use client'
import { useState, useRef, useEffect } from 'react'
interface DecodedLine {
content: string
isError: boolean
}
export default function DecodePage() {
const [decodedLines, setDecodedLines] = useState<DecodedLine[]>([])
const [errorMessage, setErrorMessage] = useState('')
const resultRef = useRef<HTMLDivElement>(null)
// 转换 Unix 时间戳为本地时间字符串
const formatTimestamp = (line: string): string => {
const match = line.match(/^\[(\d+)\](.*)/)
if (match) {
const timestamp = parseInt(match[1])
const date = new Date(timestamp * 1000)
const formattedTime = date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
return `[${formattedTime}]${match[2]}`
}
return line
}
const decodeBase64 = (base64String: string): string => {
try {
const binaryString = atob(base64String.trim())
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
const decoder = new TextDecoder('utf-8')
return decoder.decode(bytes)
} catch (e) {
throw new Error(`解码失败: ${(e as Error).message}`)
}
}
const scrollToBottom = () => {
if (resultRef.current) {
resultRef.current.scrollTop = resultRef.current.scrollHeight
}
}
// 当解码行数更新时滚动到底部
useEffect(() => {
if (decodedLines.length > 0) {
setTimeout(scrollToBottom, 100)
}
}, [decodedLines])
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) {
setErrorMessage('请选择文件')
return
}
try {
const text = await file.text()
const lines = text.split('\n')
const processedLines: DecodedLine[] = lines
.filter(line => line.trim())
.map((line, index) => {
try {
const decodedLine = decodeBase64(line)
const formattedLine = formatTimestamp(decodedLine)
return {
content: formattedLine,
isError: false
}
} catch (e) {
console.error(`第 ${index + 1} 行解码失败:`, e)
return {
content: `第 ${index + 1} 行解码失败: ${line}`,
isError: true
}
}
})
setDecodedLines(processedLines)
setErrorMessage('')
} catch (e) {
setErrorMessage('文件读取失败,请重试')
console.error('文件读取错误:', e)
}
}
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold text-gray-800 mb-6">Base64 日志解码器</h1>
{/* 文件上传区域 */}
<div className="mb-8">
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center">
<label className="block mb-4 text-sm font-medium text-gray-700">
选择日志文件:
</label>
<input
type="file"
accept=".txt,.log"
onChange={handleFileChange}
className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0 file:text-sm file:font-semibold
file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100
cursor-pointer"
/>
</div>
</div>
{/* 错误提示 */}
{errorMessage && (
<div className="mb-6 p-4 bg-red-50 text-red-600 rounded-md">
{errorMessage}
</div>
)}
{/* 解码结果 */}
{decodedLines.length > 0 && (
<div className="space-y-2">
<h2 className="text-lg font-semibold text-gray-700 mb-4">解码结果:</h2>
<div
ref={resultRef}
className="bg-white shadow rounded-lg overflow-auto max-h-[600px]"
>
<div className="divide-y divide-gray-100">
{decodedLines.map((line, index) => (
<div
key={index}
className={`py-1.5 px-4 hover:bg-gray-50 leading-tight ${
line.isError ? 'bg-red-50' : ''
}`}
>
<pre className="whitespace-pre-wrap font-mono text-sm text-gray-800">
{line.content}
</pre>
</div>
))}
</div>
</div>
</div>
)}
</div>
)
}