drdata's picture
Add 2 files
48d4bfd verified
<!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() {
// DOM elements
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');
// Character maps
const DIGITS = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const UNITS = ['', '十', '百', '千'];
const GROUP_UNITS = ['', '万', '亿', '兆'];
const DECIMAL_UNITS = ['元', '角', '分'];
// Validation regex
const NUMBER_REGEX = /^-?\d{1,3}(,\d{3})*(\.\d{0,2})?$/;
const MAX_NUMBER = 10 ** 16 - 1;
const MIN_NUMBER = -(10 ** 16 - 1);
// Event listeners
numberInput.addEventListener('input', handleInputChange);
convertBtn.addEventListener('click', convertNumber);
copyBtn.addEventListener('click', copyResult);
// Input change handler
function handleInputChange(e) {
const value = e.target.value;
// Format with commas as user types
if (value && value !== '-') {
const cursorPos = e.target.selectionStart;
const cleaned = value.replace(/[^\d.-]/g, '');
// Format with commas
let parts = cleaned.split('.');
let integerPart = parts[0].replace(/\D/g, '');
let formatted = '';
if (integerPart) {
// Handle negative numbers
const isNegative = integerPart.startsWith('-');
if (isNegative) integerPart = integerPart.substring(1);
// Add commas every 3 digits
formatted = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
if (isNegative) formatted = '-' + formatted;
}
// Add decimal part if exists
if (parts.length > 1) {
formatted += '.' + parts[1].replace(/\D/g, '').substring(0, 2);
}
// Update input value
e.target.value = formatted;
// Restore cursor position
const diff = formatted.length - value.length;
e.target.setSelectionRange(cursorPos + diff, cursorPos + diff);
}
// Validate input
validateInput();
}
// Validate input
function validateInput() {
const value = numberInput.value.trim();
if (!value) {
errorText.textContent = '';
convertBtn.disabled = true;
return;
}
// Check if valid number format
if (!NUMBER_REGEX.test(value)) {
errorText.textContent = '请输入有效数字';
convertBtn.disabled = true;
numberInput.classList.add('border-red-500');
numberInput.classList.remove('border-gray-300');
return;
}
// Convert to number and check range
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;
}
// Valid input
errorText.textContent = '';
convertBtn.disabled = false;
numberInput.classList.remove('border-red-500');
numberInput.classList.add('border-gray-300');
}
// Convert number to Chinese
function convertNumber() {
const value = numberInput.value.trim();
if (!value) return;
// Show loading state
convertBtn.innerHTML = '<i class="fas fa-circle-notch spinner mr-2"></i> 转换中...';
convertBtn.disabled = true;
// Simulate processing delay (in a real app, this would be immediate)
setTimeout(() => {
try {
const num = parseFloat(value.replace(/,/g, ''));
const result = numberToChinese(num);
// Display result
chineseResult.textContent = result;
resultContainer.classList.remove('hidden');
// Save to localStorage
localStorage.setItem('lastConversion', JSON.stringify({
number: value,
chinese: result
}));
} catch (error) {
errorText.textContent = '转换出错,请重试';
console.error('Conversion error:', error);
} finally {
// Reset button
convertBtn.innerHTML = '转换';
convertBtn.disabled = false;
}
}, 300);
}
// Core conversion function
function numberToChinese(num) {
if (isNaN(num)) throw new Error('Invalid number');
// Handle zero
if (num === 0) return '零元整';
// Handle negative numbers
let isNegative = false;
if (num < 0) {
isNegative = true;
num = Math.abs(num);
}
// Split into integer and decimal parts
const str = num.toString();
let [integerPart, decimalPart = ''] = str.split('.');
// Process integer part
let chineseInteger = '';
if (integerPart !== '0') {
// Split into 4-digit groups (right to left)
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);
}
// Convert each group
chineseInteger = groups.map((group, index) => {
const groupUnit = GROUP_UNITS[groups.length - 1 - index];
return convertGroup(group) + groupUnit;
}).join('');
// Remove consecutive zeros
chineseInteger = chineseInteger.replace(/零+/g, '零');
// Remove trailing zero
if (chineseInteger.endsWith('零')) {
chineseInteger = chineseInteger.substring(0, chineseInteger.length - 1);
}
// Add '元' for financial format
chineseInteger += '元';
}
// Process decimal part
let chineseDecimal = '';
if (decimalPart) {
// Round to 2 digits
decimalPart = decimalPart.substring(0, 2).padEnd(2, '0');
// Convert each digit
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;
// Special case: .05 → "五分" not "零元五分"
if (!chineseInteger && chineseDecimal) {
chineseInteger = '';
}
} else if (integerPart !== '0') {
// No decimal part but has integer
chineseDecimal = '整';
}
// Combine parts
let result = chineseInteger + chineseDecimal;
// Handle special cases
if (result.startsWith('一十')) {
result = result.replace('一十', '十');
}
// Add negative prefix if needed
if (isNegative) {
result = '负' + result;
}
return result;
}
// Convert a 4-digit group
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) {
// Only add zero if not leading or consecutive
if (result && !result.endsWith('零')) {
result += DIGITS[0];
}
} else {
result += DIGITS[digit] + unit;
}
}
// Remove trailing zero
if (result.endsWith('零')) {
result = result.substring(0, result.length - 1);
}
return result;
}
// Copy result to clipboard
function copyResult() {
const text = chineseResult.textContent;
if (!text) return;
navigator.clipboard.writeText(text).then(() => {
showToast();
}).catch(err => {
console.error('Failed to copy:', err);
});
}
// Show toast notification
function showToast() {
toast.classList.remove('hidden');
setTimeout(() => {
toast.classList.add('hidden');
}, 2000);
}
// Load last conversion from localStorage
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);
}
}
}
// Initialize
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>