调整,音乐模块

This commit is contained in:
zhongluofeng
2026-09-12 11:05:26 +08:00
parent 27ad5d89a5
commit d702ed0d31
71 changed files with 13647 additions and 387 deletions
+131
View File
@@ -0,0 +1,131 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { Textarea } from '@/components/ui/textarea'
import { Label } from '@/components/ui/label'
import { Badge } from '@/components/ui/badge'
import ResultArea from '../components/ResultArea.vue'
const token = ref('')
const error = ref('')
function base64UrlDecode(s: string): string {
const cleaned = s.replace(/-/g, '+').replace(/_/g, '/')
const pad = cleaned.length % 4
const normalized = pad ? cleaned + '='.repeat(4 - pad) : cleaned
const bin = atob(normalized)
const bytes = Uint8Array.from(bin, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
interface TimeClaim {
name: string
value: string
local: string
status: 'ok' | 'expired' | 'not-yet' | 'unknown'
}
const parsed = computed<{
header: string
payload: string
signature: string
timeClaims: TimeClaim[]
} | null>(() => {
error.value = ''
const t = token.value.trim()
if (!t) return null
const parts = t.split('.')
if (parts.length < 2) {
error.value = 'JWT 格式不正确(应为 header.payload.signature'
return null
}
try {
const header = formatJson(base64UrlDecode(parts[0]))
const payload = formatJson(base64UrlDecode(parts[1]))
const signature = parts[2] ?? ''
return { header, payload, signature, timeClaims: parseTimeClaims(payload) }
} catch (e) {
error.value = '解码失败:' + String(e)
return null
}
})
function formatJson(s: string): string {
let pretty = s
try {
pretty = JSON.stringify(JSON.parse(s), null, 2)
} catch {
/* 非 JSON(如已损毁),原样展示 */
}
return pretty
}
/** 解读 iat / nbf / exp 等时间声明(秒级时间戳) */
function parseTimeClaims(payload: string): TimeClaim[] {
let obj: unknown
try {
obj = JSON.parse(payload)
} catch {
return []
}
if (!obj || typeof obj !== 'object') return []
const p = obj as Record<string, unknown>
const claims: TimeClaim[] = []
const now = Math.floor(Date.now() / 1000)
const NAMES: Record<string, string> = { iat: 'iat(签发时间)', nbf: 'nbf(生效时间)', exp: 'exp(过期时间)' }
const pad = (x: number) => String(x).padStart(2, '0')
for (const key of ['iat', 'nbf', 'exp']) {
const v = p[key]
if (typeof v !== 'number' || !Number.isFinite(v)) continue
const d = new Date(v * 1000)
if (Number.isNaN(d.getTime())) continue
const local = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
let status: TimeClaim['status'] = 'ok'
if (key === 'exp' && v < now) status = 'expired'
else if (key === 'nbf' && v > now) status = 'not-yet'
else if (key === 'iat' && v > now + 60) status = 'unknown'
claims.push({ name: NAMES[key], value: String(v), local, status })
}
return claims
}
const STATUS_BADGE: Record<TimeClaim['status'], { label: string; class: string } | null> = {
ok: null,
expired: { label: '已过期', class: 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/30' },
'not-yet': { label: '尚未生效', class: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/30' },
unknown: { label: '签发时间在未来(时钟偏差?)', class: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/30' }
}
</script>
<template>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-1.5">
<Label class="text-xs">JWT Token</Label>
<Textarea
v-model="token"
placeholder="eyJhbGciOi...header.payload.signature"
class="min-h-[70px] font-mono text-xs resize-y break-all"
/>
</div>
<template v-if="parsed">
<ResultArea :text="parsed.header" label="Header" placeholder="Header" />
<ResultArea :text="parsed.payload" label="Payload(载荷)" placeholder="Payload" />
<ResultArea :text="parsed.signature" label="Signature(签名)" placeholder="Signature" />
<div v-if="parsed.timeClaims.length > 0" class="flex flex-col gap-1.5">
<Label class="text-xs">时间声明解读</Label>
<div class="rounded-md border border-border divide-y divide-border text-xs">
<div v-for="c in parsed.timeClaims" :key="c.name" class="flex items-center gap-3 px-3 py-2 flex-wrap">
<span class="font-mono text-muted-foreground">{{ c.name }}</span>
<span class="font-mono">{{ c.local }}</span>
<Badge v-if="STATUS_BADGE[c.status]" variant="outline" :class="['py-0 text-[10px] ml-auto', STATUS_BADGE[c.status]!.class]">
{{ STATUS_BADGE[c.status]!.label }}
</Badge>
</div>
</div>
</div>
</template>
<p v-else-if="!token" class="text-xs text-muted-foreground">在左侧粘贴 JWT下方将自动解析 Header Payload</p>
<p v-else class="text-xs text-destructive">{{ error }}</p>
</div>
</template>