从数字到版面:得物数据产品里数字格式化的那些事

一、前 言

做数据前端,你会很快建立一个共识:

怎样把枯燥的数字用合适的方式展示出来,是我们的第一要务,但这只是起点。

如果说规范的数字排版是中后台系统的"地基" ,保证了信息的准确传达;那么可视化图表就是地基之上的"建筑" 。地基稳固,建筑才能发挥其功能------让用户从微观的读数中解放出来,更快速地识别趋势、定位异常,从而真正从数据中获取规律。

但这篇主要想聊的,不是那座"建筑",而是这块往往被忽视,却决定了整个系统专业度的"地基"------数字格式化。

请看得物各业务线里的这些日常场景:

  • 商品详情页:券后价、折扣、分期单价等;
  • 智珠(得物社区运营平台)、得数(自助取数平台)、智能运营(得物交易运营平台)里的 GMV、转化率、留存率、PVCTR等;
  • 社区里帖子阅读量、点赞数、粉丝数。

这些数字表面上只是 number,一旦出现在屏幕上,就自动变成版面的一部分:

对齐方式、位数长短、小数几位、有没有"万 / 亿"、单位怎么放------都会影响到整个页面的节奏和专业感。

排版本质上是在管理信息和秩序:层级、节奏、对齐、留白。而数字不是排版的"附属品",恰恰是这些原则最密集的载体。

本文不发散去谈数据可视化,只专注于"数字格式化"这一件事:

  1. 什么是数字格式化?它背后有哪些鲜为人知的文化差异和技术细节?
  2. 如果没有统一方案,失控的数字会给产品决策、UI 排版和工程维护带来什么麻烦?
  3. 得物数据前端在得数 / 智珠 / 智能运营里,是怎么把这件事从"工具函数"做成"基础设施"的?
  4. 我们基于Intl.NumberFormat和@dfx/number-format 的方案,架构是怎样的,带来了哪些实际收益?

二、什么是"数字格式化"?不只是toFixed(2)

一提到提到数字格式化,第一反应是:toFixed(2)、拼个%、加个¥就完事了或者通过正则来拼接千分符。但在真实世界里,"同一个数长成什么样"远比想象复杂。

2.1 数字写法本身就是"文化差异"

zh.wikipedia.org/wiki/小数点

小数分隔符的符号演变史

据Florian Cajori 1928年的著作《数学符号史》记载,小数分隔符(旧称 separatrix)的演变经历了一个漫长的标准化过程。

在早期数学文献中,不同地区对小数的处理方式各异。以数值 34.65 为例,中世纪文献常采用在整数与小数间加横线或在个位数字上方加注标记的方式。英国早期曾采用竖线"|"作为分隔,后在印刷中逐渐简化为逗号或点,这与当时阿拉伯数学家主要使用逗号的习惯相呼应。

"点"与"逗号"分流的历史成因

17 世纪末至 18 世纪初是符号标准化的关键时期,也是英美体系与欧洲大陆体系产生分歧的起点。这一差异在很大程度上受到了微积分发明者及其符号体系的影响:

  1. 欧洲大陆(莱布尼茨的影响) :德国数学家莱布尼茨提议使用"点"作为乘法符号。这一提议经由克里斯蒂安·沃尔夫等学者的推广,在欧洲大陆教科书中广泛普及。为了避免符号含义的冲突,欧洲大陆数学家普遍采用了"逗号"作为小数分隔符。
  2. 英国(牛顿体系的延续) :英国数学界未采纳莱布尼茨的乘法符号,而是沿用"X"表示乘法。因此,"点"在英国并未被乘法占用,得以继续作为小数分隔符使用。据统计,18 世纪初的英国教科书中,约 60% 使用点,40% 使用逗号;而到了 18 世纪末,点已成为英国的绝对主流。

标准的确立

尽管比利时和意大利等国曾长期坚持使用点作为小数分隔符,但最终均向欧洲大陆的主流标准靠拢,改用了逗号。至此,英美使用"小数点"、欧洲大陆使用"小数逗号"的格局基本定型。

