forked from zguiy/utils
工具完成
This commit is contained in:
464
src/components/tools/NumberBaseConverter.vue
Normal file
464
src/components/tools/NumberBaseConverter.vue
Normal file
@ -0,0 +1,464 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user