1) 工作台
- 删掉欢迎区右侧的「运行日期」卡片,含 luxury.css 里对应样式与窄屏残留
2) 顶栏时钟(仿小满 CRM)
- 新增 TimeZoneClock:时钟图标 +「东八区:北京,上海 ⌄」+ 大号时间(20px 等宽,
tabular-nums 保证秒位不跳动),下方小字带日期
- 点整块展开时区列表,23 个时区由西向东排列,每行右侧实时显示该地时间与 UTC 偏移
- 时区用 IANA 名(Europe/London 等)而非写死偏移,夏令时自动正确;
打开列表会自动滚到当前选中项;选择记 localStorage('happy.clockZone')
- 新增 src/timezones.js 集中时区表与格式化工具
3) 顶栏汇率计算器
- 新增 FxCalculator:计算器图标点开浮层,默认 USD→CNY
- 左右两侧各带可搜索的币种下拉(币种来自接口返回的全部币种,常用币种排前),
中间一键互换;任一框输入另一框实时反算;显示主汇率与倒数汇率
- 换币种时锁住所改那一侧的数字(与 Google 换算一致)
- 金额按数量级自动取精度(USD→VND 与 CNY→USD 都不出现一堆无意义的零)
- 新增 src/currencies.js(币种中文名 + 排序 + 交叉汇率与格式化)
4) 后端汇率接口
- 新增 /api/fx/rates:以 USD 为基准返回全量汇率,前端据此算任意币种交叉汇率
- 主源 open.er-api.com(166 币种),备源 frankfurter.app(欧洲央行);
内存 + 落盘缓存 1 小时,并发请求只发一次网络
- 网络不可用时依次回退「上次成功的数据」→「内置离线汇率表」,
并在响应里用 source/warning 标注,前端明确提示数据来源
5) 顶栏排布
- 时钟变宽后中等屏会溢出,新增 ≤1440px 断点收紧留白/品牌区/搜索框;
搜索框允许收缩;1280/1100/980/700 各档实测无横向溢出
- 移动端(≤680px)汇率浮层改用固定定位铺满两侧,避免左侧被裁
6) .gitignore
- backend/data/ 整体忽略(trade.db、fx-cache.json 等全是运行时产物)
32 lines
930 B
JavaScript
32 lines
930 B
JavaScript
/**
|
|
* 汇率接口。
|
|
* 挂载点:/api/fx
|
|
*/
|
|
const express = require('express');
|
|
const fx = require('../lib/fx');
|
|
|
|
const router = express.Router();
|
|
|
|
/**
|
|
* GET /api/fx/rates?force=1
|
|
*
|
|
* 返回以 USD 为基准的完整汇率表:
|
|
* { ok, base: 'USD', rates: { USD:1, CNY:7.1, ... }, updatedAt, source, sourceLabel,
|
|
* stale, cached, warning? }
|
|
*
|
|
* 前端拿到后自行做交叉汇率:amount * rates[to] / rates[from]。
|
|
* 一次请求覆盖所有币种,切货币不再发请求。
|
|
*/
|
|
router.get('/rates', async (req, res) => {
|
|
try {
|
|
const force = String(req.query.force || '') === '1';
|
|
const data = await fx.getRates(force);
|
|
res.json({ ok: true, base: 'USD', count: Object.keys(data.rates).length, ...data });
|
|
} catch (error) {
|
|
// getRates 内部已兜底,走到这里属于意料之外
|
|
res.status(500).json({ ok: false, error: error.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|