值得注意的是,直至20世纪初,符号的统一仍未完全完成,各类文献中仍可见等非标准写法。

  • 英语系 / 中国常见写法:1,234,567.89
  • 很多欧洲国家:1.234.567,89(点是千分位,逗号是小数)
  • 瑞士习惯:1'234.56或1'234,56,用撇号 ' 做分组。

这些规则都已经被整理进Unicode CLDR和ICU数据库,现代浏览器的Intl.NumberFormat就是建立在这套数据之上,能根据 locale 自适应这些写法。

所以,一个简单的1,000:

  • 在美国人或中国人眼里是" one thousand / 一千";
  • 在某些欧洲语境下可能被读成"保留三位小数的一点零"。

一旦你做电商、跨境、数据产品,这种"写法"的差异,就不再是小问题,而是直接影响决策和合规的东西。

2.2 数字不只是"大小"还有语义和语气

在 UI 里,我们其实经常在表达"数字的语气":

  • +12.3%:不是纯数学"加号",而是一种"上涨"的信号,排版上常常配合红/绿颜色;
  • 1.2M / 120 万:为了节省空间和降低认知负担,用缩写表示量级;
  • < 0.01:极小数值,让用户知道"接近 0",而不是盯着一串 0.000032 的数字尾巴发呆;
  • --- /N/A:告诉用户"这里是没数据 / 异常",而不是"就是 0"。

这些都属于"数字的表达 "而非"运算结果"。

如果我们只用toFixed和拼字符串,很难让这些语气在整个系统内保持一致。

2.3浏览器和 Node 已经给了我们一个"引擎"

ECMAScript 402标准中提供的 Intl.NumberFormat,是一个专门做本地化数字格式化的构造器。

它支持:

  • 根据 locale 切换小数点、分组规则、数字符号;
  • 货币格式:style: "currency" + currency: "CNY" | "JPY" | ...;
  • 百分比:style: "percent",自动乘 100 并加 %;
  • 紧凑表示:notation: "compact",输出 9.9M、9.9亿等缩写;
  • formatToParts():把数字拆成整数、小数点、小数、货币符号等片段,方便做更精细的排版。

所以,数字格式化的"引擎问题"其实已经有人帮我们解决了------真正难的是:

  • 怎么结合业务语义;
  • 怎么结合排版规范;
  • 怎么在多个系统之间做到一致;
  • 怎么治理"不乱写"的工程实践。

这就回到我们自己的故事。

三、如果没有统一方案:三个数据产品的日常

下面这几个场景,相信你在得物或类似电商/数据平台里一定见过。

想象一张典型后台页面:

  • 顶部是活动看板 KPI 卡片;
  • 中间是按品类/渠道拆开的表格;
  • 下方是创作者表现列表。

3.1 价格:同一张订单,不同系统"长得不同"

那么,以前我们是怎么做的?

在没有统一规范的"蛮荒时代",面对一个数字,我们的第一反应往往是:"这还不简单?拼个字符串不就完事了?"

这种代码,你一定写过,或者在项目的某个角落实实在在地见过:

场景一:简单粗暴的拼接一把梭

javascript 复制代码
// "我管你什么场景,先拼上去再说"
function formatPrice(value: number) {
  // 隐患埋雷:这里是全角¥还是半角¥?null 会变成 "¥null" 吗?
  return '¥' + value.toFixed(2);
}

场景二:为了千分位,手写正则"炫技"

ini 复制代码
// "网上一搜一大把的正则,看着能跑就行"
export const formatNumberWithCommas = (number: number | string) => {
  const parts = number.toString().split('.');
  
  // 经典的正则替换,但这真的覆盖了所有负数、极大值场景吗?
  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  
  // 手动补零逻辑,不仅难维护,还容易在边界情况(如 undefined)下报错
  if (parts.length > 1 && parts[1]?.length === 1) {
    parts[1] = ${parts[1]}0;
  }
  return parts.join('.');
};

这看起来似乎"能用",并且用起来貌似也没啥问题。但当业务复杂度稍微上来一点或者需要做国际化的时候,那接手这个需求的同学只能发出一句『哦吼』。

  1. 视觉上的"各自为政"
    1. 符号打架: 商品卡片用半角 ⁠¥,详情页用全角 ⁠¥,甚至有的地方混用了 ⁠CNY。
    2. 精度随心: 有的开发觉得 ⁠toFixed(2) 严谨,有的觉得 ⁠。00 太多余直接砍掉,导致同一个页面里,导致同一个页面里,数字像锯齿一样参差不齐。
  2. 排版上的"秩序崩塌"
    1. 试想一个表格列:⁠1299、⁠1,299.00、⁠1299.0 混在一起。
    2. 对齐基准线完全错乱,尤其在表格里,就和『狗啃过一样』,犬牙交错,参差不齐,用户的眼睛需要在不同的小数位之间来回跳跃,阅读体验极差。
  3. 国际化上的"死胡同"
    1. 这种硬编码逻辑(Hardcode),完全堵死了国际化的路。
    2. 一旦业务要支持 USD(美元)、JPY(日元,默认无小数)、EUR(欧元,部分国家用逗号做小数点)。
    3. 比如 ⁠1,234,567.89(英美)和 ⁠1.234.567,89(德意),这完全不是改个符号就能解决的,而是整个数字书写逻辑的根本差异。

我们原本想省事写的小函数,最终变成了阻碍系统演进的技术债务。

3.2看似智能的"万/亿"缩写,其实是硬编码的陷阱

在创作者中心或内容社区,我们希望阅读量(PV)能符合用户的直觉认知:

  • 小数值:⁠< 10,000 时,显示完整数字,精确到个位;
  • 中量级:⁠≥ 10,000 时,缩写为 ⁠X.X 万;
  • 海量级:≥ 100,000,000 时,缩写为 ⁠X.X 亿。

如果是海外版,需求又变成了:⁠1.2k/⁠3.4M/5.6B。

于是,我们写出了那段经典的⁠formatPv

scss 复制代码
function formatPv(count: number) {
  if (count < 10000) return String(count);
  if (count < 100000000) return (count / 10000).toFixed(1) + '万';
  return (count / 100000000).toFixed(1) + '亿';
}

这段代码逻辑清晰,看似解决了问题。但在真实场景中,它却是一个"Bug 制造机":

1.临界点的"视觉突变"

  • 9999 下一秒变成 ⁠1.0 万?产品会立刻找你:"这个数展示是不是不大对,能不能9999之后显示 1.0w 而不是 1.0 万?"
  • 99950 四舍五入变成 ⁠10.0 万,要不要显示成更直观的 ⁠10 万?这些微小的细节,需要堆砌大量的 ⁠if-else 来修补。

2.维护上的"复制粘贴地狱"

  • 中文版写一套,英文版 ⁠formatPvEn 再写一套,等咱们的业务做往世界各地的时候,那得要多少 formatPv(xx)呢?
  • A 项目拷一份,B 项目拷一份。等哪天产品说"万"后面不留小数了,你得去 N 个仓库里把这行代码找出来改一遍。最终结果就是:全站的缩写策略处于一种"相似但不一致"的薛定谔状态。

3.文化适配上的"盲区"

  • 这套逻辑是典型的"简体中文中心主义"。
  • 印度用户看到⁠100k 是没感觉的,他们习惯用 ⁠Lakh (10 万) 和 ⁠Crore (1000 万);
  • 阿拉伯语区可能需要用东阿拉伯数字。

这不仅仅是把"万"翻译成"Wan"的问题,而是数字分级逻辑在不同文化中完全不同,在前面针对符号系统我们也提到过。

我们在 UI 上硬塞了一套"只能在简体中文里自洽"的规则,这在国际化产品中是行不通的。

3.3 被"抹平"的语义---0、空值与极小值

在得数(自助取数平台)、智珠(得物社区运营平台)、智能运营(得物交易运营平台)这类重度数据看板中,我们充斥着各种比率型指标

  • 风控类:鉴别通过率、拦截率;
  • 履约类:超时率、投诉成功率;
  • 增长类:活动转化率、复购率。

面对这些指标,以前最常见的处理方式是写一个通用的 formatPercent:

javascript 复制代码
function formatPercent(value: number | null | undefined) {
  // "没数据就当 0 算呗,省得报错"
  return ((value || 0) * 100).toFixed(2) + '%';
}

