|
<!DOCTYPE html> |
|
<html lang="zh-CN"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>数字转中文</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
.spinner { |
|
animation: spin 1s linear infinite; |
|
} |
|
.toast { |
|
transition: all 0.3s ease; |
|
} |
|
.toast-enter { |
|
opacity: 0; |
|
transform: translateY(20px); |
|
} |
|
.toast-enter-active { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
.toast-exit { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
.toast-exit-active { |
|
opacity: 0; |
|
transform: translateY(-20px); |
|
} |
|
.btn-scale:hover { |
|
transform: scale(1.05); |
|
} |
|
.btn-scale:active { |
|
transform: scale(0.98); |
|
} |
|
.container-shadow { |
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); |
|
} |
|
.container-shadow:hover { |
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); |
|
} |
|
@media (max-width: 640px) { |
|
.input-group { |
|
flex-direction: column; |
|
} |
|
.convert-btn { |
|
width: 100%; |
|
margin-top: 12px; |
|
margin-left: 0 !important; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-50 min-h-screen flex items-center justify-center p-4 dark:bg-gray-800 transition-colors duration-200"> |
|
<div class="bg-white rounded-xl p-6 w-full max-w-2xl container-shadow transition-all duration-300 dark:bg-gray-700"> |
|
<h1 class="text-3xl font-bold text-center text-gray-800 mb-6 dark:text-white">数字转中文</h1> |
|
|
|
<div class="mb-6"> |
|
<div class="flex input-group"> |
|
<input |
|
type="text" |
|
id="numberInput" |
|
placeholder="请输入阿拉伯数字" |
|
class="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-600 dark:border-gray-500 dark:text-white dark:placeholder-gray-300" |
|
aria-label="输入阿拉伯数字" |
|
> |
|
<button |
|
id="convertBtn" |
|
class="convert-btn ml-3 px-6 py-3 bg-blue-500 text-white font-medium rounded-lg hover:bg-blue-600 transition-colors duration-200 btn-scale disabled:bg-gray-400 disabled:cursor-not-allowed dark:bg-blue-600 dark:hover:bg-blue-700" |
|
disabled |
|
> |
|
转换 |
|
</button> |
|
</div> |
|
<p id="errorText" class="text-red-500 text-sm mt-2 h-5"></p> |
|
</div> |
|
|
|
<div id="resultContainer" class="hidden bg-gray-50 rounded-lg p-5 mb-6 dark:bg-gray-600"> |
|
<div class="flex items-center justify-between"> |
|
<p id="chineseResult" class="text-2xl font-semibold text-blue-500 dark:text-blue-300"></p> |
|
<button |
|
id="copyBtn" |
|
class="p-2 rounded-full hover:bg-gray-200 transition-colors duration-200 dark:hover:bg-gray-500" |
|
aria-label="复制结果" |
|
> |
|
<i class="far fa-copy text-gray-600 dark:text-gray-300"></i> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="text-sm text-gray-500 dark:text-gray-300"> |
|
<p class="mb-2"><i class="fas fa-info-circle mr-2"></i>支持整数、小数和负数转换</p> |
|
<p><i class="fas fa-lightbulb mr-2"></i>示例: 1234 → "一千二百三十四", -56.78 → "负五十六元七角八分"</p> |
|
</div> |
|
</div> |
|
|
|
<div id="toast" class="fixed bottom-6 left-1/2 transform -translate-x-1/2 hidden"> |
|
<div class="bg-gray-800 text-white px-4 py-2 rounded-lg flex items-center"> |
|
<i class="fas fa-check-circle mr-2 text-green-400"></i> |
|
<span>已复制!</span> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
const numberInput = document.getElementById('numberInput'); |
|
const convertBtn = document.getElementById('convertBtn'); |
|
const resultContainer = document.getElementById('resultContainer'); |
|
const chineseResult = document.getElementById('chineseResult'); |
|
const copyBtn = document.getElementById('copyBtn'); |
|
const errorText = document.getElementById('errorText'); |
|
const toast = document.getElementById('toast'); |
|
|
|
|
|
const DIGITS = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; |
|
const UNITS = ['', '十', '百', '千']; |
|
const GROUP_UNITS = ['', '万', '亿', '兆']; |
|
const DECIMAL_UNITS = ['元', '角', '分']; |
|
|
|
|
|
const NUMBER_REGEX = /^-?\d{1,3}(,\d{3})*(\.\d{0,2})?$/; |
|
const MAX_NUMBER = 10 ** 16 - 1; |
|
const MIN_NUMBER = -(10 ** 16 - 1); |
|
|
|
|
|
numberInput.addEventListener('input', handleInputChange); |
|
convertBtn.addEventListener('click', convertNumber); |
|
copyBtn.addEventListener('click', copyResult); |
|
|
|
|
|
function handleInputChange(e) { |
|
const value = e.target.value; |
|
|
|
|
|
if (value && value !== '-') { |
|
const cursorPos = e.target.selectionStart; |
|
const cleaned = value.replace(/[^\d.-]/g, ''); |
|
|
|
|
|
let parts = cleaned.split('.'); |
|
let integerPart = parts[0].replace(/\D/g, ''); |
|
let formatted = ''; |
|
|
|
if (integerPart) { |
|
|
|
const isNegative = integerPart.startsWith('-'); |
|
if (isNegative) integerPart = integerPart.substring(1); |
|
|
|
|
|
formatted = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); |
|
|
|
if (isNegative) formatted = '-' + formatted; |
|
} |
|
|
|
|
|
if (parts.length > 1) { |
|
formatted += '.' + parts[1].replace(/\D/g, '').substring(0, 2); |
|
} |
|
|
|
|
|
e.target.value = formatted; |
|
|
|
|
|
const diff = formatted.length - value.length; |
|
e.target.setSelectionRange(cursorPos + diff, cursorPos + diff); |
|
} |
|
|
|
|
|
validateInput(); |
|
} |
|
|
|
|
|
function validateInput() { |
|
const value = numberInput.value.trim(); |
|
|
|
if (!value) { |
|
errorText.textContent = ''; |
|
convertBtn.disabled = true; |
|
return; |
|
} |
|
|
|
|
|
if (!NUMBER_REGEX.test(value)) { |
|
errorText.textContent = '请输入有效数字'; |
|
convertBtn.disabled = true; |
|
numberInput.classList.add('border-red-500'); |
|
numberInput.classList.remove('border-gray-300'); |
|
return; |
|
} |
|
|
|
|
|
const num = parseFloat(value.replace(/,/g, '')); |
|
|
|
if (num > MAX_NUMBER || num < MIN_NUMBER) { |
|
errorText.textContent = `数字必须在 ${MIN_NUMBER} 到 ${MAX_NUMBER} 之间`; |
|
convertBtn.disabled = true; |
|
numberInput.classList.add('border-red-500'); |
|
numberInput.classList.remove('border-gray-300'); |
|
return; |
|
} |
|
|
|
|
|
errorText.textContent = ''; |
|
convertBtn.disabled = false; |
|
numberInput.classList.remove('border-red-500'); |
|
numberInput.classList.add('border-gray-300'); |
|
} |
|
|
|
|
|
function convertNumber() { |
|
const value = numberInput.value.trim(); |
|
if (!value) return; |
|
|
|
|
|
convertBtn.innerHTML = '<i class="fas fa-circle-notch spinner mr-2"></i> 转换中...'; |
|
convertBtn.disabled = true; |
|
|
|
|
|
setTimeout(() => { |
|
try { |
|
const num = parseFloat(value.replace(/,/g, '')); |
|
const result = numberToChinese(num); |
|
|
|
|
|
chineseResult.textContent = result; |
|
resultContainer.classList.remove('hidden'); |
|
|
|
|
|
localStorage.setItem('lastConversion', JSON.stringify({ |
|
number: value, |
|
chinese: result |
|
})); |
|
} catch (error) { |
|
errorText.textContent = '转换出错,请重试'; |
|
console.error('Conversion error:', error); |
|
} finally { |
|
|
|
convertBtn.innerHTML = '转换'; |
|
convertBtn.disabled = false; |
|
} |
|
}, 300); |
|
} |
|
|
|
|
|
function numberToChinese(num) { |
|
if (isNaN(num)) throw new Error('Invalid number'); |
|
|
|
|
|
if (num === 0) return '零元整'; |
|
|
|
|
|
let isNegative = false; |
|
if (num < 0) { |
|
isNegative = true; |
|
num = Math.abs(num); |
|
} |
|
|
|
|
|
const str = num.toString(); |
|
let [integerPart, decimalPart = ''] = str.split('.'); |
|
|
|
|
|
let chineseInteger = ''; |
|
if (integerPart !== '0') { |
|
|
|
const groups = []; |
|
let temp = integerPart; |
|
|
|
while (temp.length > 0) { |
|
const start = Math.max(0, temp.length - 4); |
|
groups.unshift(temp.substring(start)); |
|
temp = temp.substring(0, start); |
|
} |
|
|
|
|
|
chineseInteger = groups.map((group, index) => { |
|
const groupUnit = GROUP_UNITS[groups.length - 1 - index]; |
|
return convertGroup(group) + groupUnit; |
|
}).join(''); |
|
|
|
|
|
chineseInteger = chineseInteger.replace(/零+/g, '零'); |
|
|
|
|
|
if (chineseInteger.endsWith('零')) { |
|
chineseInteger = chineseInteger.substring(0, chineseInteger.length - 1); |
|
} |
|
|
|
|
|
chineseInteger += '元'; |
|
} |
|
|
|
|
|
let chineseDecimal = ''; |
|
if (decimalPart) { |
|
|
|
decimalPart = decimalPart.substring(0, 2).padEnd(2, '0'); |
|
|
|
|
|
const jiao = decimalPart[0] !== '0' ? DIGITS[parseInt(decimalPart[0])] + DECIMAL_UNITS[1] : ''; |
|
const fen = decimalPart[1] !== '0' ? DIGITS[parseInt(decimalPart[1])] + DECIMAL_UNITS[2] : ''; |
|
|
|
chineseDecimal = jiao + fen; |
|
|
|
|
|
if (!chineseInteger && chineseDecimal) { |
|
chineseInteger = ''; |
|
} |
|
} else if (integerPart !== '0') { |
|
|
|
chineseDecimal = '整'; |
|
} |
|
|
|
|
|
let result = chineseInteger + chineseDecimal; |
|
|
|
|
|
if (result.startsWith('一十')) { |
|
result = result.replace('一十', '十'); |
|
} |
|
|
|
|
|
if (isNegative) { |
|
result = '负' + result; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
function convertGroup(group) { |
|
group = group.padStart(4, '0'); |
|
let result = ''; |
|
|
|
for (let i = 0; i < 4; i++) { |
|
const digit = parseInt(group[i]); |
|
const unit = UNITS[4 - 1 - i]; |
|
|
|
if (digit === 0) { |
|
|
|
if (result && !result.endsWith('零')) { |
|
result += DIGITS[0]; |
|
} |
|
} else { |
|
result += DIGITS[digit] + unit; |
|
} |
|
} |
|
|
|
|
|
if (result.endsWith('零')) { |
|
result = result.substring(0, result.length - 1); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
function copyResult() { |
|
const text = chineseResult.textContent; |
|
if (!text) return; |
|
|
|
navigator.clipboard.writeText(text).then(() => { |
|
showToast(); |
|
}).catch(err => { |
|
console.error('Failed to copy:', err); |
|
}); |
|
} |
|
|
|
|
|
function showToast() { |
|
toast.classList.remove('hidden'); |
|
|
|
setTimeout(() => { |
|
toast.classList.add('hidden'); |
|
}, 2000); |
|
} |
|
|
|
|
|
function loadLastConversion() { |
|
const lastConversion = localStorage.getItem('lastConversion'); |
|
if (lastConversion) { |
|
try { |
|
const { number, chinese } = JSON.parse(lastConversion); |
|
numberInput.value = number; |
|
chineseResult.textContent = chinese; |
|
resultContainer.classList.remove('hidden'); |
|
validateInput(); |
|
} catch (e) { |
|
console.error('Failed to load last conversion:', e); |
|
} |
|
} |
|
} |
|
|
|
|
|
loadLastConversion(); |
|
}); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=drdata/numbers-to-chinese-cheque" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |