77 lines
2.8 KiB
Vue
77 lines
2.8 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Label } from '@/components/ui/label'
|
||
import Segmented from '../../components/Segmented.vue'
|
||
import ResultArea from '../../components/ResultArea.vue'
|
||
|
||
type Radix = '2' | '8' | '10' | '16'
|
||
|
||
const bases: { value: Radix; label: string }[] = [
|
||
{ value: '2', label: '二进制(2)' },
|
||
{ value: '8', label: '八进制(8)' },
|
||
{ value: '10', label: '十进制(10)' },
|
||
{ value: '16', label: '十六进制(16)' }
|
||
]
|
||
|
||
const from = ref<Radix>('10')
|
||
const to = ref<Radix>('16')
|
||
const input = ref('')
|
||
const error = ref('')
|
||
|
||
const RADIX_MAP: Record<Radix, number> = { '2': 2, '8': 8, '10': 10, '16': 16 }
|
||
|
||
/** 按进制精确解析为 BigInt(大数字不丢精度),非法输入抛错 */
|
||
function parseBigInt(s: string, radix: number): bigint {
|
||
const digits = '0123456789abcdef'
|
||
const trimmed = s.trim().toLowerCase()
|
||
const negative = trimmed.startsWith('-')
|
||
const body = trimmed.replace(/^[+-]/, '')
|
||
if (!body) throw new Error('empty')
|
||
let n = 0n
|
||
for (const ch of body) {
|
||
const d = digits.indexOf(ch)
|
||
if (d === -1 || d >= radix) throw new Error('invalid digit')
|
||
n = n * BigInt(radix) + BigInt(d)
|
||
}
|
||
return negative ? -n : n
|
||
}
|
||
|
||
const result = computed(() => {
|
||
error.value = ''
|
||
const v = input.value.trim()
|
||
if (!v) return { dec: null, output: '', outputUppercase: '' }
|
||
try {
|
||
const parsed = parseBigInt(v, RADIX_MAP[from.value])
|
||
const out = parsed.toString(RADIX_MAP[to.value])
|
||
return {
|
||
dec: parsed,
|
||
output: out,
|
||
outputUppercase: to.value === '16' ? out.toUpperCase() : out
|
||
}
|
||
} catch {
|
||
error.value = '输入不合法,请确认数字与当前进制匹配(如十六进制仅含 0-9a-f)'
|
||
return { dec: null, output: '', outputUppercase: '' }
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex flex-col gap-4">
|
||
<div class="flex items-center gap-3 flex-wrap">
|
||
<Segmented v-model="from" label="从" :options="bases" />
|
||
<span class="text-xs text-muted-foreground">→</span>
|
||
<Segmented v-model="to" label="到" :options="bases" />
|
||
</div>
|
||
|
||
<div class="flex flex-col gap-1.5">
|
||
<Label class="text-xs">输入({{ from === '2' ? '二进制' : from === '8' ? '八进制' : from === '10' ? '十进制' : '十六进制' }})</Label>
|
||
<Input v-model="input" placeholder="输入数字" class="font-mono text-sm" />
|
||
</div>
|
||
|
||
<ResultArea :text="result.output" label="结果" placeholder="结果" />
|
||
<ResultArea v-if="to === '16' && result.outputUppercase !== result.output" :text="result.outputUppercase" label="大写形式" placeholder="大写形式" />
|
||
<div v-if="result.dec !== null" class="text-xs text-muted-foreground">十进制值:{{ result.dec.toString() }}</div>
|
||
<p v-if="error" class="text-xs text-destructive">{{ error }}</p>
|
||
</div>
|
||
</template> |