这行代码虽然只有一句话,却在业务层面犯了三个严重的错误:

1. 混淆了"事实"与"缺失"

  • ⁠null 代表数据未产出或链路异常(就是没数),而 ⁠0 代表业务结果确实为零。
  • 代码粗暴地将 ⁠null 转为 ⁠0.00%,会让用户误以为"今天没人投诉"或"转化率为 0",从而掩盖了背后的系统故障或数据延迟。

2. "抹杀"了长尾数据的价值

  • 对于 AB 或算法模型来说,⁠0.000032 是一个具备统计意义的概率值。
  • 被这行代码强行截断为 ⁠0.00% 后,业务同学会困惑:"为什么 P 值还能是 0 的嘛?"这直接损害了数据的公信力,严重点来说,都会影响业务决策。

3. 阻断了精细化表达的可能

当你想把极小值优化为 ⁠< 0.01% 这种更科学的表达时,你通过 vscode 一搜代码,500+ 文件,直接就两眼一黑。

从排版设计的角度看,这三者本应拥有完全不同的视觉层级:

  • 空值(No Data) :使用 ⁠--- 或灰色占位符,表示"此处无信息",降低视觉干扰;
  • 异常值(Error) :使用 ⁠N/A 或警示色,提示用户"数据有问题";
  • 极小值(Tiny Value) :使用 ⁠< 0.01% 或者≈ 0,保留数据的存在感,同时传达"接近于零"的准确语义。

得数DataHub在治理展示层时发现,大量"同一个指标在不同页面长得不一样",根源就在于这些各自为政、缺乏语义区分的格式化规则。

四、从"写工具函数"到"定义展示语义"

回顾上述场景,透过那些混乱的代码片段,可以发现三个共性的系统性难题:

  1. 逻辑熵增:每个业务线、甚至每个页面都在重复造轮子。⁠formatPrice、⁠formatPercent 遍地开花,前后端逻辑割裂,维护成本随着业务扩张呈指数级上升。
  2. 无法治理:想把全站的"万 / 亿"阈值统一?或者把某种费率的精度从两位改成四位?这几乎是不可能的任务。
  3. 体验失控:设计规范虽然写着"空值用 ---",但落实到代码里全看开发心情。结果就是用户在不同系统间切换时,看到的是一种"似是而非"的统一,严重影响了产品的专业感。

为了解决这些问题,在数据域产品中,我们对"格式化"这件事进行了重新定义:

它不只是前端的 UI 渲染逻辑,而是指标定义的一部分。

我们不仅要定义"指标的计算口径",更要定义"指标的展示语义"。

在 Galaxy(指标管理平台)定义好"数是怎么算出来的"之后,得数 DataHub 承担起了定义"数该怎么被看见"的职责。我们将这层逻辑抽象为 "展示语义"(Visualization Semantics)

  • 定类型(Type) :它是金额(Currency)、比率(Ratio),还是计数(Integer)?
  • 定单位(Unit) :默认是元 / 万元,还是 %、‰ (千分比) 或 bp (基点)?
  • 定精度(Precision) :小数位是固定保留两位,还是根据数值大小动态截断?
  • 定状态(State) :遇到 Null(空值)、Error(异常)或 Epsilon(极小值),展示层该如何兜底?
  • 定场景(Context) : 在空间局促的 KPI 卡片里(追求简洁),和需要财务核对的 明细表格里(追求精确),是否应用不同的渲染策略?

这是一种架构上的升维:

这些关于"长什么样"的逻辑,从此不再散落在业务代码的 ⁠if-else 里,而是被统一收拢到元数据系统中进行管理。

从这一刻起,数字格式化不再是前端模板里的一个小工具,而是成为了得数体系的一项基础设施 ------一层独立、可配置、可治理的数据领域服务

五、开始造轮子:站在Intl.NumberFormat 肩膀上

在着手开发之前,首先确立了一个原则:底层能力不造轮子,拥抱 Web 标准。

ECMAScript 402 标准中的 ⁠Intl.NumberFormat 已经为我们提供了一个极其强大的本地化格式化引擎。它的能力远超大多数手写的正则替换:

