前端常见业务,单位转换Convert Unit
昨天是三八妇女节,很开心他们能好好的出去玩玩了;不得不说工作至今我内人给我的帮助是巨大的,由衷感谢她对我的支持,三八妇女节快乐!
单位转换
项目中设计到很多长度、距离、速度、体积等单位信息,而作为一个成熟的产品自然需要单位可配置;经过分析其实只需要考虑前端的展示部分即可,因此,要实现一个前端单位转换的功能;
业务分析
👕 我们可以想到首先需要一个配置界面用来配置单位;需要一个函数传入value、原始单位、显示单位,输出转换后的value;很明显这个功能比较独立、功能单一,我们可以写一个包来实现。
👖 考虑到有些单位转换后可能会出现小数很长、或精度丢失的情况,所以我们还需要一个小数点显示几位和进位的配置。
同时;允许个性化的单项配置,比如房间默认单位是m,玻璃的单位却是mm,所以我们允许个性化配置
- 🍎配置: 单位配置界面;
- 🍊配置: 小数点配置;
- 🌺配置: 个性化单项配置(单位、小数点)
- 📐function:convertUnit 函数 实现单位转换(逆转换)
- 🔢function:precision 函数 实现小数精度控制
技术分析
- 🏭 我司项目都是基于umi V3框架来实现的;所以我们可以考虑写一个Umi插件来实现我们想要的功能;当然直接写一个 js 包也未尝不可,但是umi插件有个优势那就是配置集中和🎈 Import All From Umi,个人表示最喜欢这个特点 🤭;我们也可以通过配置来实现包大小的控制,只导出/打包需要的;
- 🔩 转换单位有个库比较推荐 convert-units,convert-units有一个简单的链式 API,易于阅读。它还可以配置参与其打包的度量单位,也可以自定义度量单位。
Umi 插件
,convert-units
,React
,Typescript
;
数据结构
🏳️🌈 配置这块当然是越简单越好了;
{
"default": { //默认配置(房屋设计单位)
"m": "m", //key部分 表示后端返回的数据单位,value部分表示 显示的单位
"m/s": "m/s",
"precision": 1 // 精度,[*0* 整数,*1*一位小数,*2* 两位小数]
},
"glass.length": { //个性化单项配置(玻璃长度)
"m": "cm",
"precision": 1
}
}
代码实现
我们先实现单位转换函数
其实这个很简单,
import configureMeasurements from 'convert-units';
import length from 'convert-units/definitions/length';
const convert = configureMeasurements({ length,
});
console.log(convert(1).from('m').to('cm'));
//output 1000
所以我们的函数是
type Units = LengthUnits;
/**
* 单位转换
* @param value
* @param from 'cm' | 'm' | 'km';
* @param to 'cm' | 'm' | 'km';
* @returns
*/
function convert(value: number, from: Units, to: Units) {
return from === to ? value : ConvertUnits(value).from(from).to(to);
}
console.log(convert(1,'m','cm'));
//output 1000
🔥🔥🔥 注意这里from,to写成 string 会报一个类型错误,这里我们用type关键字单独声明一下。
然后实现精度进位函数
/**
* 精度
* @param value 默认保留两位小数
* @param precision 0 | 1 | 2
* @param round true | false 是否四舍五入
* @returns
*/
function precision(value: number, precision: number = 2,round:boolean = false): number {
const p = 1 / Number('1e-' + precision);
// Number.EPSILON避免精度丢失
return Math[!round?'trunc':'round']((value + Number.EPSILON) * p) / p;
}
export { ConvertUnits,convert, precision };
console.log(precision(1.345, 0));
//output 1
console.log(precision(1.345, 1));
//output 1.3
console.log(precision(1.345, 2));
//output 1.34,
console.log(precision(1.345, 2, true));
//output 1.35,
这个函数没什么讲的,🔥🔥🔥 注意精度丢失问题和避免使用 toFixed即可。
单位配置
我们先看一下效果
很简单,根据数据结构我们直接做状态管理就行,界面部分我们不讲,不在此次讨论范围;
const [ unitConfigs, setUnitConfig ] = useState({
//后期可以换成useSelector,用models管理状态对接后端API。
default: { mm: 'mm', 'cm/s': 'cm/s', precision: 2 },
'glass.length': { mm: 'cm', precision: 1 },
});
开始,先新启一个 Umi 插件项目
yarn create umi
● Umi Plugin (for plugin development)
● yarn
● taobao (recommended for China)
● umi-plugin-convert-unit
- 🐱 首先我们考虑使用convert-units,我们可以通过 Umi 插件中的Ïapi.addOnDemandDepsÏ自动安装convert-units;
- 🐶 我们看到 convert-units官网的一句话。
convert-units 按需引用度量单位
import configureMeasurements from 'convert-units'; import volume from 'convert-units/definitions/volume'; /* `configureMeasurements` is a closure that accepts a directory of measures and returns a factory function (`convert`) that uses only those measures. */ const convert = configureMeasurements({ volume, mass, length, });
因此,没有引用的模块是不会打包的。
但是问题来了,我们想通过如下类似配置来动态导入所需要的度量模块,应该怎么操作呢?
///.umirc.ts
export default{
ConvertUnits: ["area", "length", "mass", "pressure", "speed", "volume"],
...
检查一下api.writeTmpFileAPI 发现这个 API 是支持模版字符串的;通过动态给定context模板上下文来实现动态导入,代码如下;
/// **index.ts**
//自动安装convert-units
api.addOnDemandDeps(() => [{ name: "convert-units", version: "^3.0.0-beta.5" }]);
api.onGenerateFiles(async () => {
api.writeTmpFile({
path: "index.ts",
tplPath: join(__dirname, "./core/convert.tpl"),
context: {
measures: (api.config.ConvertUnits || ["length"]).map((value: any, index: number) => ({ value, index })),
upper: function () {
return this.value.slice(0, 1).toUpperCase() + this.value.slice(1).toLowerCase();
},
first: function () {
return this.index === 0;
},
link: function () {
return this.index !== 0;
},
},
}); // types.d.ts
api.writeTmpFile({
path: "types.d.ts",
content: "export {}",
});
});Ï
🔥🔥🔥 悄悄告诉你,Umi 插件模版字符串用的是mustache,不要问我你怎么知道的;
/// **core/convert.tpl**
import configureMeasurements, {
{{#measures}}
{{value}},{{#upper}}{{.}}{{/upper}}Systems,{{#upper}}{{.}}{{/upper}}Units,
{{/measures}}
} from "convert-units";
// Measures: The names of the measures being used
type Measures = {{#measures}}{{#first}}'{{value}}'{{/first}}{{#link}} | '{{value}}'{{/link}}{{/measures}};
// Systems: The systems being used across all measures
type Systems = {{#measures}}{{#first}}{{#upper}}{{.}}{{/upper}}Systems{{/first}}{{#link}} | {{#upper}}{{.}}{{/upper}}Systems{{/link}}{{/measures}};
// Units: All the units across all measures and their systems
type Units ={{#measures}}{{#first}}{{#upper}}{{.}}{{/upper}}Units{{/first}}{{#link}} | {{#upper}}{{.}}{{/upper}}Units{{/link}}{{/measures}};
let config = {
{{#measures}}
{{value}},
{{/measures}}
};
const ConvertUnits = configureMeasurements<Measures, Systems, Units>(config);
OK yarn link 到项目中试一下,我们只配置 ConvertUnits: ['length', 'speed'],
,两个度量单位,看看效果。完美 😘!
/// **src/.umi/plugin-convertunits/index.ts**
// @ts-nocheck
import configureMeasurements, {
length,LengthSystems,LengthUnits,
speed,SpeedSystems,SpeedUnits,
} from "convert-units";
// Measures: The names of the measures being used
type Measures = 'length' | 'speed';
// Systems: The systems being used across all measures
type Systems = LengthSystems | SpeedSystems;
// Units: All the units across all measures and their systems
type Units = LengthUnits | SpeedUnits;
let config = {
length,
speed,
};
const ConvertUnits = configureMeasurements<Measures, Systems, Units>(config);
...
function convert(value: number, from: Units, to: Units){...}
function precision(value: number, precision: number = 2){...}
OK ,至此 Umi 插件开发的核心部分基本上就这些。
⚠️ 如果convert-units中提供的度量单位不满足的时候,我们可以单独扩展他,并通过api.writeTmpFile写入 ts 文件,再将上方config字面量进行结构扩展即可;
插件使用
1️⃣ package.json 中安装插件
yarn add @umi/plugin-convert-units
2️⃣ .umirc.ts 中配置插件、及插件配置(好绕口)
export default defineConfig({
plugins: ['@umi/plugin-convert-units'],
ConvertUnits: ['length', 'speed'],
});
3️⃣ import 之:
import { convert, precision } from 'umi';
4️⃣ 结合我们之前的配置实现单位自动转换
...
const {
unitConfig: { defaultConfig, ...otherConfig },
} = yield select((state: { configModel: any }) => state.configModel);
...
array.map((item:any)=>{
let { value, unit, showUnit,key, ...other } = item;
const unitConfigItem = otherConfig?.[key] || defaultConfig;
//容错
if(!showUnit) showUnit = unit;
// 转换单位
if (!!showUnit && !!unit) {
value = convert(Number(value), showUnit, unit);
}
// 精度
value = precision(Number(value), unitConfigItem?.['precision']) ;
return {value, unit, key, ...other}
})
...
总结
- 🌻数据结构:我们比较喜欢简单易扩展的数据结构;
- 🌻convert-units:这个转换单位的第三方库支持TS,动态载入,单位类型限制;
- 🌻Umi 插件:通过插件我们可以很容易的集成一些通用的功能,不用再考虑类型检查、安装版本的问题。
- 🌻mustache.js:这是一种无逻辑的模板语法。它可以用于 HTML、配置文件、源代码 - 任何东西,它通过使用散列或对象中提供的值扩展模板中的标签来工作。没有 if 语句、else 子句或 for 循环。
最后附上mustache.js搞笑的动图
本文由mdnice多平台发布
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具