forked from zguiy/utils
		
	
		
			
				
	
	
		
			464 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			464 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<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>  |