javascript 复制代码
const n = 123456.789;


// 德国:点号分组、逗号小数
new Intl.NumberFormat('de-DE').format(n);
// "123.456,789"


// 印度:lakh/crore 分组
new Intl.NumberFormat('en-IN').format(n);
// "1,23,456.789"


// 日元货币:默认 0 位小数
new Intl.NumberFormat('ja-JP', {
  style: 'currency',
  currency: 'JPY',
}).format(n);
// "¥123,457"

它完美解决了那些最让前端头疼的国际化底层问题

  • 文化差异:全球各地的千分位、小数点、数字符号习惯;
  • 货币规则:不同币种的标准小数位(如日元 0 位,美元 2 位)和符号位置;
  • 多态支持:内置了百分比、紧凑缩写(Compact Notation)、科学计数法等模式;
  • 排版能力 :通过 ⁠formatToParts() 将数字拆解为数组(整数部分、小数部分、符号等),为精细化排版提供了可能,比如在小数或百分比符号比整数小 2 个字号。

但是,它天然"不懂"业务:

  • 它不懂中文的习惯 :无法直接实现"兆/京/垓"这种中文超大数缩写逻辑(标准最多支持到亿);
  • 它不懂得数的规范:不知道 ⁠*_rate 类型的指标在空值时要显示 ⁠"-",在极小值时要显示 ⁠< 0.01%;
  • 它不懂业务的上下文:不知道 GMV 在 KPI 卡片里要按"万元"展示,而在财务报表里必须精确到"厘"。

因此,我们的最终的架构策略是:

以 ⁠Intl.NumberFormat 为底层的"渲染引擎";

在其之上搭建一层"数字领域层"(Domain Layer);

专门用于转译得物的业务规则和排版语义。

六、@dfx/number-format:构建数字的"领域层"

在得物数据前端的大仓里,我们把这层能力实现为一个独立包:@dfx/number-format。专门服务得数、智珠、智能运营等内部系统。

可以把它理解为三件事的组合:

  • 一个统一封装了Intl.NumberFormat的核心引擎(含缓存、解析);
  • 一套可以被配置的业务规则 / 预设 (preset)和插件系统
  • 一组面向 React 和 Node 环境的接口(组件 + Hook + FP 函数)。

6.1 声明式开发:把"数字长什么样"抽象成规则

在业务开发侧,最终期望的目标是:让开发者只关心"这是什么指标",而不关心"它该怎么展示"。

场景一:基础格式化

我们可以直接使用组件,以声明式的方式调用:

ini 复制代码
import { NumberFormat } from '@dfx/number-format';
// 无论在哪个页面,价格都只需这样写
<NumberFormat
  value={price}
  options={{ style: 'currency', currency: 'CNY' }}
/>

场景二:基于语义的自动格式化

更进阶的用法是,在系统层面定义好"规则集",业务组件只需传入指标名称:

javascript 复制代码
import { NumberFormatProvider, AutoMetricNumber } from '@dfx/number-format';
// 1. 定义规则:所有 "price.cny" 类型的指标,都遵循人民币格式
const rules = [
  { name: 'price.cny', options: { style: 'currency', currency: 'CNY' } },
];
// 2. 注入上下文
<NumberFormatProvider options={{ rules }}>
  {/* 3. 业务使用:完全解耦,只传语义 */}
  <AutoMetricNumber name="price.cny" value={price} />
</NumberFormatProvider>

收益显而易见:

  • 自动化:CNY 自动带两位小数,切到 JPY 自动变 0 位小数,逻辑完全由底层接管。
  • 一致性:全站所有 ⁠price.cny 的地方,千分位、符号位置严格统一,版面节奏自然对齐。

场景三:批量匹配比率指标

对于成百上千个转化率指标,我们不需要逐一定义,只需一条正则规则:

javascript 复制代码
const rules = [
  // 匹配所有以 _rate 结尾的指标,自动转为百分比,保留2位小数
  { pattern: /_rate$/i, options: { style: 'percent', maximumFractionDigits: 2 } },
];


// 页面代码极其干净
<AutoMetricNumber name="conversion_rate" value={conversionRate} />

