1
0
forked from zguiy/utils
Files
utils/src/components/tools/NumberBaseConverter.vue
2025-06-28 22:38:49 +08:00

464 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="space-y-6">
<!-- 进制选择 -->
<div class="card p-6">
<h2 class="text-lg font-medium text-primary mb-4">数字进制转换器</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 源进制选择 -->
<div>
<label class="block text-sm font-medium text-secondary mb-2">从进制</label>
<div class="grid grid-cols-2 gap-2 mb-3">
<button
v-for="base in baseOptions"
:key="'from-' + base.id"
:class="[
'px-3 py-2 text-sm rounded transition-all',
fromBase === base.id ? 'bg-primary text-white' : 'btn-secondary'
]"
@click="setFromBase(base.id)"
>
{{ base.name }}
</button>
</div>
<!-- 自定义进制输入 -->
<div v-if="fromBase === 'custom'" class="flex items-center gap-2">
<span class="text-sm text-secondary">自定义进制:</span>
<input
v-model.number="customFromBase"
type="number"
min="2"
max="36"
class="w-20 p-1 bg-block border border-primary/20 rounded text-center input-field"
/>
</div>
</div>
<!-- 目标进制选择 -->
<div>
<label class="block text-sm font-medium text-secondary mb-2">到进制</label>
<div class="grid grid-cols-2 gap-2 mb-3">
<button
v-for="base in baseOptions"
:key="'to-' + base.id"
:class="[
'px-3 py-2 text-sm rounded transition-all',
toBase === base.id ? 'bg-primary text-white' : 'btn-secondary'
]"
@click="setToBase(base.id)"
>
{{ base.name }}
</button>
</div>
<!-- 自定义进制输入 -->
<div v-if="toBase === 'custom'" class="flex items-center gap-2">
<span class="text-sm text-secondary">自定义进制:</span>
<input
v-model.number="customToBase"
type="number"
min="2"
max="36"
class="w-20 p-1 bg-block border border-primary/20 rounded text-center input-field"
/>
</div>
</div>
</div>
</div>
<!-- 输入和输出区域 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 输入区域 -->
<div class="card p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-md font-medium text-primary">输入数字</h3>
<div class="flex gap-2">
<button @click="loadExample" class="btn-secondary text-sm">
<FontAwesomeIcon :icon="['fas', 'sync-alt']" class="mr-1" />
示例
</button>
<button @click="clearAll" class="btn-secondary text-sm">
<FontAwesomeIcon :icon="['fas', 'eraser']" class="mr-1" />
清空
</button>
</div>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm text-secondary font-medium mb-2">
{{ getBaseLabel(getCurrentFromBase()) }} 数字
</label>
<textarea
v-model="inputValue"
class="textarea-field h-32 w-full resize-y font-mono"
:placeholder="getInputPlaceholder()"
/>
</div>
<!-- 错误消息 -->
<div v-if="error" class="p-3 bg-red-900/20 border border-red-700/30 text-error rounded-lg">
<FontAwesomeIcon :icon="['fas', 'exclamation-triangle']" class="mr-2" />
{{ error }}
</div>
</div>
</div>
<!-- 输出区域 -->
<div class="card p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-md font-medium text-primary">转换结果</h3>
<button
v-if="outputValue"
@click="copyToClipboard"
class="btn-secondary text-sm"
>
<FontAwesomeIcon :icon="copied ? ['fas', 'check'] : ['fas', 'copy']" class="mr-1" />
{{ copied ? '已复制' : '复制' }}
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm text-secondary font-medium mb-2">
{{ getBaseLabel(getCurrentToBase()) }} 数字
</label>
<textarea
v-model="outputValue"
readonly
class="textarea-field h-32 w-full resize-y font-mono bg-block"
placeholder="转换结果将在这里显示..."
/>
</div>
<!-- 转换信息 -->
<div v-if="outputValue" class="text-sm text-tertiary">
<div>十进制值: {{ getDecimalValue() }}</div>
<div>字符长度: {{ outputValue.length }}</div>
</div>
</div>
</div>
</div>
<!-- 高级选项 -->
<div class="card p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-md font-medium text-primary">高级选项</h3>
<button
@click="showAdvancedOptions = !showAdvancedOptions"
class="btn-secondary text-sm"
>
<FontAwesomeIcon :icon="['fas', 'cog']" class="mr-1" />
{{ showAdvancedOptions ? '隐藏选项' : '显示选项' }}
</button>
</div>
<div v-if="showAdvancedOptions" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<label class="flex items-center">
<input
v-model="useUppercase"
type="checkbox"
class="w-4 h-4 text-primary bg-block rounded border-primary/20 focus:ring-primary focus:ring-opacity-25"
/>
<span class="ml-2 text-sm text-secondary">使用大写字母</span>
</label>
<label class="flex items-center">
<input
v-model="addPrefix"
type="checkbox"
class="w-4 h-4 text-primary bg-block rounded border-primary/20 focus:ring-primary focus:ring-opacity-25"
/>
<span class="ml-2 text-sm text-secondary">添加进制前缀</span>
</label>
<label class="flex items-center">
<input
v-model="groupDigits"
type="checkbox"
class="w-4 h-4 text-primary bg-block rounded border-primary/20 focus:ring-primary focus:ring-opacity-25"
/>
<span class="ml-2 text-sm text-secondary">数字分组</span>
</label>
</div>
</div>
<!-- 快速转换面板 -->
<div class="card p-6">
<h3 class="text-md font-medium text-primary mb-4">快速转换</h3>
<div v-if="inputValue && !error" class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div
v-for="quickBase in quickBases"
:key="quickBase.id"
class="bg-block rounded-lg p-4"
>
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-secondary">{{ quickBase.name }}</h4>
<button
@click="() => copyQuickResult(quickBase.id)"
class="text-tertiary hover:text-primary transition-colors"
>
<FontAwesomeIcon :icon="['fas', 'copy']" />
</button>
</div>
<code class="text-sm text-primary font-mono break-all">
{{ getQuickConversion(quickBase.id) }}
</code>
</div>
</div>
<div v-else class="text-center py-8 text-tertiary">
请输入有效数字以查看快速转换结果
</div>
</div>
<!-- 使用说明 -->
<div class="card p-6">
<h3 class="text-md font-medium text-primary mb-4">
<FontAwesomeIcon :icon="['fas', 'info-circle']" class="mr-2" />
使用说明
</h3>
<div class="text-sm text-secondary space-y-2">
<p> 支持 2-36 进制之间的任意转换</p>
<p> 可以输入带前缀的数字 0x, 0b, 0o</p>
<p> 支持大写/小写字母添加前缀数字分组等格式选项</p>
<p> 十六进制以上的进制使用字母 A-Z 表示 10-35</p>
<p> 输入时可以使用空格或下划线分隔系统会自动忽略</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
// 响应式状态
const inputValue = ref('')
const outputValue = ref('')
const fromBase = ref('10')
const toBase = ref('2')
const customFromBase = ref(10)
const customToBase = ref(2)
const error = ref('')
const copied = ref(false)
const showAdvancedOptions = ref(false)
const useUppercase = ref(true)
const addPrefix = ref(false)
const groupDigits = ref(false)
// 进制选项
const baseOptions = [
{ id: '2', name: '二进制' },
{ id: '8', name: '八进制' },
{ id: '10', name: '十进制' },
{ id: '16', name: '十六进制' },
{ id: 'custom', name: '自定义' }
]
// 快速转换进制
const quickBases = [
{ id: '2', name: '二进制' },
{ id: '8', name: '八进制' },
{ id: '10', name: '十进制' },
{ id: '16', name: '十六进制' }
]
// 获取当前源进制
const getCurrentFromBase = (): number => {
return fromBase.value === 'custom' ? customFromBase.value : parseInt(fromBase.value)
}
// 获取当前目标进制
const getCurrentToBase = (): number => {
return toBase.value === 'custom' ? customToBase.value : parseInt(toBase.value)
}
// 获取进制标签
const getBaseLabel = (base: number): string => {
const labels: Record<number, string> = {
2: '二进制',
8: '八进制',
10: '十进制',
16: '十六进制'
}
return labels[base] || `${base}进制`
}
// 获取输入提示
const getInputPlaceholder = (): string => {
const base = getCurrentFromBase()
if (base === 2) return '例如: 1010, 0b1010'
if (base === 8) return '例如: 755, 0o755'
if (base === 10) return '例如: 42, 255'
if (base === 16) return '例如: FF, 0xFF'
return `请输入${base}进制数字...`
}
// 获取十进制值
const getDecimalValue = (): string => {
if (!inputValue.value.trim() || error.value) return '-'
try {
const cleanValue = inputValue.value.replace(/^0[bxo]|[\s_]/gi, '')
const decimal = parseInt(cleanValue, getCurrentFromBase())
return isNaN(decimal) ? '-' : decimal.toString()
} catch {
return '-'
}
}
// 进制转换函数
const convertBase = (value: string, from: number, to: number): string => {
// 验证进制范围
if (from < 2 || from > 36 || to < 2 || to > 36) {
throw new Error('进制范围必须在 2-36 之间')
}
// 移除输入中可能存在的前缀和格式化字符
const cleanValue = value.replace(/^0[bxo]|[\s_]/gi, '')
// 转换为十进制
let decimalValue
try {
decimalValue = parseInt(cleanValue, from)
if (isNaN(decimalValue)) {
throw new Error()
}
} catch {
throw new Error('输入的数字格式无效')
}
// 转换为目标进制
let result = decimalValue.toString(to)
// 大写字母
if (useUppercase.value && to > 10) {
result = result.toUpperCase()
}
// 添加前缀
if (addPrefix.value) {
if (to === 2) result = '0b' + result
else if (to === 8) result = '0o' + result
else if (to === 16) result = '0x' + result
}
// 数字分组
if (groupDigits.value) {
const prefix = result.match(/^0[bxo]/i)?.[0] || ''
const digits = result.replace(/^0[bxo]/i, '')
let grouped = ''
if (to === 2) {
// 二进制每8位分组
grouped = digits.match(/.{1,8}/g)?.join('_') || digits
} else if (to === 16) {
// 十六进制每4位分组
grouped = digits.match(/.{1,4}/g)?.join('_') || digits
} else {
// 其他进制每4位分组
grouped = digits.match(/.{1,4}/g)?.join('_') || digits
}
result = prefix + grouped
}
return result
}
// 获取快速转换结果
const getQuickConversion = (baseId: string): string => {
if (!inputValue.value.trim() || error.value) return '-'
try {
return convertBase(inputValue.value, getCurrentFromBase(), parseInt(baseId))
} catch {
return '错误'
}
}
// 设置源进制
const setFromBase = (base: string) => {
fromBase.value = base
}
// 设置目标进制
const setToBase = (base: string) => {
toBase.value = base
}
// 复制输出内容
const copyToClipboard = async () => {
if (!outputValue.value) return
try {
await navigator.clipboard.writeText(outputValue.value)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
} catch (err) {
console.error('复制失败:', err)
error.value = '复制失败'
}
}
// 复制快速转换结果
const copyQuickResult = async (baseId: string) => {
const result = getQuickConversion(baseId)
if (result === '-' || result === '错误') return
try {
await navigator.clipboard.writeText(result)
} catch (err) {
console.error('复制失败:', err)
}
}
// 清空所有内容
const clearAll = () => {
inputValue.value = ''
outputValue.value = ''
error.value = ''
}
// 加载示例
const loadExample = () => {
const examples: Record<number, string> = {
2: '1010',
8: '755',
10: '42',
16: 'FF'
}
const currentFromBase = getCurrentFromBase()
const example = examples[currentFromBase] || examples[10]
inputValue.value = example
}
// 监听输入变化并自动转换
watch([inputValue, fromBase, toBase, customFromBase, customToBase, useUppercase, addPrefix, groupDigits], () => {
if (inputValue.value.trim() === '') {
outputValue.value = ''
error.value = ''
return
}
try {
const result = convertBase(inputValue.value, getCurrentFromBase(), getCurrentToBase())
outputValue.value = result
error.value = ''
} catch (err) {
if (err instanceof Error) {
error.value = err.message
} else {
error.value = '转换错误'
}
outputValue.value = ''
}
})
</script>