进制转换工具

This commit is contained in:
star7th
2025-04-12 09:26:44 +08:00
parent c8b18a663c
commit 6711583c18
8 changed files with 484 additions and 4 deletions
@@ -0,0 +1,387 @@
'use client';
import React, { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCalculator, faCopy, faCheck, faSyncAlt, faEraser, faCog } from '@fortawesome/free-solid-svg-icons';
import ToolHeader from '@/components/ToolHeader';
import BackToTop from '@/components/BackToTop';
import tools from '@/config/tools';
import { useLanguage } from '@/context/LanguageContext';
// 添加CSS变量样式
const styles = {
card: "card p-6",
container: "min-h-screen flex flex-col max-w-[1440px] mx-auto p-4 md:p-6",
typeBtnActive: "px-4 py-2 rounded-md transition-all bg-gradient-to-r from-[rgb(var(--color-primary))] to-[rgb(var(--color-primary-hover))] text-white shadow-sm shadow-[rgba(var(--color-primary),0.3)]",
typeBtnInactive: "px-4 py-2 rounded-md transition-all btn-secondary",
inputGroup: "flex flex-col gap-2 w-full",
inputLabel: "text-sm font-medium text-secondary",
textArea: "w-full p-3 bg-block border border-purple-glow rounded-lg text-primary focus:border-purple focus:outline-none focus:ring-1 focus:ring-purple resize-y font-mono h-32",
errorMsg: "py-2 px-3 bg-red-900/20 border border-red-700/30 text-error rounded-md",
flexRow: "flex flex-col sm:flex-row gap-4 justify-between items-center",
copyBtn: "flex items-center gap-1 px-3 py-1 text-sm rounded-md bg-block-strong hover:bg-block-hover text-secondary transition-colors",
actionBtn: "flex items-center gap-1 px-4 py-2 rounded-md",
advancedOptionsBtn: "flex items-center gap-1 px-3 py-1 text-sm rounded-md bg-block-strong hover:bg-block-hover text-secondary transition-colors",
checkbox: "w-4 h-4 text-purple bg-block rounded border-purple-glow focus:ring-purple focus:ring-opacity-25",
checkboxLabel: "ml-2 text-sm font-medium text-secondary",
inputField: "w-full p-3 bg-block border border-purple-glow rounded-lg text-primary focus:border-purple focus:outline-none focus:ring-1 focus:ring-purple",
numberInput: "w-20 p-1 bg-block border border-purple-glow rounded-md text-primary text-center focus:border-purple focus:outline-none focus:ring-1 focus:ring-purple"
};
export default function NumberBaseConverter() {
const { t } = useLanguage();
// 从工具配置中获取当前工具图标
const toolIcon = tools.find(tool => tool.code === 'number_base_converter')?.icon || faCalculator;
// 状态管理
const [inputValue, setInputValue] = useState('');
const [outputValue, setOutputValue] = useState('');
const [fromBase, setFromBase] = useState('10'); // 默认从十进制
const [toBase, setToBase] = useState('2'); // 默认转换为二进制
const [customFromBase, setCustomFromBase] = useState('10');
const [customToBase, setCustomToBase] = useState('2');
const [error, setError] = useState('');
const [copied, setCopied] = useState(false);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const [useUppercase, setUseUppercase] = useState(true);
const [addPrefix, setAddPrefix] = useState(false);
const [groupDigits, setGroupDigits] = useState(false);
// 有效的进制列表
const baseOptions = [
{ id: '2', name: t('tools.number_base_converter.binary') },
{ id: '8', name: t('tools.number_base_converter.octal') },
{ id: '10', name: t('tools.number_base_converter.decimal') },
{ id: '16', name: t('tools.number_base_converter.hex') },
{ id: 'custom', name: t('tools.number_base_converter.custom') }
];
// 当输入值、进制等变化时自动转换
useEffect(() => {
if (inputValue.trim() === '') {
setOutputValue('');
setError('');
return;
}
try {
const result = convertBase(
inputValue,
fromBase === 'custom' ? customFromBase : fromBase,
toBase === 'custom' ? customToBase : toBase
);
setOutputValue(result);
setError('');
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError(t('tools.number_base_converter.general_error'));
}
setOutputValue('');
}
}, [inputValue, fromBase, toBase, customFromBase, customToBase, useUppercase, addPrefix, groupDigits, t]);
// 进制转换函数
const convertBase = (value: string, from: string, to: string): string => {
const fromBaseInt = parseInt(from, 10);
const toBaseInt = parseInt(to, 10);
// 验证进制范围
if (fromBaseInt < 2 || fromBaseInt > 36 || toBaseInt < 2 || toBaseInt > 36) {
throw new Error(t('tools.number_base_converter.base_error'));
}
// 移除输入中可能存在的前缀和格式化字符
const cleanValue = value.replace(/^0[bxo]|[\s_]/gi, '');
// 尝试转换为十进制
let decimalValue;
try {
decimalValue = parseInt(cleanValue, fromBaseInt);
// 检查NaN,表明输入无效
if (isNaN(decimalValue)) {
throw new Error();
}
} catch {
throw new Error(t('tools.number_base_converter.input_error'));
}
// 转换为目标进制
let result = decimalValue.toString(toBaseInt);
// 大写十六进制或更高进制字母
if (useUppercase && toBaseInt > 10) {
result = result.toUpperCase();
}
// 添加适当的前缀
if (addPrefix) {
if (toBaseInt === 2) result = '0b' + result;
else if (toBaseInt === 8) result = '0o' + result;
else if (toBaseInt === 16) result = '0x' + result;
}
// 分组数字以提高可读性
if (groupDigits) {
// 二进制每8位分组
if (toBaseInt === 2) {
result = result.replace(/^0[b]/i, '').match(/.{1,8}/g)?.join('_') || result;
if (addPrefix) result = '0b' + result;
}
// 十六进制每4位分组
else if (toBaseInt === 16) {
result = result.replace(/^0[x]/i, '').match(/.{1,4}/g)?.join('_') || result;
if (addPrefix) result = '0x' + result;
}
// 其他进制每4位分组
else if (toBaseInt !== 10) {
result = result.replace(/^0[bo]/i, '').match(/.{1,4}/g)?.join('_') || result;
if (addPrefix) {
if (toBaseInt === 8) result = '0o' + result;
}
}
}
return result;
};
// 复制输出内容到剪贴板
const copyToClipboard = () => {
if (!outputValue) return;
navigator.clipboard.writeText(outputValue)
.then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
})
.catch(err => {
console.error(t('tools.number_base_converter.copy_failed'), err);
setError(t('tools.number_base_converter.clipboard_error'));
});
};
// 清空输入和输出
const clearAll = () => {
setInputValue('');
setOutputValue('');
setError('');
};
// 加载示例
const loadExample = () => {
const examples: Record<string, string> = {
'2': t('tools.number_base_converter.example_binary'),
'8': t('tools.number_base_converter.example_octal'),
'10': t('tools.number_base_converter.example_decimal'),
'16': t('tools.number_base_converter.example_hex')
};
// 从当前选择的进制加载示例
const currentFromBase = fromBase === 'custom' ? customFromBase : fromBase;
const example = examples[currentFromBase] || examples['10'];
setInputValue(example);
};
return (
<div className={styles.container}>
{/* 使用 ToolHeader 组件 */}
<ToolHeader
toolCode="number_base_converter"
title=""
description=""
icon={toolIcon}
/>
{/* 主要内容区域 */}
<div className={styles.card}>
<div className="space-y-6">
{/* 进制选择 */}
<div className={styles.flexRow}>
<div className="w-full sm:w-1/2 space-y-2">
<label className={styles.inputLabel}>{t('tools.number_base_converter.from_base')}</label>
<div className="flex flex-wrap gap-2">
{baseOptions.map((option) => (
<button
key={option.id}
className={fromBase === option.id ? styles.typeBtnActive : styles.typeBtnInactive}
onClick={() => setFromBase(option.id)}
>
{option.name}
</button>
))}
</div>
{fromBase === 'custom' && (
<div className="mt-2">
<label className={styles.inputLabel}>{t('tools.number_base_converter.custom_base_from')}</label>
<input
type="number"
min="2"
max="36"
value={customFromBase}
onChange={(e) => setCustomFromBase(e.target.value)}
className={styles.numberInput}
/>
</div>
)}
</div>
<div className="w-full sm:w-1/2 space-y-2">
<label className={styles.inputLabel}>{t('tools.number_base_converter.to_base')}</label>
<div className="flex flex-wrap gap-2">
{baseOptions.map((option) => (
<button
key={option.id}
className={toBase === option.id ? styles.typeBtnActive : styles.typeBtnInactive}
onClick={() => setToBase(option.id)}
>
{option.name}
</button>
))}
</div>
{toBase === 'custom' && (
<div className="mt-2">
<label className={styles.inputLabel}>{t('tools.number_base_converter.custom_base_to')}</label>
<input
type="number"
min="2"
max="36"
value={customToBase}
onChange={(e) => setCustomToBase(e.target.value)}
className={styles.numberInput}
/>
</div>
)}
</div>
</div>
{/* 高级选项 */}
<div>
<button
className={styles.advancedOptionsBtn}
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
>
<FontAwesomeIcon icon={faCog} className="mr-1" />
{t('tools.number_base_converter.advanced_options')}
</button>
{showAdvancedOptions && (
<div className="mt-3 p-3 bg-block-strong rounded-md space-y-2">
<div className="flex items-center">
<input
id="uppercase"
type="checkbox"
checked={useUppercase}
onChange={(e) => setUseUppercase(e.target.checked)}
className={styles.checkbox}
/>
<label htmlFor="uppercase" className={styles.checkboxLabel}>
{t('tools.number_base_converter.use_uppercase')}
</label>
</div>
<div className="flex items-center">
<input
id="prefix"
type="checkbox"
checked={addPrefix}
onChange={(e) => setAddPrefix(e.target.checked)}
className={styles.checkbox}
/>
<label htmlFor="prefix" className={styles.checkboxLabel}>
{t('tools.number_base_converter.add_prefix')}
</label>
</div>
<div className="flex items-center">
<input
id="group"
type="checkbox"
checked={groupDigits}
onChange={(e) => setGroupDigits(e.target.checked)}
className={styles.checkbox}
/>
<label htmlFor="group" className={styles.checkboxLabel}>
{t('tools.number_base_converter.group_digits')}
</label>
</div>
</div>
)}
</div>
{/* 输入区域 */}
<div className={styles.inputGroup}>
<label className={styles.inputLabel}>{t('tools.number_base_converter.input_label')}</label>
<textarea
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder={t('tools.number_base_converter.input_placeholder')}
className={styles.textArea}
/>
</div>
{/* 操作按钮 */}
<div className="flex justify-between flex-wrap gap-2">
<div className="flex gap-2">
<button
className="btn-primary"
onClick={loadExample}
>
<FontAwesomeIcon icon={faSyncAlt} className="mr-2" />
{t('tools.number_base_converter.load_example')}
</button>
<button
className="btn-secondary"
onClick={clearAll}
>
<FontAwesomeIcon icon={faEraser} className="mr-2" />
{t('tools.number_base_converter.clear')}
</button>
</div>
</div>
{/* 错误消息 */}
{error && (
<div className={styles.errorMsg}>
{error}
</div>
)}
{/* 输出区域 */}
<div className={styles.inputGroup}>
<div className="flex justify-between items-center">
<label className={styles.inputLabel}>{t('tools.number_base_converter.result_label')}</label>
{outputValue && (
<button
className={styles.copyBtn}
onClick={copyToClipboard}
>
<FontAwesomeIcon icon={copied ? faCheck : faCopy} />
{copied
? t('tools.number_base_converter.copy_success')
: t('tools.number_base_converter.copy')}
</button>
)}
</div>
<textarea
value={outputValue}
readOnly
placeholder={t('tools.number_base_converter.output_placeholder')}
className={styles.textArea}
/>
</div>
</div>
</div>
{/* 返回顶部按钮 */}
<BackToTop />
</div>
);
}
+2 -1
View File
@@ -25,7 +25,8 @@ export const en = {
text_space_stripper: tools.text_space_stripper.en,
timezone_converter: tools.timezone_converter.en,
unicode_converter: tools.unicode_converter.en,
url_encoder: tools.url_encoder.en
url_encoder: tools.url_encoder.en,
number_base_converter: tools.number_base_converter.en
}
};
+3 -1
View File
@@ -20,6 +20,7 @@ import textSpaceStripper from './text_space_stripper';
import timezoneConverter from './timezone_converter';
import unicodeConverter from './unicode_converter';
import urlEncoder from './url_encoder';
import numberBaseConverter from './number_base_converter';
export const tools = {
json_formatter: jsonFormatter,
@@ -43,7 +44,8 @@ export const tools = {
text_space_stripper: textSpaceStripper,
timezone_converter: timezoneConverter,
unicode_converter: unicodeConverter,
url_encoder: urlEncoder
url_encoder: urlEncoder,
number_base_converter: numberBaseConverter
};
export default tools;
@@ -0,0 +1,37 @@
export const numberBaseConverterEn = {
title: 'Number Base Converter',
description: 'Convert between binary, octal, decimal, hexadecimal and other number bases',
from_base: 'From Base',
to_base: 'To Base',
convert: 'Convert',
copy: 'Copy Result',
clear: 'Clear',
load_example: 'Load Example',
input_placeholder: 'Enter a value to convert...',
output_placeholder: 'Conversion result will appear here...',
binary: 'Binary (2)',
octal: 'Octal (8)',
decimal: 'Decimal (10)',
hex: 'Hexadecimal (16)',
custom: 'Custom',
custom_base_from: 'Custom Base (2-36)',
custom_base_to: 'Target Base (2-36)',
base_error: 'Base must be an integer between 2 and 36',
input_error: 'Invalid input, please ensure the value matches the selected base',
general_error: 'An error occurred during conversion',
example_decimal: '255',
example_binary: '11111111',
example_octal: '377',
example_hex: 'FF',
result_label: 'Conversion Result',
input_label: 'Value Input',
copy_success: 'Copied to clipboard',
copy_failed: 'Copy failed:',
clipboard_error: 'Failed to copy to clipboard',
advanced_options: 'Advanced Options',
use_uppercase: 'Use uppercase letters (for hex and higher bases)',
add_prefix: 'Add prefixes (0b, 0o, 0x etc.)',
group_digits: 'Group digits (every 4 or 8 digits)'
};
export default numberBaseConverterEn;
@@ -0,0 +1,9 @@
import numberBaseConverterZh from './zh';
import numberBaseConverterEn from './en';
export const numberBaseConverter = {
zh: numberBaseConverterZh,
en: numberBaseConverterEn
};
export default numberBaseConverter;
@@ -0,0 +1,37 @@
export const numberBaseConverterZh = {
title: '进制转换工具',
description: '二进制、八进制、十进制、十六进制等数值进制转换工具',
from_base: '从进制',
to_base: '转换为',
convert: '转换',
copy: '复制结果',
clear: '清空',
load_example: '加载示例',
input_placeholder: '输入要转换的数值...',
output_placeholder: '转换结果将在这里显示...',
binary: '二进制 (2)',
octal: '八进制 (8)',
decimal: '十进制 (10)',
hex: '十六进制 (16)',
custom: '自定义',
custom_base_from: '自定义进制 (2-36)',
custom_base_to: '目标进制 (2-36)',
base_error: '进制必须是2到36之间的整数',
input_error: '输入无效,请确保数值符合所选进制',
general_error: '转换过程中发生错误',
example_decimal: '255',
example_binary: '11111111',
example_octal: '377',
example_hex: 'FF',
result_label: '转换结果',
input_label: '数值输入',
copy_success: '已复制到剪贴板',
copy_failed: '复制失败:',
clipboard_error: '复制到剪贴板失败',
advanced_options: '高级选项',
use_uppercase: '使用大写字母(用于十六进制以上进制)',
add_prefix: '添加前缀(0b, 0o, 0x等)',
group_digits: '分组数字(每4位或8位)'
};
export default numberBaseConverterZh;
+2 -1
View File
@@ -25,7 +25,8 @@ export const zh = {
text_space_stripper: tools.text_space_stripper.zh,
timezone_converter: tools.timezone_converter.zh,
unicode_converter: tools.unicode_converter.zh,
url_encoder: tools.url_encoder.zh
url_encoder: tools.url_encoder.zh,
number_base_converter: tools.number_base_converter.zh
}
};
+7 -1
View File
@@ -2,7 +2,7 @@ import {
faCode, faExchangeAlt, faClock, faGlobe, faLink, faLock,
faImage, faCogs, faFileCode, faKey, faFont,
faCalendarAlt, faPalette, faEdit, faRuler, faNetworkWired,
faEraser
faEraser, faCalculator
} from '@fortawesome/free-solid-svg-icons';
import { Tool } from '@/types/tools';
@@ -141,6 +141,12 @@ const tools: Tool[] = [
icon: faCogs,
category: ['frontend'],
keywords: ['css渐变', '渐变', '渐变背景', 'css', 'gradient', '背景', 'css生成器', 'jianbian', 'jb', 'beijing', 'bj']
},
{
code: 'number_base_converter',
icon: faCalculator,
category: ['encoding'],
keywords: ['进制转换', '进制', '二进制', '八进制', '十进制', '十六进制', '二进制转换', '十六进制转换', 'binary', 'hex', 'decimal', 'octal', 'base conversion', 'jinzhi', 'jzzh', 'jz']
}
] as Tool[];