空值与异常值如何展示、极小值是否显示 < 0.01%、两位小数从哪里来,全部在规则 + 插件里处理。

6.2插件系统:收口那些"奇怪但常见"的需求

得物的业务场景中,存在大量 ⁠Intl 标准无法直接覆盖的边缘需求。我们将这些需求统一建模为插件(Format Plugin) ,介入格式化的生命周期:

  • 千分比 / 基点 (bps) :需在 ⁠pre-process 阶段将数值乘 1000 或 10000;
  • 中文大写金额:会计与合同场景的特殊转换;
  • 极小值兜底:设定阈值,当数值小于 ⁠0.0001 时,⁠post-process 阶段输出 ⁠< 0.01%;
  • 会计格式:负数使用括号 ⁠(1,234.56) 而非负号;
  • 动态精度策略:根据数值大小动态决定保留几位小数。

这套插件机制的意义在于"治理":

它让"在某个业务仓库里偷偷写一个特殊正则"成为过去式。任何新的格式化需求,都必须以插件形式接入,由数据前端统一评审、沉淀,最终复用到全站。

6.3 全链路打通:从Galaxy元数据到UI渲染

最后,将这套系统与得数的中台能力打通(也是目前我们正在做的),形成了一条完整的渲染链路。

在Galaxy和得数的元数据里,指标本身已经有code、label、type 等字段。我们只需要再加一点约定:

json 复制代码
{
  "metricCode": "gmv",
  "label": "GMV",
  "format": "currency", // 基础类型
  "meta": {
    "formatConfig": {"style": "currency", "maximumFractionDigits": 2}, 
    "category": ["交易指标"],
    "displayConfig": { "table": "precise", "card": "compact" }
  }
}

前端渲染时

  • 配置下发:页面加载时,获取指标的 ⁠Code 及元信息,转换为前端的 ⁠MetricFormatRule[]。
  • 上下文注入:在应用根节点通过 ⁠NumberFormatProvider 注入规则集。
  • 傻瓜式使用:表格、卡片、图表组件只需消费指标 ID:

这样一来就实现了真正的『数据驱动 UI』:

  • "指标长什么样"只在得数元数据管理中定义一次。
  • 修改展示规则(如调整精度),只需改配置,无需批量修改前端代码,更无需重新发版;
  • 前台业务与中台报表看到的同一个指标,格式永远是物理上的一致。

七、统一之后:重塑设计、工程与业务的价值

从我们的视角出发,这套统一数字格式方案的落地,带来的收益远不止"代码整洁"那么简单,它在三个层面产生了深远的影响。

7.1 对设计与排版:版面终于可控了

曾经,产品需要在每个页面的验收中反复纠结数字的对齐和精度。现在,设计规范只需在文档中定义一次:

  • 金额类:统一保留两位小数,强制右对齐,货币符号半角化;
  • 比率类:空值兜底为 ⁠---,异常值显示 ⁠N/A,极小值转译为 ⁠< 0.01%;
  • 缩写策略:全站统一遵循"中文万/亿、英文 k/M/B"的梯度逻辑。

开发同学不再是『古法手搓』,而是直接接入统一的 Preset 和插件。

无论是看板、详情页,还是导出的 Excel 报表,数字风格保持了像素级的一致。从视觉角度看,我们相当于给数字建立了一套Design Token :精度、单位、占位符都有了标准索引,让整个平台呈现出高度统一的专业感和节奏感

7.2 对工程质量与效率:从"搜索toFixed"到"改一处生效全站"

所有数字格式逻辑集中在一个领域层和配置中心。

一类指标的规则变更(精度、单位、空值策略)可以配置化调整。

规约可以进入 Lint(数字格式化限制) / Code Review:

  • 禁止直接在业务代码中new Intl.NumberFormat、toFixed拼字符串;
  • 鼓励所有数字展示全部走@dfx/number-format与 DataHub 规则。

这就是工程治理层面的价值:它将"依赖开发者自觉"的软约束,变成了 "可配置、可维护、可迭代"的系统能力

7.3 对业务和数据信任:从"怀疑这数有问题"到"愿意用来决策"

