Files
manpengan 92304d4fa2 feat: 汇率换算功能完整实现
- 首页双向换算:30种主流货币,实时计算,一键互换
- 汇率总览页:一对多查看所有货币换算结果
- 数据源 open.er-api.com,4小时本地缓存 + 失败兜底
- UI 美化:渐变背景、分色货币块、紫色互换按钮

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 17:26:53 +08:00

131 lines
3.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 汇率 API 调用 + 本地缓存
* 数据源https://open.er-api.com/v6/latest/{base}
* 免费,无需 key每日更新1500 次/月
*/
const CACHE_KEY = 'exchange_rates';
const CACHE_TTL = 4 * 60 * 60 * 1000; // 4 小时
function getCache() {
try {
const data = wx.getStorageSync(CACHE_KEY);
if (!data) return null;
return data;
} catch (e) {
return null;
}
}
function setCache(data) {
try {
wx.setStorageSync(CACHE_KEY, data);
} catch (e) {
console.warn('cache write failed', e);
}
}
function isCacheValid(cache, base) {
if (!cache || !cache.rates || !cache.timestamp) return false;
if (cache.base !== base) return false;
return Date.now() - cache.timestamp < CACHE_TTL;
}
function fetchRates(base) {
return new Promise((resolve, reject) => {
wx.request({
url: `https://open.er-api.com/v6/latest/${base}`,
method: 'GET',
success(res) {
if (res.statusCode === 200 && res.data && res.data.result === 'success') {
resolve(res.data.rates);
} else {
reject(new Error('API 返回异常'));
}
},
fail(err) {
reject(err);
},
});
});
}
async function getRates(base) {
const cache = getCache();
if (isCacheValid(cache, base)) {
return {
rates: cache.rates,
updatedAt: cache.updatedAt,
fromCache: true,
};
}
try {
const rates = await fetchRates(base);
const now = new Date();
const cacheData = {
base,
rates,
timestamp: now.getTime(),
updatedAt: formatTime(now),
};
setCache(cacheData);
return {
rates,
updatedAt: cacheData.updatedAt,
fromCache: false,
};
} catch (e) {
// API 失败时用过期缓存兜底
if (cache && cache.rates) {
return {
rates: cache.rates,
updatedAt: cache.updatedAt + '(缓存)',
fromCache: true,
};
}
throw e;
}
}
function convert(amount, fromCode, toCode, rates) {
if (!rates || !rates[fromCode] || !rates[toCode]) return 0;
if (fromCode === toCode) return amount;
return (amount / rates[fromCode]) * rates[toCode];
}
function formatAmount(value, code) {
// 日元、韩元、越南盾、印尼盾等无小数货币
const noDecimal = ['JPY', 'KRW', 'VND', 'IDR'];
if (noDecimal.includes(code)) {
return Math.round(value).toLocaleString('en-US');
}
return Number(value.toFixed(2)).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
}
function formatRate(value) {
if (value >= 100) return value.toFixed(2);
if (value >= 1) return value.toFixed(4);
return value.toFixed(6);
}
function formatTime(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}`;
}
module.exports = {
getRates,
convert,
formatAmount,
formatRate,
};