进制转换工具
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user