统一数字格式还有一个最隐性、却最核心的价值:重构数据信任。

  • 消除歧义:前台商品价格与中台报表金额完全一致,运营不再因为"显示精度不同"而去质疑数据准确性;
  • 精准语义:空值(Null)和零(Zero)被清晰区分,避免了因格式问题导致的错误决策。

在得物,严谨是需要刻在基因里的关键词

作为数据前端,我们深知:懂数据,并能用最专业的方式呈现数据,是这一岗位的核心素养。 当我们不仅能交付代码,还能通过精准的数字展示消除歧义、传递信任时,技术与业务之间就建立起了一种深层的默契。

这种对数字细节的极致追求,不仅是对用户体验的尊重,更是对得物"正品感"品牌心智在数字世界的延伸。

八、数字,是版面的『最后一公里』

回头看,得物数据前端在得数、智珠智能运营这条线做的,其实有两件事:

  • 给予数字应有的"设计尊严"

我们不再将数字视为模板字符串里一个随时可替换的"黑洞",而是将其视作与字体、色彩、间距同等重要的排版元素,纳入统一的设计语言系统。

  • 为数字构建专属的"基础设施"

我们以 Web 标准⁠Intl.NumberFormat为引擎,以 ⁠@dfx/number-format为领域层,以得数 Schema 为配置中枢,将"数字如何展示"这一命题,从硬编码的泥潭中解放出来,转变为一种可配置、可治理、可演进的系统能力。

当你再回头看公司产品的各个页面------

  • 前台商品详情页的价格和券后价;
  • 社区里的阅读和点赞数字;
  • 智珠的策略看板;
  • 得数的自助分析报表。

你会发现它们拥有了一个共同的特征:

这些数字不再是各说各话的噪点,而是共同操着同一种精准、优雅的"排版语言"。

这就是我们做"数字格式化"的初衷:用技术的确定性,换取业务的专注力。

此时此刻,彼时彼刻,作为前端开发,你可以坚定地说出:我这展示的没问题,是数出问题了~

九、走出数据域:从内部门户到全业务

前面的篇幅,更多围绕着得数、智珠、智能运营等中后台系统展开。在这些场景下,数字格式化的核心用户是运营、分析师和算法同学------帮助他们透过数据看清业务走势。

但这套能力并不应局限于"后台"。它完全具备走出数据域的潜力,成为得物全业务线共享的一层关键基础设施

9.1 跨业务线:从"运营看板"走向"交易链路"

目前@dfx/number-format 虽然生长于数据土壤,但其解决的问题是通用的。未来,它可以自然地渗透到更广阔的业务场景:

  • 交易域: 统一商品详情页、结算页的价格、分期费率、税费展示逻辑;
  • 社区域: 标准化社区详情页中的风险分、阈值、命中率精度;
  • 客服域: 规范赔付金额、工单时长的展示口径。

这在工程上意味着一次"升维":

  • 依赖升级:将 ⁠@dfx/number-format 从数据域私有包提升为公司级的基础依赖;
  • 开发范式:新业务在处理数字展示时,默认动作不再是"手写一个 format 函数",而是查阅现有的 Preset 和插件;

最终带来的体验质变是:

用户无论是在得物 App的前台页面,还是在内部的各类管理系统中,看到的数字都拥有同一套呼吸感和视觉习惯。这种跨端的一致性,是品牌专业感最直接的体现。

9.2 跨地区与汇率:构建独立的"全球化价格能力"

随着得物业务的出海,多地区、多币种是绕不开的挑战。此时,数字领域层不仅要负责"长什么样",更要承担起"展示策略"的职责。

我们可以设想一个**「全球化价格组件」**:

ini 复制代码
<MultiRegionPrice
  skuId="123456"
  basePriceCny={price}
  regions={[
    { locale: 'zh-CN', currency: 'CNY' },
    { locale: 'en-US', currency: 'USD' },
    { locale: 'ja-JP', currency: 'JPY' },
  ]}
/>

这个组件将负责两层逻辑的解耦:

  • 计算层: 负责实时汇率换算、多币种价格计算。
  • 展示策略层
    • 对照展示: 是否显示"原币种 + 本地参考价"(如 ⁠JP¥ 20,000 (≈ $135.00));
    • 文化适配 : 是否遵循当地的"心理价位"策略(如 ⁠ <math xmlns="http://www.w3.org/1998/Math/MathML"> 199.99 v s ⁠ 199.99 vs ⁠ </math>199.99vs⁠200);
    • 格式渲染: 最终调用 ⁠@dfx/number-format,确保日元无小数、美元有小数、分节号正确。

从工程视角看,这避免了"汇率算对了、数字排版全乱了"的尴尬;从产品视角看,这正是得物沉淀"出海技术套件"的重要一步(比如国际智能运营)。

9.3 时间与日期:用同样的思路,去做第二条"格式化主线"

数字是一类挑战,"时间"则是另一类。跨时区转换、相对时间(此刻)、不同地区的日期写法(⁠DD/MM vs ⁠MM/DD),其复杂度丝毫不亚于数字。

既然浏览器提供了Intl.DateTimeFormat,我们完全可以复刻数字领域层的成功路径,再赢一次:

  • 基础设施:构建 ⁠@dfx/date-format,统一封装时间格式化与相对时间逻辑;
  • 预设管理:定义标准 Preset(如"运营报表用 ⁠YYYY-MM-DD"、"C 端动态用 ⁠今天 12:30");
  • 组件化:提供 ⁠ 和 ⁠ 组件;
  • 元数据打通:在得数的元数据中,针对时间型的维度也同样进行配置。

这样,数据前端的展示层就拥有了两条清晰的主线:

  • 数值主线 :⁠Intl.NumberFormat → ⁠@dfx/number-format → 数字展示规范
  • 时间主线 :⁠Intl.DateTimeFormat → ⁠@dfx/date-format → 时间展示规范

未来,我们甚至可以抽象出更上层的 ⁠@dfx/data-format,让"任何字段该怎么展示",完全由 Schema 配置 + 领域层规则共同决定。

十、最后:把"展示"这件事做到极致

如果只看代码,我们做的事情似乎很简单:

把 ⁠Intl 标准用到极致,封装了一层领域库,并接通了元数据配置,写了个工具库。

但如果从产品体验和工程演进的角度看,我们其实完成了一次基础设施的升级

把前端开发中最琐碎、最容易被忽视的"数字与时间展示",从"到处粘贴的小工具",升级成了"有统一规范、有可观测性、有迭代空间的系统能力"。

现在,这套能力已经让得数、智珠、智能运营的部分模块长出了统一的"数字气质"。

未来,期望它能够走出数据平台,支撑得物更广泛的业务场景,让同一件商品,在不同地区、不同语言、不同终端上,既算得对,又看得顺。

参考资料:

往期回顾

  1. 一文解析得物自建 Redis 最新技术演进

  2. Golang HTTP请求超时与重试:构建高可靠网络请求|得物技术

  3. RN与hawk碰撞的火花之C++异常捕获|得物技术

  4. 得物TiDB升级实践

  5. 得物管理类目配置线上化:从业务痛点到技术实现

文 /柏锐

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

相关推荐
拉不动的猪2 小时前
Vue 跨组件通信底层:provide/inject 原理与实战指南
前端·javascript·面试
用户6600676685392 小时前
用 Symbol 解决多人协作中的对象属性冲突实战
前端·javascript
yinuo2 小时前
前端跨页面通讯终极指南①:postMessage 用法全解析
前端
c***97982 小时前
Vue性能优化实战
前端·javascript·vue.js
哈哈O哈哈哈2 小时前
ECMAScript 2025 正式发布:10 个让你眼前一亮的 JavaScript 新特性!
前端·javascript
哈哈O哈哈哈2 小时前
2025 年值得关注的 CSS 新属性与功能
前端·css
我叫张小白。2 小时前
TypeScript泛型进阶:掌握类型系统的强大工具
前端·javascript·typescript
麦麦在写代码2 小时前
前端学习4
前端·学习
你听得到112 小时前
Web前端们!我用三年亲身经历,说说从 uniapp 到 Flutter怎么转型的,这条路我爬过,坑我踩过
前端·flutter·uni-app